fict 0.2.0 → 0.2.2
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/{chunk-QPBAYI4X.js → chunk-YSNNU4OE.js} +30 -2
- package/dist/chunk-YSNNU4OE.js.map +1 -0
- package/dist/index.cjs +28 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plus.cjs +62 -1
- package/dist/plus.cjs.map +1 -1
- package/dist/plus.d.cts +11 -0
- package/dist/plus.d.ts +11 -0
- package/dist/plus.js +35 -2
- package/dist/plus.js.map +1 -1
- package/dist/slim.cjs +14 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.cts +21 -0
- package/dist/slim.d.ts +21 -0
- package/dist/slim.js +11 -0
- package/dist/slim.js.map +1 -0
- package/package.json +8 -3
- package/src/resource.ts +58 -0
- package/src/slim.ts +25 -0
- package/src/store.ts +28 -1
- package/dist/chunk-QPBAYI4X.js.map +0 -1
|
@@ -53,12 +53,22 @@ function $store(initialValue) {
|
|
|
53
53
|
boundMethods.set(prop, { ref: currentValue, bound });
|
|
54
54
|
return bound;
|
|
55
55
|
}
|
|
56
|
+
{
|
|
57
|
+
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
58
|
+
if (boundMethods && boundMethods.has(prop)) {
|
|
59
|
+
boundMethods.delete(prop);
|
|
60
|
+
if (boundMethods.size === 0) {
|
|
61
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
56
65
|
if (typeof currentValue === "object" && currentValue !== null) {
|
|
57
66
|
return $store(currentValue);
|
|
58
67
|
}
|
|
59
68
|
return currentValue;
|
|
60
69
|
},
|
|
61
70
|
set(target, prop, newValue, receiver) {
|
|
71
|
+
const oldLength = Array.isArray(target) && prop === "length" ? target.length : void 0;
|
|
62
72
|
const oldValue = Reflect.get(target, prop, receiver);
|
|
63
73
|
const hadKey = Object.prototype.hasOwnProperty.call(target, prop);
|
|
64
74
|
if (oldValue === newValue && hadKey) {
|
|
@@ -68,6 +78,9 @@ function $store(initialValue) {
|
|
|
68
78
|
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
69
79
|
if (boundMethods && boundMethods.has(prop)) {
|
|
70
80
|
boundMethods.delete(prop);
|
|
81
|
+
if (boundMethods.size === 0) {
|
|
82
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
83
|
+
}
|
|
71
84
|
}
|
|
72
85
|
const signals = SIGNAL_CACHE.get(target);
|
|
73
86
|
if (signals && signals[prop]) {
|
|
@@ -83,6 +96,18 @@ function $store(initialValue) {
|
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
if (Array.isArray(target) && prop === "length") {
|
|
99
|
+
const nextLength = target.length;
|
|
100
|
+
if (typeof oldLength === "number" && nextLength < oldLength) {
|
|
101
|
+
const signals2 = SIGNAL_CACHE.get(target);
|
|
102
|
+
if (signals2) {
|
|
103
|
+
for (let i = nextLength; i < oldLength; i += 1) {
|
|
104
|
+
const key = String(i);
|
|
105
|
+
if (signals2[key]) {
|
|
106
|
+
signals2[key](void 0);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
86
111
|
triggerIteration(target);
|
|
87
112
|
}
|
|
88
113
|
return result;
|
|
@@ -98,6 +123,9 @@ function $store(initialValue) {
|
|
|
98
123
|
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
99
124
|
if (boundMethods && boundMethods.has(prop)) {
|
|
100
125
|
boundMethods.delete(prop);
|
|
126
|
+
if (boundMethods.size === 0) {
|
|
127
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
128
|
+
}
|
|
101
129
|
}
|
|
102
130
|
triggerIteration(target);
|
|
103
131
|
}
|
|
@@ -117,5 +145,5 @@ function $store(initialValue) {
|
|
|
117
145
|
}
|
|
118
146
|
|
|
119
147
|
export { $store };
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
121
|
-
//# sourceMappingURL=chunk-
|
|
148
|
+
//# sourceMappingURL=chunk-YSNNU4OE.js.map
|
|
149
|
+
//# sourceMappingURL=chunk-YSNNU4OE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts"],"names":["signals"],"mappings":"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","file":"chunk-YSNNU4OE.js","sourcesContent":["/**\n * @fileoverview Deep reactive store implementation for Fict.\n *\n * $store creates a deeply reactive proxy that tracks property access at the path level.\n * Unlike $state (which is shallow), $store allows direct mutation of nested properties.\n *\n * @example\n * ```typescript\n * const user = $store({ name: 'Alice', address: { city: 'London' } })\n * user.address.city = 'Paris' // Fine-grained reactive update\n * ```\n */\n\nimport { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\n/** Function type for bound methods */\ntype AnyFn = (...args: unknown[]) => unknown\n\n/** Cache entry for bound methods to preserve identity */\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\n/** Type for objects with indexable properties */\ntype IndexableObject = Record<string | symbol, unknown>\n\n/** Cache of proxied objects to avoid duplicate proxies */\nconst PROXY_CACHE = new WeakMap<object, unknown>()\n\n/** Cache of signals per object property */\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\n\n/** Cache of bound methods to preserve function identity across reads */\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\n\n/** Special key for tracking iteration (Object.keys, for-in, etc.) */\nconst ITERATE_KEY = Symbol('iterate')\n\n/**\n * Get or create a signal for a specific property on a target object.\n * @internal\n */\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\n/**\n * Trigger iteration signal to notify consumers that keys have changed.\n * @internal\n */\nfunction triggerIteration(target: object): void {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\n/**\n * Create a deep reactive store using Proxy.\n *\n * Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:\n * - **Deep reactivity**: Nested objects are automatically wrapped in proxies\n * - **Direct mutation**: Modify properties directly without spread operators\n * - **Path-level tracking**: Only components reading changed paths re-render\n *\n * @param initialValue - The initial object to make reactive\n * @returns A reactive proxy of the object\n *\n * @example\n * ```tsx\n * import { $store } from 'fict'\n *\n * const form = $store({\n * user: { name: '', email: '' },\n * settings: { theme: 'light' }\n * })\n *\n * // Direct mutation works\n * form.user.name = 'Alice'\n *\n * // In JSX - only updates when form.user.name changes\n * <input value={form.user.name} />\n * ```\n *\n * @public\n */\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n {\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldLength = Array.isArray(target) && prop === 'length' ? target.length : undefined\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length(target.length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n if (Array.isArray(target) && prop === 'length') {\n const nextLength = target.length\n if (typeof oldLength === 'number' && nextLength < oldLength) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals) {\n for (let i = nextLength; i < oldLength; i += 1) {\n const key = String(i)\n if (signals[key]) {\n signals[key](undefined)\n }\n }\n }\n }\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -56,12 +56,22 @@ function $store(initialValue) {
|
|
|
56
56
|
boundMethods.set(prop, { ref: currentValue, bound });
|
|
57
57
|
return bound;
|
|
58
58
|
}
|
|
59
|
+
{
|
|
60
|
+
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
61
|
+
if (boundMethods && boundMethods.has(prop)) {
|
|
62
|
+
boundMethods.delete(prop);
|
|
63
|
+
if (boundMethods.size === 0) {
|
|
64
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
59
68
|
if (typeof currentValue === "object" && currentValue !== null) {
|
|
60
69
|
return $store(currentValue);
|
|
61
70
|
}
|
|
62
71
|
return currentValue;
|
|
63
72
|
},
|
|
64
73
|
set(target, prop, newValue, receiver) {
|
|
74
|
+
const oldLength = Array.isArray(target) && prop === "length" ? target.length : void 0;
|
|
65
75
|
const oldValue = Reflect.get(target, prop, receiver);
|
|
66
76
|
const hadKey = Object.prototype.hasOwnProperty.call(target, prop);
|
|
67
77
|
if (oldValue === newValue && hadKey) {
|
|
@@ -71,6 +81,9 @@ function $store(initialValue) {
|
|
|
71
81
|
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
72
82
|
if (boundMethods && boundMethods.has(prop)) {
|
|
73
83
|
boundMethods.delete(prop);
|
|
84
|
+
if (boundMethods.size === 0) {
|
|
85
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
88
|
const signals = SIGNAL_CACHE.get(target);
|
|
76
89
|
if (signals && signals[prop]) {
|
|
@@ -86,6 +99,18 @@ function $store(initialValue) {
|
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
if (Array.isArray(target) && prop === "length") {
|
|
102
|
+
const nextLength = target.length;
|
|
103
|
+
if (typeof oldLength === "number" && nextLength < oldLength) {
|
|
104
|
+
const signals2 = SIGNAL_CACHE.get(target);
|
|
105
|
+
if (signals2) {
|
|
106
|
+
for (let i = nextLength; i < oldLength; i += 1) {
|
|
107
|
+
const key = String(i);
|
|
108
|
+
if (signals2[key]) {
|
|
109
|
+
signals2[key](void 0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
89
114
|
triggerIteration(target);
|
|
90
115
|
}
|
|
91
116
|
return result;
|
|
@@ -101,6 +126,9 @@ function $store(initialValue) {
|
|
|
101
126
|
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
102
127
|
if (boundMethods && boundMethods.has(prop)) {
|
|
103
128
|
boundMethods.delete(prop);
|
|
129
|
+
if (boundMethods.size === 0) {
|
|
130
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
131
|
+
}
|
|
104
132
|
}
|
|
105
133
|
triggerIteration(target);
|
|
106
134
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/index.ts"],"names":["createSignal","signals"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview Deep reactive store implementation for Fict.\n *\n * $store creates a deeply reactive proxy that tracks property access at the path level.\n * Unlike $state (which is shallow), $store allows direct mutation of nested properties.\n *\n * @example\n * ```typescript\n * const user = $store({ name: 'Alice', address: { city: 'London' } })\n * user.address.city = 'Paris' // Fine-grained reactive update\n * ```\n */\n\nimport { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\n/** Function type for bound methods */\ntype AnyFn = (...args: unknown[]) => unknown\n\n/** Cache entry for bound methods to preserve identity */\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\n/** Type for objects with indexable properties */\ntype IndexableObject = Record<string | symbol, unknown>\n\n/** Cache of proxied objects to avoid duplicate proxies */\nconst PROXY_CACHE = new WeakMap<object, unknown>()\n\n/** Cache of signals per object property */\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\n\n/** Cache of bound methods to preserve function identity across reads */\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\n\n/** Special key for tracking iteration (Object.keys, for-in, etc.) */\nconst ITERATE_KEY = Symbol('iterate')\n\n/**\n * Get or create a signal for a specific property on a target object.\n * @internal\n */\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\n/**\n * Trigger iteration signal to notify consumers that keys have changed.\n * @internal\n */\nfunction triggerIteration(target: object): void {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\n/**\n * Create a deep reactive store using Proxy.\n *\n * Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:\n * - **Deep reactivity**: Nested objects are automatically wrapped in proxies\n * - **Direct mutation**: Modify properties directly without spread operators\n * - **Path-level tracking**: Only components reading changed paths re-render\n *\n * @param initialValue - The initial object to make reactive\n * @returns A reactive proxy of the object\n *\n * @example\n * ```tsx\n * import { $store } from 'fict'\n *\n * const form = $store({\n * user: { name: '', email: '' },\n * settings: { theme: 'light' }\n * })\n *\n * // Direct mutation works\n * form.user.name = 'Alice'\n *\n * // In JSX - only updates when form.user.name changes\n * <input value={form.user.name} />\n * ```\n *\n * @public\n */\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length(target.length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n // But usually 'length' is set explicitly or handled by the runtime.\n if (Array.isArray(target) && prop === 'length') {\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n","/**\n * @fileoverview Fict Framework - Complete API\n *\n * This is the main entry point for the Fict framework.\n *\n * ## Recommended Import Pattern (v1.0+)\n *\n * ```typescript\n * // Core public API (most users need only this)\n * // Use $state in components (compiler-transformed)\n * // Use $store for cross-component shared state\n * import { $store, render } from 'fict'\n *\n * // Async utilities\n * import { resource, lazy } from 'fict/plus'\n *\n * // Advanced APIs (escape hatches, library authors)\n * import { createSignal, createContext, createScope } from 'fict/advanced'\n * ```\n *\n * ## State Management Guide\n *\n * | Use Case | API |\n * |----------|-----|\n * | Component-local state | `$state` (compiler-transformed) |\n * | Derived values / side effects | JS + auto-derived + `$effect` |\n * | Cross-component (large objects, deep mutation) | `$store` |\n * | Cross-component (scalar/lightweight, library-level) | `createSignal` (advanced) |\n * | Cross-component (subtree scope, SSR isolation) | `Context` (advanced) |\n *\n * @public\n * @packageDocumentation\n */\n\n// Re-export everything from runtime\nexport * from '@fictjs/runtime'\n\n// Re-export commonly used advanced APIs for convenience\nexport { createSelector, createScope, runInScope } from '@fictjs/runtime/advanced'\n\n// ============================================================================\n// Convenience Aliases\n// ============================================================================\n\n/**\n * Alias for createMemo.\n * Creates a memoized value that only recomputes when dependencies change.\n *\n * @example\n * ```tsx\n * const fullName = $memo(() => firstName + ' ' + lastName)\n * ```\n *\n * @public\n */\nexport { createMemo as $memo } from '@fictjs/runtime'\n\n// ============================================================================\n// Deep Reactive Store (Proxy-based)\n// ============================================================================\n\n/**\n * Create a deep reactive store using Proxy.\n * Unlike createStore, $store allows direct mutation.\n *\n * @example\n * ```tsx\n * const user = $store({ name: 'Alice', address: { city: 'Beijing' } })\n * user.name = 'Bob' // Reactive update\n * user.address.city = 'Shanghai' // Deep reactive\n * ```\n *\n * @public\n */\nexport { $store } from './store'\n\n// ============================================================================\n// Compiler Macros (transformed at compile time)\n// ============================================================================\n\n/**\n * Compiler macro for reactive state.\n * This is transformed at compile time and should never be called at runtime.\n *\n * @example\n * ```tsx\n * let count = $state(0)\n * count++ // Reactive update\n * ```\n *\n * @public\n */\nexport function $state<T>(_initialValue: T): T {\n // This function is never called at runtime - the compiler transforms it\n throw new Error('$state() is a compiler macro and should be transformed at compile time')\n}\n\n/**\n * Compiler macro for reactive effects.\n * This is transformed at compile time and should never be called at runtime.\n *\n * @example\n * ```tsx\n * $effect(() => {\n * console.log('count changed:', count)\n * })\n * ```\n *\n * @public\n */\nexport function $effect(_fn: () => void | (() => void)): void {\n // This function is never called at runtime - the compiler transforms it\n throw new Error('$effect() is a compiler macro and should be transformed at compile time')\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/index.ts"],"names":["createSignal","signals"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview Deep reactive store implementation for Fict.\n *\n * $store creates a deeply reactive proxy that tracks property access at the path level.\n * Unlike $state (which is shallow), $store allows direct mutation of nested properties.\n *\n * @example\n * ```typescript\n * const user = $store({ name: 'Alice', address: { city: 'London' } })\n * user.address.city = 'Paris' // Fine-grained reactive update\n * ```\n */\n\nimport { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\n/** Function type for bound methods */\ntype AnyFn = (...args: unknown[]) => unknown\n\n/** Cache entry for bound methods to preserve identity */\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\n/** Type for objects with indexable properties */\ntype IndexableObject = Record<string | symbol, unknown>\n\n/** Cache of proxied objects to avoid duplicate proxies */\nconst PROXY_CACHE = new WeakMap<object, unknown>()\n\n/** Cache of signals per object property */\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\n\n/** Cache of bound methods to preserve function identity across reads */\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\n\n/** Special key for tracking iteration (Object.keys, for-in, etc.) */\nconst ITERATE_KEY = Symbol('iterate')\n\n/**\n * Get or create a signal for a specific property on a target object.\n * @internal\n */\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\n/**\n * Trigger iteration signal to notify consumers that keys have changed.\n * @internal\n */\nfunction triggerIteration(target: object): void {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\n/**\n * Create a deep reactive store using Proxy.\n *\n * Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:\n * - **Deep reactivity**: Nested objects are automatically wrapped in proxies\n * - **Direct mutation**: Modify properties directly without spread operators\n * - **Path-level tracking**: Only components reading changed paths re-render\n *\n * @param initialValue - The initial object to make reactive\n * @returns A reactive proxy of the object\n *\n * @example\n * ```tsx\n * import { $store } from 'fict'\n *\n * const form = $store({\n * user: { name: '', email: '' },\n * settings: { theme: 'light' }\n * })\n *\n * // Direct mutation works\n * form.user.name = 'Alice'\n *\n * // In JSX - only updates when form.user.name changes\n * <input value={form.user.name} />\n * ```\n *\n * @public\n */\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n {\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldLength = Array.isArray(target) && prop === 'length' ? target.length : undefined\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length(target.length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n if (Array.isArray(target) && prop === 'length') {\n const nextLength = target.length\n if (typeof oldLength === 'number' && nextLength < oldLength) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals) {\n for (let i = nextLength; i < oldLength; i += 1) {\n const key = String(i)\n if (signals[key]) {\n signals[key](undefined)\n }\n }\n }\n }\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n","/**\n * @fileoverview Fict Framework - Complete API\n *\n * This is the main entry point for the Fict framework.\n *\n * ## Recommended Import Pattern (v1.0+)\n *\n * ```typescript\n * // Core public API (most users need only this)\n * // Use $state in components (compiler-transformed)\n * // Use $store for cross-component shared state\n * import { $store, render } from 'fict'\n *\n * // Async utilities\n * import { resource, lazy } from 'fict/plus'\n *\n * // Advanced APIs (escape hatches, library authors)\n * import { createSignal, createContext, createScope } from 'fict/advanced'\n * ```\n *\n * ## State Management Guide\n *\n * | Use Case | API |\n * |----------|-----|\n * | Component-local state | `$state` (compiler-transformed) |\n * | Derived values / side effects | JS + auto-derived + `$effect` |\n * | Cross-component (large objects, deep mutation) | `$store` |\n * | Cross-component (scalar/lightweight, library-level) | `createSignal` (advanced) |\n * | Cross-component (subtree scope, SSR isolation) | `Context` (advanced) |\n *\n * @public\n * @packageDocumentation\n */\n\n// Re-export everything from runtime\nexport * from '@fictjs/runtime'\n\n// Re-export commonly used advanced APIs for convenience\nexport { createSelector, createScope, runInScope } from '@fictjs/runtime/advanced'\n\n// ============================================================================\n// Convenience Aliases\n// ============================================================================\n\n/**\n * Alias for createMemo.\n * Creates a memoized value that only recomputes when dependencies change.\n *\n * @example\n * ```tsx\n * const fullName = $memo(() => firstName + ' ' + lastName)\n * ```\n *\n * @public\n */\nexport { createMemo as $memo } from '@fictjs/runtime'\n\n// ============================================================================\n// Deep Reactive Store (Proxy-based)\n// ============================================================================\n\n/**\n * Create a deep reactive store using Proxy.\n * Unlike createStore, $store allows direct mutation.\n *\n * @example\n * ```tsx\n * const user = $store({ name: 'Alice', address: { city: 'Beijing' } })\n * user.name = 'Bob' // Reactive update\n * user.address.city = 'Shanghai' // Deep reactive\n * ```\n *\n * @public\n */\nexport { $store } from './store'\n\n// ============================================================================\n// Compiler Macros (transformed at compile time)\n// ============================================================================\n\n/**\n * Compiler macro for reactive state.\n * This is transformed at compile time and should never be called at runtime.\n *\n * @example\n * ```tsx\n * let count = $state(0)\n * count++ // Reactive update\n * ```\n *\n * @public\n */\nexport function $state<T>(_initialValue: T): T {\n // This function is never called at runtime - the compiler transforms it\n throw new Error('$state() is a compiler macro and should be transformed at compile time')\n}\n\n/**\n * Compiler macro for reactive effects.\n * This is transformed at compile time and should never be called at runtime.\n *\n * @example\n * ```tsx\n * $effect(() => {\n * console.log('count changed:', count)\n * })\n * ```\n *\n * @public\n */\nexport function $effect(_fn: () => void | (() => void)): void {\n // This function is never called at runtime - the compiler transforms it\n throw new Error('$effect() is a compiler macro and should be transformed at compile time')\n}\n"]}
|
package/dist/index.js
CHANGED
package/dist/plus.cjs
CHANGED
|
@@ -56,12 +56,22 @@ function $store(initialValue) {
|
|
|
56
56
|
boundMethods.set(prop, { ref: currentValue, bound });
|
|
57
57
|
return bound;
|
|
58
58
|
}
|
|
59
|
+
{
|
|
60
|
+
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
61
|
+
if (boundMethods && boundMethods.has(prop)) {
|
|
62
|
+
boundMethods.delete(prop);
|
|
63
|
+
if (boundMethods.size === 0) {
|
|
64
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
59
68
|
if (typeof currentValue === "object" && currentValue !== null) {
|
|
60
69
|
return $store(currentValue);
|
|
61
70
|
}
|
|
62
71
|
return currentValue;
|
|
63
72
|
},
|
|
64
73
|
set(target, prop, newValue, receiver) {
|
|
74
|
+
const oldLength = Array.isArray(target) && prop === "length" ? target.length : void 0;
|
|
65
75
|
const oldValue = Reflect.get(target, prop, receiver);
|
|
66
76
|
const hadKey = Object.prototype.hasOwnProperty.call(target, prop);
|
|
67
77
|
if (oldValue === newValue && hadKey) {
|
|
@@ -71,6 +81,9 @@ function $store(initialValue) {
|
|
|
71
81
|
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
72
82
|
if (boundMethods && boundMethods.has(prop)) {
|
|
73
83
|
boundMethods.delete(prop);
|
|
84
|
+
if (boundMethods.size === 0) {
|
|
85
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
88
|
const signals = SIGNAL_CACHE.get(target);
|
|
76
89
|
if (signals && signals[prop]) {
|
|
@@ -86,6 +99,18 @@ function $store(initialValue) {
|
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
if (Array.isArray(target) && prop === "length") {
|
|
102
|
+
const nextLength = target.length;
|
|
103
|
+
if (typeof oldLength === "number" && nextLength < oldLength) {
|
|
104
|
+
const signals2 = SIGNAL_CACHE.get(target);
|
|
105
|
+
if (signals2) {
|
|
106
|
+
for (let i = nextLength; i < oldLength; i += 1) {
|
|
107
|
+
const key = String(i);
|
|
108
|
+
if (signals2[key]) {
|
|
109
|
+
signals2[key](void 0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
89
114
|
triggerIteration(target);
|
|
90
115
|
}
|
|
91
116
|
return result;
|
|
@@ -101,6 +126,9 @@ function $store(initialValue) {
|
|
|
101
126
|
const boundMethods = BOUND_METHOD_CACHE.get(target);
|
|
102
127
|
if (boundMethods && boundMethods.has(prop)) {
|
|
103
128
|
boundMethods.delete(prop);
|
|
129
|
+
if (boundMethods.size === 0) {
|
|
130
|
+
BOUND_METHOD_CACHE.delete(target);
|
|
131
|
+
}
|
|
104
132
|
}
|
|
105
133
|
triggerIteration(target);
|
|
106
134
|
}
|
|
@@ -164,6 +192,7 @@ function resource(optionsOrFetcher) {
|
|
|
164
192
|
generation: 0,
|
|
165
193
|
expiresAt: void 0,
|
|
166
194
|
inFlight: void 0,
|
|
195
|
+
inFlightArgs: void 0,
|
|
167
196
|
controller: void 0
|
|
168
197
|
};
|
|
169
198
|
cache.set(key, state);
|
|
@@ -184,6 +213,9 @@ function resource(optionsOrFetcher) {
|
|
|
184
213
|
entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs) ? Date.now() + resolvedCacheOptions.ttlMs : void 0;
|
|
185
214
|
};
|
|
186
215
|
const startFetch = (entry, key, args, isRevalidating = false) => {
|
|
216
|
+
if (entry.inFlight && entry.inFlightArgs === args) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
187
219
|
entry.controller?.abort();
|
|
188
220
|
entry.inFlight = void 0;
|
|
189
221
|
const controller = new AbortController();
|
|
@@ -225,9 +257,11 @@ function resource(optionsOrFetcher) {
|
|
|
225
257
|
}
|
|
226
258
|
}).finally(() => {
|
|
227
259
|
entry.inFlight = void 0;
|
|
260
|
+
entry.inFlightArgs = void 0;
|
|
228
261
|
entry.controller = void 0;
|
|
229
262
|
});
|
|
230
263
|
entry.inFlight = fetchPromise;
|
|
264
|
+
entry.inFlightArgs = args;
|
|
231
265
|
runtime.onCleanup(() => {
|
|
232
266
|
if (resolvedCacheOptions.mode === "none") {
|
|
233
267
|
controller.abort();
|
|
@@ -263,6 +297,32 @@ function resource(optionsOrFetcher) {
|
|
|
263
297
|
startFetch(entry, key, args);
|
|
264
298
|
}
|
|
265
299
|
};
|
|
300
|
+
const mutate = (argsAccessor, value, options) => {
|
|
301
|
+
const args = readArgs(argsAccessor);
|
|
302
|
+
const key = options?.key ?? computeKey(args);
|
|
303
|
+
const entry = ensureEntry(key);
|
|
304
|
+
const prevValue = entry.data();
|
|
305
|
+
const nextValue = typeof value === "function" ? value(prevValue) : value;
|
|
306
|
+
entry.controller?.abort();
|
|
307
|
+
entry.inFlight = void 0;
|
|
308
|
+
entry.inFlightArgs = void 0;
|
|
309
|
+
entry.generation += 1;
|
|
310
|
+
entry.data(nextValue);
|
|
311
|
+
entry.hasValue = true;
|
|
312
|
+
entry.status = "success";
|
|
313
|
+
entry.loading(false);
|
|
314
|
+
entry.error(void 0);
|
|
315
|
+
markExpiry(entry);
|
|
316
|
+
entry.lastArgs = args;
|
|
317
|
+
entry.lastVersion = entry.version();
|
|
318
|
+
if (entry.pendingToken) {
|
|
319
|
+
entry.pendingToken.resolve();
|
|
320
|
+
entry.pendingToken = null;
|
|
321
|
+
}
|
|
322
|
+
if (options?.revalidate) {
|
|
323
|
+
entry.version(entry.version() + 1);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
266
326
|
return {
|
|
267
327
|
read(argsAccessor) {
|
|
268
328
|
const entryRef = advanced.createSignal(null);
|
|
@@ -320,7 +380,8 @@ function resource(optionsOrFetcher) {
|
|
|
320
380
|
};
|
|
321
381
|
},
|
|
322
382
|
invalidate,
|
|
323
|
-
prefetch
|
|
383
|
+
prefetch,
|
|
384
|
+
mutate
|
|
324
385
|
};
|
|
325
386
|
}
|
|
326
387
|
function lazy(loader, options = {}) {
|
package/dist/plus.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/resource.ts","../src/lazy.ts"],"names":["createSignal","signals","createSuspenseToken","onCleanup","entry","createEffect"],"mappings":"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","file":"plus.cjs","sourcesContent":["/**\n * @fileoverview Deep reactive store implementation for Fict.\n *\n * $store creates a deeply reactive proxy that tracks property access at the path level.\n * Unlike $state (which is shallow), $store allows direct mutation of nested properties.\n *\n * @example\n * ```typescript\n * const user = $store({ name: 'Alice', address: { city: 'London' } })\n * user.address.city = 'Paris' // Fine-grained reactive update\n * ```\n */\n\nimport { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\n/** Function type for bound methods */\ntype AnyFn = (...args: unknown[]) => unknown\n\n/** Cache entry for bound methods to preserve identity */\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\n/** Type for objects with indexable properties */\ntype IndexableObject = Record<string | symbol, unknown>\n\n/** Cache of proxied objects to avoid duplicate proxies */\nconst PROXY_CACHE = new WeakMap<object, unknown>()\n\n/** Cache of signals per object property */\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\n\n/** Cache of bound methods to preserve function identity across reads */\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\n\n/** Special key for tracking iteration (Object.keys, for-in, etc.) */\nconst ITERATE_KEY = Symbol('iterate')\n\n/**\n * Get or create a signal for a specific property on a target object.\n * @internal\n */\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\n/**\n * Trigger iteration signal to notify consumers that keys have changed.\n * @internal\n */\nfunction triggerIteration(target: object): void {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\n/**\n * Create a deep reactive store using Proxy.\n *\n * Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:\n * - **Deep reactivity**: Nested objects are automatically wrapped in proxies\n * - **Direct mutation**: Modify properties directly without spread operators\n * - **Path-level tracking**: Only components reading changed paths re-render\n *\n * @param initialValue - The initial object to make reactive\n * @returns A reactive proxy of the object\n *\n * @example\n * ```tsx\n * import { $store } from 'fict'\n *\n * const form = $store({\n * user: { name: '', email: '' },\n * settings: { theme: 'light' }\n * })\n *\n * // Direct mutation works\n * form.user.name = 'Alice'\n *\n * // In JSX - only updates when form.user.name changes\n * <input value={form.user.name} />\n * ```\n *\n * @public\n */\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length(target.length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n // But usually 'length' is set explicitly or handled by the runtime.\n if (Array.isArray(target) && prop === 'length') {\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n","/**\n * @fileoverview Async data fetching with caching and Suspense support.\n *\n * The `resource` function creates a reactive data fetcher that:\n * - Automatically cancels in-flight requests when args change\n * - Supports Suspense for loading states\n * - Provides caching with TTL and stale-while-revalidate\n * - Handles errors gracefully\n */\n\nimport { createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'\nimport { createSignal } from '@fictjs/runtime/advanced'\n\n/**\n * The result of reading a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n */\nexport interface ResourceResult<T> {\n /** The fetched data, or undefined if not yet loaded or on error */\n readonly data: T | undefined\n /** Whether the resource is currently loading (initial fetch or refetch) */\n readonly loading: boolean\n /**\n * Any error that occurred during fetching.\n * Type is unknown since errors can be any value in JavaScript.\n */\n readonly error: unknown\n /** Manually trigger a refetch of the resource */\n refresh: () => void\n}\n\n/**\n * Cache configuration options for a resource.\n */\nexport interface ResourceCacheOptions {\n /**\n * Caching mode:\n * - `'memory'`: Cache responses in memory (default)\n * - `'none'`: No caching, always refetch\n * @default 'memory'\n */\n mode?: 'memory' | 'none'\n\n /**\n * Time-to-live in milliseconds before cached data is considered stale.\n * @default Infinity\n */\n ttlMs?: number\n\n /**\n * If true, return stale cached data immediately while refetching in background.\n * @default false\n */\n staleWhileRevalidate?: boolean\n\n /**\n * If true, cache error responses as well.\n * @default false\n */\n cacheErrors?: boolean\n}\n\n/**\n * Configuration options for creating a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface ResourceOptions<T, Args> {\n /**\n * Custom cache key. Can be a static value or a function that computes\n * the key from the args. If not provided, args are used as the key.\n */\n key?: unknown | ((args: Args) => unknown)\n\n /**\n * The fetcher function that performs the async data retrieval.\n * Receives an AbortController signal for cancellation support.\n */\n fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>\n\n /**\n * If true, the resource will throw a Suspense token while loading,\n * enabling React-like Suspense boundaries.\n * @default false\n */\n suspense?: boolean\n\n /**\n * Cache configuration options.\n */\n cache?: ResourceCacheOptions\n\n /**\n * A value or reactive getter that, when changed, resets the resource.\n * Useful for clearing cache when certain conditions change.\n */\n reset?: unknown | (() => unknown)\n}\n\n/**\n * Return type of the resource factory.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface Resource<T, Args> {\n /**\n * Read the resource data, triggering a fetch if needed.\n * Can accept static args or a reactive getter.\n *\n * @param argsAccessor - Arguments or a getter returning arguments\n */\n read(argsAccessor: (() => Args) | Args): ResourceResult<T>\n\n /**\n * Invalidate cached data, causing the next read to refetch.\n *\n * @param key - Optional specific key to invalidate. If omitted, invalidates all.\n */\n invalidate(key?: unknown): void\n\n /**\n * Prefetch data without reading it. Useful for eager loading.\n *\n * @param args - Arguments to pass to the fetcher\n * @param keyOverride - Optional cache key override\n */\n prefetch(args: Args, keyOverride?: unknown): void\n}\n\n/**\n * Resource status values for tracking fetch lifecycle.\n * @internal\n */\nexport type ResourceStatus = 'idle' | 'pending' | 'success' | 'error'\n\n/**\n * Internal cache entry for a resource.\n * Tracks the reactive state and metadata for a single cached fetch.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n * @internal\n */\ninterface ResourceEntry<T, Args> {\n /** Reactive signal for the fetched data */\n data: ReturnType<typeof createSignal<T | undefined>>\n /** Reactive signal for loading state */\n loading: ReturnType<typeof createSignal<boolean>>\n /** Reactive signal for error state */\n error: ReturnType<typeof createSignal<unknown>>\n /** Version counter for invalidation */\n version: ReturnType<typeof createSignal<number>>\n /** Suspense token when using suspense mode */\n pendingToken: ReturnType<typeof createSuspenseToken> | null\n /** Last used arguments for change detection */\n lastArgs: Args | undefined\n /** Last seen version for change detection */\n lastVersion: number\n /** Last reset token value for change detection */\n lastReset: unknown\n /** Whether we have a valid cached value */\n hasValue: boolean\n /** Current fetch status */\n status: ResourceStatus\n /** Generation counter to handle race conditions */\n generation: number\n /** Timestamp when the cached value expires */\n expiresAt: number | undefined\n /** Currently in-flight fetch promise */\n inFlight: Promise<void> | undefined\n /** AbortController for cancelling in-flight requests */\n controller: AbortController | undefined\n}\n\nconst defaultCacheOptions: Required<ResourceCacheOptions> = {\n mode: 'memory',\n ttlMs: Number.POSITIVE_INFINITY,\n staleWhileRevalidate: false,\n cacheErrors: false,\n}\n\n/**\n * Create a reactive async data resource.\n *\n * Resources handle async data fetching with automatic caching, cancellation,\n * and optional Suspense integration.\n *\n * @param optionsOrFetcher - A fetcher function or full configuration object\n * @returns A resource factory with read, invalidate, and prefetch methods\n *\n * @example\n * ```tsx\n * import { resource } from 'fict'\n *\n * // Simple fetcher\n * const userResource = resource(\n * ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}`, { signal }).then(r => r.json())\n * )\n *\n * // With full options\n * const postsResource = resource({\n * fetch: ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}/posts`, { signal }).then(r => r.json()),\n * suspense: true,\n * cache: {\n * ttlMs: 60_000,\n * staleWhileRevalidate: true,\n * },\n * })\n *\n * // Usage in component\n * function UserProfile({ userId }: { userId: string }) {\n * const { data, loading, error, refresh } = userResource.read(() => userId)\n *\n * if (loading) return <Spinner />\n * if (error) return <ErrorMessage error={error} />\n * return <div>{data.name}</div>\n * }\n * ```\n *\n * @public\n */\nexport function resource<T, Args = void>(\n optionsOrFetcher:\n | ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)\n | ResourceOptions<T, Args>,\n): Resource<T, Args> {\n const fetcher = typeof optionsOrFetcher === 'function' ? optionsOrFetcher : optionsOrFetcher.fetch\n const useSuspense = typeof optionsOrFetcher === 'object' && !!optionsOrFetcher.suspense\n const cacheOptions: ResourceCacheOptions =\n typeof optionsOrFetcher === 'object' ? (optionsOrFetcher.cache ?? {}) : {}\n const resolvedCacheOptions = { ...defaultCacheOptions, ...cacheOptions }\n const cache = new Map<unknown, ResourceEntry<T, Args>>()\n\n const readArgs = (argsAccessor: (() => Args) | Args): Args =>\n typeof argsAccessor === 'function' ? (argsAccessor as () => Args)() : argsAccessor\n\n const computeKey = (argsAccessor: (() => Args) | Args): unknown => {\n const argsValue = readArgs(argsAccessor)\n if (typeof optionsOrFetcher === 'object' && optionsOrFetcher.key !== undefined) {\n const key = optionsOrFetcher.key\n return typeof key === 'function' ? (key as (args: Args) => unknown)(argsValue) : key\n }\n return argsValue\n }\n\n const readResetToken = (): unknown => {\n if (typeof optionsOrFetcher !== 'object') return undefined\n const reset = optionsOrFetcher.reset\n if (typeof reset === 'function' && (reset as () => unknown).length === 0) {\n return (reset as () => unknown)()\n }\n return reset\n }\n\n const ensureEntry = (key: unknown): ResourceEntry<T, Args> => {\n let state = cache.get(key)\n if (!state) {\n state = {\n data: createSignal<T | undefined>(undefined),\n loading: createSignal<boolean>(false),\n error: createSignal<unknown>(undefined),\n version: createSignal(0),\n pendingToken: null,\n lastArgs: undefined,\n lastVersion: -1,\n lastReset: undefined,\n hasValue: false,\n status: 'idle',\n generation: 0,\n expiresAt: undefined,\n inFlight: undefined,\n controller: undefined,\n }\n cache.set(key, state)\n }\n return state!\n }\n\n const isExpired = (entry: ResourceEntry<T, Args>): boolean => {\n if (resolvedCacheOptions.mode === 'none') return true\n if (!Number.isFinite(resolvedCacheOptions.ttlMs)) return false\n if (entry.expiresAt === undefined) return false\n return entry.expiresAt < Date.now()\n }\n\n const markExpiry = (entry: ResourceEntry<T, Args>) => {\n if (resolvedCacheOptions.mode === 'none') {\n entry.expiresAt = Date.now() - 1\n return\n }\n entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs)\n ? Date.now() + resolvedCacheOptions.ttlMs\n : undefined\n }\n\n const startFetch = (\n entry: ResourceEntry<T, Args>,\n key: unknown,\n args: Args,\n isRevalidating = false,\n ) => {\n entry.controller?.abort()\n entry.inFlight = undefined\n const controller = new AbortController()\n entry.controller = controller\n entry.status = 'pending'\n // For stale-while-revalidate: don't show loading if we already have data to display\n if (!isRevalidating) {\n entry.loading(true)\n }\n entry.error(undefined)\n entry.generation += 1\n const currentGen = entry.generation\n\n const shouldSuspend = useSuspense && !entry.hasValue\n entry.pendingToken = shouldSuspend ? createSuspenseToken() : null\n\n const fetchPromise = fetcher({ signal: controller.signal }, args)\n .then(res => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.data(res)\n entry.hasValue = true\n entry.status = 'success'\n entry.loading(false)\n markExpiry(entry)\n if (entry.pendingToken) {\n entry.pendingToken.resolve()\n entry.pendingToken = null\n }\n })\n .catch(err => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.error(err)\n entry.status = 'error'\n entry.loading(false)\n if (resolvedCacheOptions.cacheErrors) {\n markExpiry(entry)\n } else {\n entry.expiresAt = Date.now() - 1\n entry.hasValue = false\n }\n if (entry.pendingToken) {\n entry.pendingToken.reject(err)\n entry.pendingToken = null\n }\n })\n .finally(() => {\n entry.inFlight = undefined\n entry.controller = undefined\n })\n\n entry.inFlight = fetchPromise\n\n onCleanup(() => {\n if (resolvedCacheOptions.mode === 'none') {\n controller.abort()\n cache.delete(key)\n }\n })\n }\n\n const invalidate = (key?: unknown) => {\n if (key === undefined) {\n cache.forEach(entry => {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n })\n cache.clear()\n return\n }\n const entry = cache.get(key)\n if (entry) {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n cache.delete(key)\n }\n }\n\n const prefetch = (args: Args, keyOverride?: unknown) => {\n const key = keyOverride ?? computeKey(args)\n const entry = ensureEntry(key)\n const usableData = entry.hasValue && !isExpired(entry)\n if (!usableData) {\n entry.lastArgs = args\n entry.lastVersion = entry.version()\n startFetch(entry, key, args)\n }\n }\n\n return {\n read(argsAccessor: (() => Args) | Args): ResourceResult<T> {\n const entryRef = createSignal<ResourceEntry<T, Args> | null>(null)\n\n createEffect(() => {\n const key = computeKey(argsAccessor)\n const entry = ensureEntry(key)\n entryRef(entry)\n const args = readArgs(argsAccessor)\n const currentVersion = entry.version()\n const expired = isExpired(entry)\n const argsChanged = entry.lastArgs !== args\n const versionChanged = entry.lastVersion !== currentVersion\n const resetToken = readResetToken()\n const resetChanged = entry.lastReset !== resetToken\n // For stale-while-revalidate: if we have cached data, don't treat expired as requiring immediate refetch\n // We'll handle the revalidation separately to show stale data without loading state\n const canUseStaleData =\n resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired\n const shouldRefetch =\n (expired && !canUseStaleData) ||\n argsChanged ||\n versionChanged ||\n resetChanged ||\n (entry.status === 'error' && !resolvedCacheOptions.cacheErrors)\n\n entry.lastArgs = args\n entry.lastVersion = currentVersion\n entry.lastReset = resetToken\n\n if (shouldRefetch) {\n if (entry.inFlight && (argsChanged || versionChanged)) {\n entry.controller?.abort()\n entry.inFlight = undefined\n }\n if (resetChanged) {\n entry.hasValue = false\n entry.expiresAt = Date.now() - 1\n }\n startFetch(entry, key, args as Args)\n } else if (canUseStaleData && entry.inFlight === undefined) {\n // stale-while-revalidate: return stale data immediately, refresh in background\n // Pass isRevalidating=true to avoid showing loading state\n startFetch(entry, key, args as Args, true)\n }\n })\n\n return {\n get data() {\n const entry = entryRef()\n if (!entry) return undefined\n if (useSuspense && entry.pendingToken) {\n throw entry.pendingToken.token\n }\n return entry.data()\n },\n get loading() {\n const entry = entryRef()\n return entry ? entry.loading() : false\n },\n get error() {\n const entry = entryRef()\n return entry ? entry.error() : undefined\n },\n refresh: () => {\n const entry = entryRef()\n if (entry) entry.version(entry.version() + 1)\n },\n }\n },\n invalidate,\n prefetch,\n }\n}\n","/**\n * @fileoverview Lazy component loading with Suspense support.\n *\n * Creates a component that loads its implementation asynchronously,\n * suspending rendering until the module is loaded.\n */\n\nimport { createSuspenseToken } from '@fictjs/runtime'\nimport type { Component } from '@fictjs/runtime'\n\n/** Module shape expected from dynamic imports */\nexport interface LazyModule<TProps extends Record<string, unknown>> {\n default: Component<TProps>\n}\n\n/** Options for lazy loading behavior */\nexport interface LazyOptions {\n /**\n * Maximum number of retry attempts on load failure.\n * Set to 0 to disable retries (default behavior).\n * @default 0\n */\n maxRetries?: number\n\n /**\n * Delay in milliseconds between retry attempts.\n * Uses exponential backoff: delay * 2^(attempt - 1)\n * @default 1000\n */\n retryDelay?: number\n}\n\n/** Extended component with retry capability */\nexport interface LazyComponent<TProps extends Record<string, unknown>> extends Component<TProps> {\n /**\n * Reset the lazy component state, allowing it to retry loading.\n * Useful when used with ErrorBoundary reset functionality.\n */\n reset: () => void\n\n /**\n * Preload the component without rendering it.\n * Returns a promise that resolves when the component is loaded.\n */\n preload: () => Promise<void>\n}\n\n/**\n * Create a lazy component that suspends while loading.\n *\n * @param loader - Function that returns a promise resolving to the component module\n * @param options - Optional configuration for retry behavior\n * @returns A component that suspends during loading and supports retry on failure\n *\n * @example\n * ```tsx\n * import { lazy, Suspense } from 'fict'\n *\n * // Basic usage\n * const LazyChart = lazy(() => import('./Chart'))\n *\n * // With retry options\n * const LazyDashboard = lazy(() => import('./Dashboard'), {\n * maxRetries: 3,\n * retryDelay: 1000\n * })\n *\n * function App() {\n * return (\n * <Suspense fallback={<Loading />}>\n * <LazyChart />\n * </Suspense>\n * )\n * }\n *\n * // Reset on error (with ErrorBoundary)\n * <ErrorBoundary fallback={(err, reset) => (\n * <button onClick={() => { LazyChart.reset(); reset(); }}>Retry</button>\n * )}>\n * <LazyChart />\n * </ErrorBoundary>\n * ```\n *\n * @public\n */\nexport function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(\n loader: () => Promise<LazyModule<TProps> | { default: Component<TProps> }>,\n options: LazyOptions = {},\n): LazyComponent<TProps> {\n const { maxRetries = 0, retryDelay = 1000 } = options\n\n let loaded: Component<TProps> | null = null\n let loadError: unknown = null\n let loadingPromise: Promise<unknown> | null = null\n let pendingToken: ReturnType<typeof createSuspenseToken> | null = null\n let retryCount = 0\n\n const attemptLoad = (): Promise<void> => {\n return loader()\n .then(mod => {\n loaded = (mod as LazyModule<TProps>).default\n loadError = null\n retryCount = 0\n pendingToken?.resolve()\n })\n .catch((err: unknown) => {\n if (retryCount < maxRetries) {\n retryCount++\n const delay = retryDelay * Math.pow(2, retryCount - 1)\n return new Promise<void>(resolve => {\n setTimeout(() => {\n resolve(attemptLoad())\n }, delay)\n })\n }\n loadError = err\n pendingToken?.reject(err)\n return undefined\n })\n .finally(() => {\n loadingPromise = null\n pendingToken = null\n })\n }\n\n const component = ((props: TProps) => {\n if (loaded) {\n return loaded(props)\n }\n if (loadError) {\n throw loadError\n }\n if (!loadingPromise) {\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n }\n if (pendingToken) {\n throw pendingToken.token\n }\n // Should never hit if pendingToken exists, but fallback for type safety.\n throw new Error('Lazy component failed to start loading')\n }) as LazyComponent<TProps>\n\n /**\n * Reset the lazy component state, clearing any cached error.\n * Call this before triggering a re-render to retry loading.\n */\n component.reset = () => {\n loadError = null\n loadingPromise = null\n pendingToken = null\n retryCount = 0\n // Note: we don't clear `loaded` - if it was successfully loaded, keep it\n }\n\n /**\n * Preload the component without rendering.\n * Useful for eager loading on route prefetch.\n */\n component.preload = (): Promise<void> => {\n if (loaded) {\n return Promise.resolve()\n }\n if (loadingPromise) {\n return loadingPromise as Promise<void>\n }\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n return loadingPromise as Promise<void>\n }\n\n return component\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/resource.ts","../src/lazy.ts"],"names":["createSignal","signals","createSuspenseToken","onCleanup","entry","createEffect"],"mappings":";;;;;;AA4BA,IAAM,WAAA,uBAAkB,OAAA,EAAyB;AAGjD,IAAM,YAAA,uBAAmB,OAAA,EAA0D;AAGnF,IAAM,kBAAA,uBAAyB,OAAA,EAAwD;AAGvF,IAAM,WAAA,GAAc,OAAO,SAAS,CAAA;AAMpC,SAAS,SAAA,CAAU,QAAgB,IAAA,EAAwC;AACzE,EAAA,IAAI,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACrC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAA,GAAU,EAAC;AACX,IAAA,YAAA,CAAa,GAAA,CAAI,QAAQ,OAAO,CAAA;AAAA,EAClC;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,IAAI,CAAA,EAAG;AAClB,IAAA,MAAM,OAAA,GAAU,IAAA,KAAS,WAAA,GAAc,CAAA,GAAK,OAA2B,IAAI,CAAA;AAC3E,IAAA,OAAA,CAAQ,IAAI,CAAA,GAAIA,qBAAA,CAAa,OAAO,CAAA;AAAA,EACtC;AACA,EAAA,OAAO,QAAQ,IAAI,CAAA;AACrB;AAMA,SAAS,iBAAiB,MAAA,EAAsB;AAC9C,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACvC,EAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,WAAW,CAAA,EAAG;AACnC,IAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,WAAW,CAAA,EAAE;AACrC,IAAA,OAAA,CAAQ,WAAW,CAAA,CAAE,OAAA,GAAU,CAAC,CAAA;AAAA,EAClC;AACF;AA+BO,SAAS,OAAyB,YAAA,EAAoB;AAC3D,EAAA,IAAI,OAAO,YAAA,KAAiB,QAAA,IAAY,YAAA,KAAiB,IAAA,EAAM;AAC7D,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,IAAI,WAAA,CAAY,GAAA,CAAI,YAAY,CAAA,EAAG;AACjC,IAAA,OAAO,WAAA,CAAY,IAAI,YAAY,CAAA;AAAA,EACrC;AAEA,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,YAAA,EAAc;AAAA,IACpC,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU;AAG1B,MAAA,MAAM,MAAA,GAAS,SAAA,CAAU,MAAA,EAAQ,IAAI,CAAA;AACrC,MAAA,MAAM,eAAe,MAAA,EAAO;AAE5B,MAAA,MAAM,eAAe,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,YAAY,KAAK,CAAA;AAChE,MAAA,IAAI,iBAAiB,YAAA,EAAc;AAIjC,QAAA,MAAA,CAAO,YAAY,CAAA;AAAA,MACrB;AAEA,MAAA,IAAI,OAAO,iBAAiB,UAAA,EAAY;AACtC,QAAA,IAAI,YAAA,GAAe,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAA;AAChD,QAAA,IAAI,CAAC,YAAA,EAAc;AACjB,UAAA,YAAA,uBAAmB,GAAA,EAAI;AACvB,UAAA,kBAAA,CAAmB,GAAA,CAAI,QAAQ,YAAY,CAAA;AAAA,QAC7C;AACA,QAAA,MAAM,MAAA,GAAS,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA;AACpC,QAAA,IAAI,MAAA,IAAU,MAAA,CAAO,GAAA,KAAQ,YAAA,EAAc;AACzC,UAAA,OAAO,MAAA,CAAO,KAAA;AAAA,QAChB;AAEA,QAAA,MAAM,KAAA,GAAS,YAAA,CAAuB,IAAA,CAAK,QAAA,IAAY,KAAK,CAAA;AAC5D,QAAA,YAAA,CAAa,IAAI,IAAA,EAAM,EAAE,GAAA,EAAK,YAAA,EAAuB,OAAO,CAAA;AAC5D,QAAA,OAAO,KAAA;AAAA,MACT;AACA,MAAA;AACE,QAAA,MAAM,YAAA,GAAe,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAA;AAClD,QAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,EAAG;AAC1C,UAAA,YAAA,CAAa,OAAO,IAAI,CAAA;AACxB,UAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,YAAA,kBAAA,CAAmB,OAAO,MAAM,CAAA;AAAA,UAClC;AAAA,QACF;AAAA,MACF;AAGA,MAAA,IAAI,OAAO,YAAA,KAAiB,QAAA,IAAY,YAAA,KAAiB,IAAA,EAAM;AAC7D,QAAA,OAAO,OAAO,YAAuC,CAAA;AAAA,MACvD;AAGA,MAAA,OAAO,YAAA;AAAA,IACT,CAAA;AAAA,IAEA,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,QAAA,EAAU;AACpC,MAAA,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,MAAM,KAAK,IAAA,KAAS,QAAA,GAAW,OAAO,MAAA,GAAS,MAAA;AAC/E,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,MAAM,QAAQ,CAAA;AACnD,MAAA,MAAM,SAAS,MAAA,CAAO,SAAA,CAAU,cAAA,CAAe,IAAA,CAAK,QAAQ,IAAI,CAAA;AAGhE,MAAA,IAAI,QAAA,KAAa,YAAY,MAAA,EAAQ;AACnC,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,MAAM,SAAS,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,UAAU,QAAQ,CAAA;AAG3D,MAAA,MAAM,YAAA,GAAe,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAA;AAClD,MAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,EAAG;AAC1C,QAAA,YAAA,CAAa,OAAO,IAAI,CAAA;AACxB,QAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,UAAA,kBAAA,CAAmB,OAAO,MAAM,CAAA;AAAA,QAClC;AAAA,MACF;AAGA,MAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACvC,MAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC5B,QAAA,OAAA,CAAQ,IAAI,EAAE,QAAQ,CAAA;AAAA,MACxB;AAGA,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,gBAAA,CAAiB,MAAM,CAAA;AAAA,MACzB;AAIA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,IAAK,SAAS,QAAA,EAAU;AAC9C,QAAA,MAAMC,QAAAA,GAAU,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACvC,QAAA,IAAIA,QAAAA,IAAWA,SAAQ,MAAA,EAAQ;AAC7B,UAAAA,QAAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AAAA,QAC9B;AAAA,MACF;AAGA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,IAAK,SAAS,QAAA,EAAU;AAC9C,QAAA,MAAM,aAAa,MAAA,CAAO,MAAA;AAC1B,QAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,UAAA,GAAa,SAAA,EAAW;AAC3D,UAAA,MAAMA,QAAAA,GAAU,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACvC,UAAA,IAAIA,QAAAA,EAAS;AACX,YAAA,KAAA,IAAS,CAAA,GAAI,UAAA,EAAY,CAAA,GAAI,SAAA,EAAW,KAAK,CAAA,EAAG;AAC9C,cAAA,MAAM,GAAA,GAAM,OAAO,CAAC,CAAA;AACpB,cAAA,IAAIA,QAAAA,CAAQ,GAAG,CAAA,EAAG;AAChB,gBAAAA,QAAAA,CAAQ,GAAG,CAAA,CAAE,MAAS,CAAA;AAAA,cACxB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA,QAAA,gBAAA,CAAiB,MAAM,CAAA;AAAA,MACzB;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IAEA,cAAA,CAAe,QAAQ,IAAA,EAAM;AAC3B,MAAA,MAAM,SAAS,MAAA,CAAO,SAAA,CAAU,cAAA,CAAe,IAAA,CAAK,QAAQ,IAAI,CAAA;AAChE,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,cAAA,CAAe,MAAA,EAAQ,IAAI,CAAA;AAElD,MAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,QAAA,MAAM,OAAA,GAAU,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AACvC,QAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC5B,UAAA,OAAA,CAAQ,IAAI,EAAE,MAAS,CAAA;AAAA,QACzB;AAGA,QAAA,MAAM,YAAA,GAAe,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAA;AAClD,QAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,EAAG;AAC1C,UAAA,YAAA,CAAa,OAAO,IAAI,CAAA;AACxB,UAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,YAAA,kBAAA,CAAmB,OAAO,MAAM,CAAA;AAAA,UAClC;AAAA,QACF;AAEA,QAAA,gBAAA,CAAiB,MAAM,CAAA;AAAA,MACzB;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IAEA,QAAQ,MAAA,EAAQ;AACd,MAAA,SAAA,CAAU,MAAA,EAAQ,WAAW,CAAA,EAAE;AAC/B,MAAA,OAAO,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,IAC/B,CAAA;AAAA,IAEA,GAAA,CAAI,QAAQ,IAAA,EAAM;AAChB,MAAA,SAAA,CAAU,MAAA,EAAQ,IAAI,CAAA,EAAE;AACxB,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAI,CAAA;AAAA,IACjC;AAAA,GACD,CAAA;AAED,EAAA,WAAA,CAAY,GAAA,CAAI,cAAc,KAAK,CAAA;AACnC,EAAA,OAAO,KAAA;AACT;AC9DA,IAAM,mBAAA,GAAsD;AAAA,EAC1D,IAAA,EAAM,QAAA;AAAA,EACN,OAAO,MAAA,CAAO,iBAAA;AAAA,EACd,oBAAA,EAAsB,KAAA;AAAA,EACtB,WAAA,EAAa;AACf,CAAA;AA4CO,SAAS,SACd,gBAAA,EAGmB;AACnB,EAAA,MAAM,OAAA,GAAU,OAAO,gBAAA,KAAqB,UAAA,GAAa,mBAAmB,gBAAA,CAAiB,KAAA;AAC7F,EAAA,MAAM,cAAc,OAAO,gBAAA,KAAqB,QAAA,IAAY,CAAC,CAAC,gBAAA,CAAiB,QAAA;AAC/E,EAAA,MAAM,YAAA,GACJ,OAAO,gBAAA,KAAqB,QAAA,GAAY,iBAAiB,KAAA,IAAS,KAAM,EAAC;AAC3E,EAAA,MAAM,oBAAA,GAAuB,EAAE,GAAG,mBAAA,EAAqB,GAAG,YAAA,EAAa;AACvE,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAqC;AAEvD,EAAA,MAAM,WAAW,CAAC,YAAA,KAChB,OAAO,YAAA,KAAiB,UAAA,GAAc,cAA4B,GAAI,YAAA;AAExE,EAAA,MAAM,UAAA,GAAa,CAAC,YAAA,KAA+C;AACjE,IAAA,MAAM,SAAA,GAAY,SAAS,YAAY,CAAA;AACvC,IAAA,IAAI,OAAO,gBAAA,KAAqB,QAAA,IAAY,gBAAA,CAAiB,QAAQ,MAAA,EAAW;AAC9E,MAAA,MAAM,MAAM,gBAAA,CAAiB,GAAA;AAC7B,MAAA,OAAO,OAAO,GAAA,KAAQ,UAAA,GAAc,GAAA,CAAgC,SAAS,CAAA,GAAI,GAAA;AAAA,IACnF;AACA,IAAA,OAAO,SAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,iBAAiB,MAAe;AACpC,IAAA,IAAI,OAAO,gBAAA,KAAqB,QAAA,EAAU,OAAO,MAAA;AACjD,IAAA,MAAM,QAAQ,gBAAA,CAAiB,KAAA;AAC/B,IAAA,IAAI,OAAO,KAAA,KAAU,UAAA,IAAe,KAAA,CAAwB,WAAW,CAAA,EAAG;AACxE,MAAA,OAAQ,KAAA,EAAwB;AAAA,IAClC;AACA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,GAAA,KAAyC;AAC5D,IAAA,IAAI,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACzB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,KAAA,GAAQ;AAAA,QACN,IAAA,EAAMD,sBAA4B,MAAS,CAAA;AAAA,QAC3C,OAAA,EAASA,sBAAsB,KAAK,CAAA;AAAA,QACpC,KAAA,EAAOA,sBAAsB,MAAS,CAAA;AAAA,QACtC,OAAA,EAASA,sBAAa,CAAC,CAAA;AAAA,QACvB,YAAA,EAAc,IAAA;AAAA,QACd,QAAA,EAAU,MAAA;AAAA,QACV,WAAA,EAAa,EAAA;AAAA,QACb,SAAA,EAAW,MAAA;AAAA,QACX,QAAA,EAAU,KAAA;AAAA,QACV,MAAA,EAAQ,MAAA;AAAA,QACR,UAAA,EAAY,CAAA;AAAA,QACZ,SAAA,EAAW,MAAA;AAAA,QACX,QAAA,EAAU,MAAA;AAAA,QACV,YAAA,EAAc,MAAA;AAAA,QACd,UAAA,EAAY;AAAA,OACd;AACA,MAAA,KAAA,CAAM,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,IACtB;AACA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAA2C;AAC5D,IAAA,IAAI,oBAAA,CAAqB,IAAA,KAAS,MAAA,EAAQ,OAAO,IAAA;AACjD,IAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,oBAAA,CAAqB,KAAK,GAAG,OAAO,KAAA;AACzD,IAAA,IAAI,KAAA,CAAM,SAAA,KAAc,MAAA,EAAW,OAAO,KAAA;AAC1C,IAAA,OAAO,KAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI;AAAA,EACpC,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAAkC;AACpD,IAAA,IAAI,oBAAA,CAAqB,SAAS,MAAA,EAAQ;AACxC,MAAA,KAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,GAAI,CAAA;AAC/B,MAAA;AAAA,IACF;AACA,IAAA,KAAA,CAAM,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,oBAAA,CAAqB,KAAK,IACxD,IAAA,CAAK,GAAA,EAAI,GAAI,oBAAA,CAAqB,KAAA,GAClC,MAAA;AAAA,EACN,CAAA;AAEA,EAAA,MAAM,aAAa,CACjB,KAAA,EACA,GAAA,EACA,IAAA,EACA,iBAAiB,KAAA,KACd;AACH,IAAA,IAAI,KAAA,CAAM,QAAA,IAAY,KAAA,CAAM,YAAA,KAAiB,IAAA,EAAM;AACjD,MAAA;AAAA,IACF;AACA,IAAA,KAAA,CAAM,YAAY,KAAA,EAAM;AACxB,IAAA,KAAA,CAAM,QAAA,GAAW,MAAA;AACjB,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,KAAA,CAAM,UAAA,GAAa,UAAA;AACnB,IAAA,KAAA,CAAM,MAAA,GAAS,SAAA;AAEf,IAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,MAAA,KAAA,CAAM,QAAQ,IAAI,CAAA;AAAA,IACpB;AACA,IAAA,KAAA,CAAM,MAAM,MAAS,CAAA;AACrB,IAAA,KAAA,CAAM,UAAA,IAAc,CAAA;AACpB,IAAA,MAAM,aAAa,KAAA,CAAM,UAAA;AAEzB,IAAA,MAAM,aAAA,GAAgB,WAAA,IAAe,CAAC,KAAA,CAAM,QAAA;AAC5C,IAAA,KAAA,CAAM,YAAA,GAAe,aAAA,GAAgBE,2BAAA,EAAoB,GAAI,IAAA;AAE7D,IAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,EAAE,MAAA,EAAQ,UAAA,CAAW,QAAO,EAAG,IAAI,CAAA,CAC7D,IAAA,CAAK,CAAA,GAAA,KAAO;AACX,MAAA,IAAI,UAAA,CAAW,MAAA,CAAO,OAAA,IAAW,KAAA,CAAM,eAAe,UAAA,EAAY;AAClE,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AACd,MAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AACjB,MAAA,KAAA,CAAM,MAAA,GAAS,SAAA;AACf,MAAA,KAAA,CAAM,QAAQ,KAAK,CAAA;AACnB,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA,IAAI,MAAM,YAAA,EAAc;AACtB,QAAA,KAAA,CAAM,aAAa,OAAA,EAAQ;AAC3B,QAAA,KAAA,CAAM,YAAA,GAAe,IAAA;AAAA,MACvB;AAAA,IACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAA,GAAA,KAAO;AACZ,MAAA,IAAI,UAAA,CAAW,MAAA,CAAO,OAAA,IAAW,KAAA,CAAM,eAAe,UAAA,EAAY;AAClE,MAAA,KAAA,CAAM,MAAM,GAAG,CAAA;AACf,MAAA,KAAA,CAAM,MAAA,GAAS,OAAA;AACf,MAAA,KAAA,CAAM,QAAQ,KAAK,CAAA;AACnB,MAAA,IAAI,qBAAqB,WAAA,EAAa;AACpC,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,GAAI,CAAA;AAC/B,QAAA,KAAA,CAAM,QAAA,GAAW,KAAA;AAAA,MACnB;AACA,MAAA,IAAI,MAAM,YAAA,EAAc;AACtB,QAAA,KAAA,CAAM,YAAA,CAAa,OAAO,GAAG,CAAA;AAC7B,QAAA,KAAA,CAAM,YAAA,GAAe,IAAA;AAAA,MACvB;AAAA,IACF,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,KAAA,CAAM,QAAA,GAAW,MAAA;AACjB,MAAA,KAAA,CAAM,YAAA,GAAe,MAAA;AACrB,MAAA,KAAA,CAAM,UAAA,GAAa,MAAA;AAAA,IACrB,CAAC,CAAA;AAEH,IAAA,KAAA,CAAM,QAAA,GAAW,YAAA;AACjB,IAAA,KAAA,CAAM,YAAA,GAAe,IAAA;AAErB,IAAAC,iBAAA,CAAU,MAAM;AACd,MAAA,IAAI,oBAAA,CAAqB,SAAS,MAAA,EAAQ;AACxC,QAAA,UAAA,CAAW,KAAA,EAAM;AACjB,QAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MAClB;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,GAAA,KAAkB;AACpC,IAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,MAAA,KAAA,CAAM,OAAA,CAAQ,CAAAC,MAAAA,KAAS;AACrB,QAAAA,MAAAA,CAAM,YAAY,KAAA,EAAM;AACxB,QAAAA,MAAAA,CAAM,OAAA,CAAQA,MAAAA,CAAM,OAAA,KAAY,CAAC,CAAA;AACjC,QAAAA,MAAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,GAAI,CAAA;AAAA,MACjC,CAAC,CAAA;AACD,MAAA,KAAA,CAAM,KAAA,EAAM;AACZ,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC3B,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,KAAA,CAAM,YAAY,KAAA,EAAM;AACxB,MAAA,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,OAAA,EAAQ,GAAI,CAAC,CAAA;AACjC,MAAA,KAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,GAAI,CAAA;AAC/B,MAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,IAClB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,IAAA,EAAY,WAAA,KAA0B;AACtD,IAAA,MAAM,GAAA,GAAM,WAAA,IAAe,UAAA,CAAW,IAAI,CAAA;AAC1C,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAG,CAAA;AAC7B,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,QAAA,IAAY,CAAC,UAAU,KAAK,CAAA;AACrD,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AACjB,MAAA,KAAA,CAAM,WAAA,GAAc,MAAM,OAAA,EAAQ;AAClC,MAAA,UAAA,CAAW,KAAA,EAAO,KAAK,IAAI,CAAA;AAAA,IAC7B;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CACb,YAAA,EACA,KAAA,EACA,OAAA,KACG;AACH,IAAA,MAAM,IAAA,GAAO,SAAS,YAAY,CAAA;AAClC,IAAA,MAAM,GAAA,GAAM,OAAA,EAAS,GAAA,IAAO,UAAA,CAAW,IAAI,CAAA;AAC3C,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAG,CAAA;AAC7B,IAAA,MAAM,SAAA,GAAY,MAAM,IAAA,EAAK;AAC7B,IAAA,MAAM,YACJ,OAAO,KAAA,KAAU,UAAA,GAAc,KAAA,CAAqC,SAAS,CAAA,GAAI,KAAA;AAEnF,IAAA,KAAA,CAAM,YAAY,KAAA,EAAM;AACxB,IAAA,KAAA,CAAM,QAAA,GAAW,MAAA;AACjB,IAAA,KAAA,CAAM,YAAA,GAAe,MAAA;AACrB,IAAA,KAAA,CAAM,UAAA,IAAc,CAAA;AAEpB,IAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AACpB,IAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AACjB,IAAA,KAAA,CAAM,MAAA,GAAS,SAAA;AACf,IAAA,KAAA,CAAM,QAAQ,KAAK,CAAA;AACnB,IAAA,KAAA,CAAM,MAAM,MAAS,CAAA;AACrB,IAAA,UAAA,CAAW,KAAK,CAAA;AAChB,IAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AACjB,IAAA,KAAA,CAAM,WAAA,GAAc,MAAM,OAAA,EAAQ;AAElC,IAAA,IAAI,MAAM,YAAA,EAAc;AACtB,MAAA,KAAA,CAAM,aAAa,OAAA,EAAQ;AAC3B,MAAA,KAAA,CAAM,YAAA,GAAe,IAAA;AAAA,IACvB;AAEA,IAAA,IAAI,SAAS,UAAA,EAAY;AACvB,MAAA,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,OAAA,EAAQ,GAAI,CAAC,CAAA;AAAA,IACnC;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,KAAK,YAAA,EAAsD;AACzD,MAAA,MAAM,QAAA,GAAWJ,sBAA4C,IAAI,CAAA;AAEjE,MAAAK,oBAAA,CAAa,MAAM;AACjB,QAAA,MAAM,GAAA,GAAM,WAAW,YAAY,CAAA;AACnC,QAAA,MAAM,KAAA,GAAQ,YAAY,GAAG,CAAA;AAC7B,QAAA,QAAA,CAAS,KAAK,CAAA;AACd,QAAA,MAAM,IAAA,GAAO,SAAS,YAAY,CAAA;AAClC,QAAA,MAAM,cAAA,GAAiB,MAAM,OAAA,EAAQ;AACrC,QAAA,MAAM,OAAA,GAAU,UAAU,KAAK,CAAA;AAC/B,QAAA,MAAM,WAAA,GAAc,MAAM,QAAA,KAAa,IAAA;AACvC,QAAA,MAAM,cAAA,GAAiB,MAAM,WAAA,KAAgB,cAAA;AAC7C,QAAA,MAAM,aAAa,cAAA,EAAe;AAClC,QAAA,MAAM,YAAA,GAAe,MAAM,SAAA,KAAc,UAAA;AAGzC,QAAA,MAAM,eAAA,GACJ,oBAAA,CAAqB,oBAAA,IAAwB,KAAA,CAAM,QAAA,IAAY,OAAA;AACjE,QAAA,MAAM,aAAA,GACH,OAAA,IAAW,CAAC,eAAA,IACb,WAAA,IACA,cAAA,IACA,YAAA,IACC,KAAA,CAAM,MAAA,KAAW,OAAA,IAAW,CAAC,oBAAA,CAAqB,WAAA;AAErD,QAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AACjB,QAAA,KAAA,CAAM,WAAA,GAAc,cAAA;AACpB,QAAA,KAAA,CAAM,SAAA,GAAY,UAAA;AAElB,QAAA,IAAI,aAAA,EAAe;AACjB,UAAA,IAAI,KAAA,CAAM,QAAA,KAAa,WAAA,IAAe,cAAA,CAAA,EAAiB;AACrD,YAAA,KAAA,CAAM,YAAY,KAAA,EAAM;AACxB,YAAA,KAAA,CAAM,QAAA,GAAW,MAAA;AAAA,UACnB;AACA,UAAA,IAAI,YAAA,EAAc;AAChB,YAAA,KAAA,CAAM,QAAA,GAAW,KAAA;AACjB,YAAA,KAAA,CAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,GAAI,CAAA;AAAA,UACjC;AACA,UAAA,UAAA,CAAW,KAAA,EAAO,KAAK,IAAY,CAAA;AAAA,QACrC,CAAA,MAAA,IAAW,eAAA,IAAmB,KAAA,CAAM,QAAA,KAAa,MAAA,EAAW;AAG1D,UAAA,UAAA,CAAW,KAAA,EAAO,GAAA,EAAK,IAAA,EAAc,IAAI,CAAA;AAAA,QAC3C;AAAA,MACF,CAAC,CAAA;AAED,MAAA,OAAO;AAAA,QACL,IAAI,IAAA,GAAO;AACT,UAAA,MAAM,QAAQ,QAAA,EAAS;AACvB,UAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,UAAA,IAAI,WAAA,IAAe,MAAM,YAAA,EAAc;AACrC,YAAA,MAAM,MAAM,YAAA,CAAa,KAAA;AAAA,UAC3B;AACA,UAAA,OAAO,MAAM,IAAA,EAAK;AAAA,QACpB,CAAA;AAAA,QACA,IAAI,OAAA,GAAU;AACZ,UAAA,MAAM,QAAQ,QAAA,EAAS;AACvB,UAAA,OAAO,KAAA,GAAQ,KAAA,CAAM,OAAA,EAAQ,GAAI,KAAA;AAAA,QACnC,CAAA;AAAA,QACA,IAAI,KAAA,GAAQ;AACV,UAAA,MAAM,QAAQ,QAAA,EAAS;AACvB,UAAA,OAAO,KAAA,GAAQ,KAAA,CAAM,KAAA,EAAM,GAAI,MAAA;AAAA,QACjC,CAAA;AAAA,QACA,SAAS,MAAM;AACb,UAAA,MAAM,QAAQ,QAAA,EAAS;AACvB,UAAA,IAAI,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,OAAA,KAAY,CAAC,CAAA;AAAA,QAC9C;AAAA,OACF;AAAA,IACF,CAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACF;AACF;AC1bO,SAAS,IAAA,CACd,MAAA,EACA,OAAA,GAAuB,EAAC,EACD;AACvB,EAAA,MAAM,EAAE,UAAA,GAAa,CAAA,EAAG,UAAA,GAAa,KAAK,GAAI,OAAA;AAE9C,EAAA,IAAI,MAAA,GAAmC,IAAA;AACvC,EAAA,IAAI,SAAA,GAAqB,IAAA;AACzB,EAAA,IAAI,cAAA,GAA0C,IAAA;AAC9C,EAAA,IAAI,YAAA,GAA8D,IAAA;AAClE,EAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,EAAA,MAAM,cAAc,MAAqB;AACvC,IAAA,OAAO,MAAA,EAAO,CACX,IAAA,CAAK,CAAA,GAAA,KAAO;AACX,MAAA,MAAA,GAAU,GAAA,CAA2B,OAAA;AACrC,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,UAAA,GAAa,CAAA;AACb,MAAA,YAAA,EAAc,OAAA,EAAQ;AAAA,IACxB,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAiB;AACvB,MAAA,IAAI,aAAa,UAAA,EAAY;AAC3B,QAAA,UAAA,EAAA;AACA,QAAA,MAAM,QAAQ,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,aAAa,CAAC,CAAA;AACrD,QAAA,OAAO,IAAI,QAAc,CAAA,OAAA,KAAW;AAClC,UAAA,UAAA,CAAW,MAAM;AACf,YAAA,OAAA,CAAQ,aAAa,CAAA;AAAA,UACvB,GAAG,KAAK,CAAA;AAAA,QACV,CAAC,CAAA;AAAA,MACH;AACA,MAAA,SAAA,GAAY,GAAA;AACZ,MAAA,YAAA,EAAc,OAAO,GAAG,CAAA;AACxB,MAAA,OAAO,MAAA;AAAA,IACT,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,cAAA,GAAiB,IAAA;AACjB,MAAA,YAAA,GAAe,IAAA;AAAA,IACjB,CAAC,CAAA;AAAA,EACL,CAAA;AAEA,EAAA,MAAM,SAAA,IAAa,CAAC,KAAA,KAAkB;AACpC,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO,OAAO,KAAK,CAAA;AAAA,IACrB;AACA,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,MAAM,SAAA;AAAA,IACR;AACA,IAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,MAAA,YAAA,GAAeH,2BAAAA,EAAoB;AACnC,MAAA,cAAA,GAAiB,WAAA,EAAY;AAAA,IAC/B;AACA,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,MAAM,YAAA,CAAa,KAAA;AAAA,IACrB;AAEA,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D,CAAA,CAAA;AAMA,EAAA,SAAA,CAAU,QAAQ,MAAM;AACtB,IAAA,SAAA,GAAY,IAAA;AACZ,IAAA,cAAA,GAAiB,IAAA;AACjB,IAAA,YAAA,GAAe,IAAA;AACf,IAAA,UAAA,GAAa,CAAA;AAAA,EAEf,CAAA;AAMA,EAAA,SAAA,CAAU,UAAU,MAAqB;AACvC,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB;AACA,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,OAAO,cAAA;AAAA,IACT;AACA,IAAA,YAAA,GAAeA,2BAAAA,EAAoB;AACnC,IAAA,cAAA,GAAiB,WAAA,EAAY;AAC7B,IAAA,OAAO,cAAA;AAAA,EACT,CAAA;AAEA,EAAA,OAAO,SAAA;AACT","file":"plus.cjs","sourcesContent":["/**\n * @fileoverview Deep reactive store implementation for Fict.\n *\n * $store creates a deeply reactive proxy that tracks property access at the path level.\n * Unlike $state (which is shallow), $store allows direct mutation of nested properties.\n *\n * @example\n * ```typescript\n * const user = $store({ name: 'Alice', address: { city: 'London' } })\n * user.address.city = 'Paris' // Fine-grained reactive update\n * ```\n */\n\nimport { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\n/** Function type for bound methods */\ntype AnyFn = (...args: unknown[]) => unknown\n\n/** Cache entry for bound methods to preserve identity */\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\n/** Type for objects with indexable properties */\ntype IndexableObject = Record<string | symbol, unknown>\n\n/** Cache of proxied objects to avoid duplicate proxies */\nconst PROXY_CACHE = new WeakMap<object, unknown>()\n\n/** Cache of signals per object property */\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\n\n/** Cache of bound methods to preserve function identity across reads */\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\n\n/** Special key for tracking iteration (Object.keys, for-in, etc.) */\nconst ITERATE_KEY = Symbol('iterate')\n\n/**\n * Get or create a signal for a specific property on a target object.\n * @internal\n */\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\n/**\n * Trigger iteration signal to notify consumers that keys have changed.\n * @internal\n */\nfunction triggerIteration(target: object): void {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\n/**\n * Create a deep reactive store using Proxy.\n *\n * Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:\n * - **Deep reactivity**: Nested objects are automatically wrapped in proxies\n * - **Direct mutation**: Modify properties directly without spread operators\n * - **Path-level tracking**: Only components reading changed paths re-render\n *\n * @param initialValue - The initial object to make reactive\n * @returns A reactive proxy of the object\n *\n * @example\n * ```tsx\n * import { $store } from 'fict'\n *\n * const form = $store({\n * user: { name: '', email: '' },\n * settings: { theme: 'light' }\n * })\n *\n * // Direct mutation works\n * form.user.name = 'Alice'\n *\n * // In JSX - only updates when form.user.name changes\n * <input value={form.user.name} />\n * ```\n *\n * @public\n */\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n {\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldLength = Array.isArray(target) && prop === 'length' ? target.length : undefined\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length(target.length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n if (Array.isArray(target) && prop === 'length') {\n const nextLength = target.length\n if (typeof oldLength === 'number' && nextLength < oldLength) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals) {\n for (let i = nextLength; i < oldLength; i += 1) {\n const key = String(i)\n if (signals[key]) {\n signals[key](undefined)\n }\n }\n }\n }\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n if (boundMethods.size === 0) {\n BOUND_METHOD_CACHE.delete(target)\n }\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n","/**\n * @fileoverview Async data fetching with caching and Suspense support.\n *\n * The `resource` function creates a reactive data fetcher that:\n * - Automatically cancels in-flight requests when args change\n * - Supports Suspense for loading states\n * - Provides caching with TTL and stale-while-revalidate\n * - Handles errors gracefully\n */\n\nimport { createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'\nimport { createSignal } from '@fictjs/runtime/advanced'\n\n/**\n * The result of reading a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n */\nexport interface ResourceResult<T> {\n /** The fetched data, or undefined if not yet loaded or on error */\n readonly data: T | undefined\n /** Whether the resource is currently loading (initial fetch or refetch) */\n readonly loading: boolean\n /**\n * Any error that occurred during fetching.\n * Type is unknown since errors can be any value in JavaScript.\n */\n readonly error: unknown\n /** Manually trigger a refetch of the resource */\n refresh: () => void\n}\n\n/**\n * Cache configuration options for a resource.\n */\nexport interface ResourceCacheOptions {\n /**\n * Caching mode:\n * - `'memory'`: Cache responses in memory (default)\n * - `'none'`: No caching, always refetch\n * @default 'memory'\n */\n mode?: 'memory' | 'none'\n\n /**\n * Time-to-live in milliseconds before cached data is considered stale.\n * @default Infinity\n */\n ttlMs?: number\n\n /**\n * If true, return stale cached data immediately while refetching in background.\n * @default false\n */\n staleWhileRevalidate?: boolean\n\n /**\n * If true, cache error responses as well.\n * @default false\n */\n cacheErrors?: boolean\n}\n\n/**\n * Configuration options for creating a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface ResourceOptions<T, Args> {\n /**\n * Custom cache key. Can be a static value or a function that computes\n * the key from the args. If not provided, args are used as the key.\n */\n key?: unknown | ((args: Args) => unknown)\n\n /**\n * The fetcher function that performs the async data retrieval.\n * Receives an AbortController signal for cancellation support.\n */\n fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>\n\n /**\n * If true, the resource will throw a Suspense token while loading,\n * enabling React-like Suspense boundaries.\n * @default false\n */\n suspense?: boolean\n\n /**\n * Cache configuration options.\n */\n cache?: ResourceCacheOptions\n\n /**\n * A value or reactive getter that, when changed, resets the resource.\n * Useful for clearing cache when certain conditions change.\n */\n reset?: unknown | (() => unknown)\n}\n\n/**\n * Return type of the resource factory.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface Resource<T, Args> {\n /**\n * Read the resource data, triggering a fetch if needed.\n * Can accept static args or a reactive getter.\n *\n * @param argsAccessor - Arguments or a getter returning arguments\n */\n read(argsAccessor: (() => Args) | Args): ResourceResult<T>\n\n /**\n * Invalidate cached data, causing the next read to refetch.\n *\n * @param key - Optional specific key to invalidate. If omitted, invalidates all.\n */\n invalidate(key?: unknown): void\n\n /**\n * Prefetch data without reading it. Useful for eager loading.\n *\n * @param args - Arguments to pass to the fetcher\n * @param keyOverride - Optional cache key override\n */\n prefetch(args: Args, keyOverride?: unknown): void\n\n /**\n * Optimistically update cached data for a given args/key.\n *\n * @param argsAccessor - Arguments or a getter returning arguments\n * @param value - New value or updater function\n * @param options - Optional settings (key override, revalidate)\n */\n mutate(\n argsAccessor: (() => Args) | Args,\n value: T | ((prev: T | undefined) => T),\n options?: { key?: unknown; revalidate?: boolean },\n ): void\n}\n\n/**\n * Resource status values for tracking fetch lifecycle.\n * @internal\n */\nexport type ResourceStatus = 'idle' | 'pending' | 'success' | 'error'\n\n/**\n * Internal cache entry for a resource.\n * Tracks the reactive state and metadata for a single cached fetch.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n * @internal\n */\ninterface ResourceEntry<T, Args> {\n /** Reactive signal for the fetched data */\n data: ReturnType<typeof createSignal<T | undefined>>\n /** Reactive signal for loading state */\n loading: ReturnType<typeof createSignal<boolean>>\n /** Reactive signal for error state */\n error: ReturnType<typeof createSignal<unknown>>\n /** Version counter for invalidation */\n version: ReturnType<typeof createSignal<number>>\n /** Suspense token when using suspense mode */\n pendingToken: ReturnType<typeof createSuspenseToken> | null\n /** Last used arguments for change detection */\n lastArgs: Args | undefined\n /** Last seen version for change detection */\n lastVersion: number\n /** Last reset token value for change detection */\n lastReset: unknown\n /** Whether we have a valid cached value */\n hasValue: boolean\n /** Current fetch status */\n status: ResourceStatus\n /** Generation counter to handle race conditions */\n generation: number\n /** Timestamp when the cached value expires */\n expiresAt: number | undefined\n /** Currently in-flight fetch promise */\n inFlight: Promise<void> | undefined\n /** Args used for the current in-flight request */\n inFlightArgs: Args | undefined\n /** AbortController for cancelling in-flight requests */\n controller: AbortController | undefined\n}\n\nconst defaultCacheOptions: Required<ResourceCacheOptions> = {\n mode: 'memory',\n ttlMs: Number.POSITIVE_INFINITY,\n staleWhileRevalidate: false,\n cacheErrors: false,\n}\n\n/**\n * Create a reactive async data resource.\n *\n * Resources handle async data fetching with automatic caching, cancellation,\n * and optional Suspense integration.\n *\n * @param optionsOrFetcher - A fetcher function or full configuration object\n * @returns A resource factory with read, invalidate, and prefetch methods\n *\n * @example\n * ```tsx\n * import { resource } from 'fict'\n *\n * // Simple fetcher\n * const userResource = resource(\n * ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}`, { signal }).then(r => r.json())\n * )\n *\n * // With full options\n * const postsResource = resource({\n * fetch: ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}/posts`, { signal }).then(r => r.json()),\n * suspense: true,\n * cache: {\n * ttlMs: 60_000,\n * staleWhileRevalidate: true,\n * },\n * })\n *\n * // Usage in component\n * function UserProfile({ userId }: { userId: string }) {\n * const { data, loading, error, refresh } = userResource.read(() => userId)\n *\n * if (loading) return <Spinner />\n * if (error) return <ErrorMessage error={error} />\n * return <div>{data.name}</div>\n * }\n * ```\n *\n * @public\n */\nexport function resource<T, Args = void>(\n optionsOrFetcher:\n | ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)\n | ResourceOptions<T, Args>,\n): Resource<T, Args> {\n const fetcher = typeof optionsOrFetcher === 'function' ? optionsOrFetcher : optionsOrFetcher.fetch\n const useSuspense = typeof optionsOrFetcher === 'object' && !!optionsOrFetcher.suspense\n const cacheOptions: ResourceCacheOptions =\n typeof optionsOrFetcher === 'object' ? (optionsOrFetcher.cache ?? {}) : {}\n const resolvedCacheOptions = { ...defaultCacheOptions, ...cacheOptions }\n const cache = new Map<unknown, ResourceEntry<T, Args>>()\n\n const readArgs = (argsAccessor: (() => Args) | Args): Args =>\n typeof argsAccessor === 'function' ? (argsAccessor as () => Args)() : argsAccessor\n\n const computeKey = (argsAccessor: (() => Args) | Args): unknown => {\n const argsValue = readArgs(argsAccessor)\n if (typeof optionsOrFetcher === 'object' && optionsOrFetcher.key !== undefined) {\n const key = optionsOrFetcher.key\n return typeof key === 'function' ? (key as (args: Args) => unknown)(argsValue) : key\n }\n return argsValue\n }\n\n const readResetToken = (): unknown => {\n if (typeof optionsOrFetcher !== 'object') return undefined\n const reset = optionsOrFetcher.reset\n if (typeof reset === 'function' && (reset as () => unknown).length === 0) {\n return (reset as () => unknown)()\n }\n return reset\n }\n\n const ensureEntry = (key: unknown): ResourceEntry<T, Args> => {\n let state = cache.get(key)\n if (!state) {\n state = {\n data: createSignal<T | undefined>(undefined),\n loading: createSignal<boolean>(false),\n error: createSignal<unknown>(undefined),\n version: createSignal(0),\n pendingToken: null,\n lastArgs: undefined,\n lastVersion: -1,\n lastReset: undefined,\n hasValue: false,\n status: 'idle',\n generation: 0,\n expiresAt: undefined,\n inFlight: undefined,\n inFlightArgs: undefined,\n controller: undefined,\n }\n cache.set(key, state)\n }\n return state!\n }\n\n const isExpired = (entry: ResourceEntry<T, Args>): boolean => {\n if (resolvedCacheOptions.mode === 'none') return true\n if (!Number.isFinite(resolvedCacheOptions.ttlMs)) return false\n if (entry.expiresAt === undefined) return false\n return entry.expiresAt < Date.now()\n }\n\n const markExpiry = (entry: ResourceEntry<T, Args>) => {\n if (resolvedCacheOptions.mode === 'none') {\n entry.expiresAt = Date.now() - 1\n return\n }\n entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs)\n ? Date.now() + resolvedCacheOptions.ttlMs\n : undefined\n }\n\n const startFetch = (\n entry: ResourceEntry<T, Args>,\n key: unknown,\n args: Args,\n isRevalidating = false,\n ) => {\n if (entry.inFlight && entry.inFlightArgs === args) {\n return\n }\n entry.controller?.abort()\n entry.inFlight = undefined\n const controller = new AbortController()\n entry.controller = controller\n entry.status = 'pending'\n // For stale-while-revalidate: don't show loading if we already have data to display\n if (!isRevalidating) {\n entry.loading(true)\n }\n entry.error(undefined)\n entry.generation += 1\n const currentGen = entry.generation\n\n const shouldSuspend = useSuspense && !entry.hasValue\n entry.pendingToken = shouldSuspend ? createSuspenseToken() : null\n\n const fetchPromise = fetcher({ signal: controller.signal }, args)\n .then(res => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.data(res)\n entry.hasValue = true\n entry.status = 'success'\n entry.loading(false)\n markExpiry(entry)\n if (entry.pendingToken) {\n entry.pendingToken.resolve()\n entry.pendingToken = null\n }\n })\n .catch(err => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.error(err)\n entry.status = 'error'\n entry.loading(false)\n if (resolvedCacheOptions.cacheErrors) {\n markExpiry(entry)\n } else {\n entry.expiresAt = Date.now() - 1\n entry.hasValue = false\n }\n if (entry.pendingToken) {\n entry.pendingToken.reject(err)\n entry.pendingToken = null\n }\n })\n .finally(() => {\n entry.inFlight = undefined\n entry.inFlightArgs = undefined\n entry.controller = undefined\n })\n\n entry.inFlight = fetchPromise\n entry.inFlightArgs = args\n\n onCleanup(() => {\n if (resolvedCacheOptions.mode === 'none') {\n controller.abort()\n cache.delete(key)\n }\n })\n }\n\n const invalidate = (key?: unknown) => {\n if (key === undefined) {\n cache.forEach(entry => {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n })\n cache.clear()\n return\n }\n const entry = cache.get(key)\n if (entry) {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n cache.delete(key)\n }\n }\n\n const prefetch = (args: Args, keyOverride?: unknown) => {\n const key = keyOverride ?? computeKey(args)\n const entry = ensureEntry(key)\n const usableData = entry.hasValue && !isExpired(entry)\n if (!usableData) {\n entry.lastArgs = args\n entry.lastVersion = entry.version()\n startFetch(entry, key, args)\n }\n }\n\n const mutate = (\n argsAccessor: (() => Args) | Args,\n value: T | ((prev: T | undefined) => T),\n options?: { key?: unknown; revalidate?: boolean },\n ) => {\n const args = readArgs(argsAccessor)\n const key = options?.key ?? computeKey(args)\n const entry = ensureEntry(key)\n const prevValue = entry.data()\n const nextValue =\n typeof value === 'function' ? (value as (prev: T | undefined) => T)(prevValue) : value\n\n entry.controller?.abort()\n entry.inFlight = undefined\n entry.inFlightArgs = undefined\n entry.generation += 1\n\n entry.data(nextValue)\n entry.hasValue = true\n entry.status = 'success'\n entry.loading(false)\n entry.error(undefined)\n markExpiry(entry)\n entry.lastArgs = args\n entry.lastVersion = entry.version()\n\n if (entry.pendingToken) {\n entry.pendingToken.resolve()\n entry.pendingToken = null\n }\n\n if (options?.revalidate) {\n entry.version(entry.version() + 1)\n }\n }\n\n return {\n read(argsAccessor: (() => Args) | Args): ResourceResult<T> {\n const entryRef = createSignal<ResourceEntry<T, Args> | null>(null)\n\n createEffect(() => {\n const key = computeKey(argsAccessor)\n const entry = ensureEntry(key)\n entryRef(entry)\n const args = readArgs(argsAccessor)\n const currentVersion = entry.version()\n const expired = isExpired(entry)\n const argsChanged = entry.lastArgs !== args\n const versionChanged = entry.lastVersion !== currentVersion\n const resetToken = readResetToken()\n const resetChanged = entry.lastReset !== resetToken\n // For stale-while-revalidate: if we have cached data, don't treat expired as requiring immediate refetch\n // We'll handle the revalidation separately to show stale data without loading state\n const canUseStaleData =\n resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired\n const shouldRefetch =\n (expired && !canUseStaleData) ||\n argsChanged ||\n versionChanged ||\n resetChanged ||\n (entry.status === 'error' && !resolvedCacheOptions.cacheErrors)\n\n entry.lastArgs = args\n entry.lastVersion = currentVersion\n entry.lastReset = resetToken\n\n if (shouldRefetch) {\n if (entry.inFlight && (argsChanged || versionChanged)) {\n entry.controller?.abort()\n entry.inFlight = undefined\n }\n if (resetChanged) {\n entry.hasValue = false\n entry.expiresAt = Date.now() - 1\n }\n startFetch(entry, key, args as Args)\n } else if (canUseStaleData && entry.inFlight === undefined) {\n // stale-while-revalidate: return stale data immediately, refresh in background\n // Pass isRevalidating=true to avoid showing loading state\n startFetch(entry, key, args as Args, true)\n }\n })\n\n return {\n get data() {\n const entry = entryRef()\n if (!entry) return undefined\n if (useSuspense && entry.pendingToken) {\n throw entry.pendingToken.token\n }\n return entry.data()\n },\n get loading() {\n const entry = entryRef()\n return entry ? entry.loading() : false\n },\n get error() {\n const entry = entryRef()\n return entry ? entry.error() : undefined\n },\n refresh: () => {\n const entry = entryRef()\n if (entry) entry.version(entry.version() + 1)\n },\n }\n },\n invalidate,\n prefetch,\n mutate,\n }\n}\n","/**\n * @fileoverview Lazy component loading with Suspense support.\n *\n * Creates a component that loads its implementation asynchronously,\n * suspending rendering until the module is loaded.\n */\n\nimport { createSuspenseToken } from '@fictjs/runtime'\nimport type { Component } from '@fictjs/runtime'\n\n/** Module shape expected from dynamic imports */\nexport interface LazyModule<TProps extends Record<string, unknown>> {\n default: Component<TProps>\n}\n\n/** Options for lazy loading behavior */\nexport interface LazyOptions {\n /**\n * Maximum number of retry attempts on load failure.\n * Set to 0 to disable retries (default behavior).\n * @default 0\n */\n maxRetries?: number\n\n /**\n * Delay in milliseconds between retry attempts.\n * Uses exponential backoff: delay * 2^(attempt - 1)\n * @default 1000\n */\n retryDelay?: number\n}\n\n/** Extended component with retry capability */\nexport interface LazyComponent<TProps extends Record<string, unknown>> extends Component<TProps> {\n /**\n * Reset the lazy component state, allowing it to retry loading.\n * Useful when used with ErrorBoundary reset functionality.\n */\n reset: () => void\n\n /**\n * Preload the component without rendering it.\n * Returns a promise that resolves when the component is loaded.\n */\n preload: () => Promise<void>\n}\n\n/**\n * Create a lazy component that suspends while loading.\n *\n * @param loader - Function that returns a promise resolving to the component module\n * @param options - Optional configuration for retry behavior\n * @returns A component that suspends during loading and supports retry on failure\n *\n * @example\n * ```tsx\n * import { lazy, Suspense } from 'fict'\n *\n * // Basic usage\n * const LazyChart = lazy(() => import('./Chart'))\n *\n * // With retry options\n * const LazyDashboard = lazy(() => import('./Dashboard'), {\n * maxRetries: 3,\n * retryDelay: 1000\n * })\n *\n * function App() {\n * return (\n * <Suspense fallback={<Loading />}>\n * <LazyChart />\n * </Suspense>\n * )\n * }\n *\n * // Reset on error (with ErrorBoundary)\n * <ErrorBoundary fallback={(err, reset) => (\n * <button onClick={() => { LazyChart.reset(); reset(); }}>Retry</button>\n * )}>\n * <LazyChart />\n * </ErrorBoundary>\n * ```\n *\n * @public\n */\nexport function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(\n loader: () => Promise<LazyModule<TProps> | { default: Component<TProps> }>,\n options: LazyOptions = {},\n): LazyComponent<TProps> {\n const { maxRetries = 0, retryDelay = 1000 } = options\n\n let loaded: Component<TProps> | null = null\n let loadError: unknown = null\n let loadingPromise: Promise<unknown> | null = null\n let pendingToken: ReturnType<typeof createSuspenseToken> | null = null\n let retryCount = 0\n\n const attemptLoad = (): Promise<void> => {\n return loader()\n .then(mod => {\n loaded = (mod as LazyModule<TProps>).default\n loadError = null\n retryCount = 0\n pendingToken?.resolve()\n })\n .catch((err: unknown) => {\n if (retryCount < maxRetries) {\n retryCount++\n const delay = retryDelay * Math.pow(2, retryCount - 1)\n return new Promise<void>(resolve => {\n setTimeout(() => {\n resolve(attemptLoad())\n }, delay)\n })\n }\n loadError = err\n pendingToken?.reject(err)\n return undefined\n })\n .finally(() => {\n loadingPromise = null\n pendingToken = null\n })\n }\n\n const component = ((props: TProps) => {\n if (loaded) {\n return loaded(props)\n }\n if (loadError) {\n throw loadError\n }\n if (!loadingPromise) {\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n }\n if (pendingToken) {\n throw pendingToken.token\n }\n // Should never hit if pendingToken exists, but fallback for type safety.\n throw new Error('Lazy component failed to start loading')\n }) as LazyComponent<TProps>\n\n /**\n * Reset the lazy component state, clearing any cached error.\n * Call this before triggering a re-render to retry loading.\n */\n component.reset = () => {\n loadError = null\n loadingPromise = null\n pendingToken = null\n retryCount = 0\n // Note: we don't clear `loaded` - if it was successfully loaded, keep it\n }\n\n /**\n * Preload the component without rendering.\n * Useful for eager loading on route prefetch.\n */\n component.preload = (): Promise<void> => {\n if (loaded) {\n return Promise.resolve()\n }\n if (loadingPromise) {\n return loadingPromise as Promise<void>\n }\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n return loadingPromise as Promise<void>\n }\n\n return component\n}\n"]}
|
package/dist/plus.d.cts
CHANGED
|
@@ -117,6 +117,17 @@ interface Resource<T, Args> {
|
|
|
117
117
|
* @param keyOverride - Optional cache key override
|
|
118
118
|
*/
|
|
119
119
|
prefetch(args: Args, keyOverride?: unknown): void;
|
|
120
|
+
/**
|
|
121
|
+
* Optimistically update cached data for a given args/key.
|
|
122
|
+
*
|
|
123
|
+
* @param argsAccessor - Arguments or a getter returning arguments
|
|
124
|
+
* @param value - New value or updater function
|
|
125
|
+
* @param options - Optional settings (key override, revalidate)
|
|
126
|
+
*/
|
|
127
|
+
mutate(argsAccessor: (() => Args) | Args, value: T | ((prev: T | undefined) => T), options?: {
|
|
128
|
+
key?: unknown;
|
|
129
|
+
revalidate?: boolean;
|
|
130
|
+
}): void;
|
|
120
131
|
}
|
|
121
132
|
/**
|
|
122
133
|
* Create a reactive async data resource.
|
package/dist/plus.d.ts
CHANGED
|
@@ -117,6 +117,17 @@ interface Resource<T, Args> {
|
|
|
117
117
|
* @param keyOverride - Optional cache key override
|
|
118
118
|
*/
|
|
119
119
|
prefetch(args: Args, keyOverride?: unknown): void;
|
|
120
|
+
/**
|
|
121
|
+
* Optimistically update cached data for a given args/key.
|
|
122
|
+
*
|
|
123
|
+
* @param argsAccessor - Arguments or a getter returning arguments
|
|
124
|
+
* @param value - New value or updater function
|
|
125
|
+
* @param options - Optional settings (key override, revalidate)
|
|
126
|
+
*/
|
|
127
|
+
mutate(argsAccessor: (() => Args) | Args, value: T | ((prev: T | undefined) => T), options?: {
|
|
128
|
+
key?: unknown;
|
|
129
|
+
revalidate?: boolean;
|
|
130
|
+
}): void;
|
|
120
131
|
}
|
|
121
132
|
/**
|
|
122
133
|
* Create a reactive async data resource.
|
package/dist/plus.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { $store } from './chunk-
|
|
1
|
+
export { $store } from './chunk-YSNNU4OE.js';
|
|
2
2
|
import { createEffect, createSuspenseToken, onCleanup } from '@fictjs/runtime';
|
|
3
3
|
import { createSignal } from '@fictjs/runtime/advanced';
|
|
4
4
|
|
|
@@ -48,6 +48,7 @@ function resource(optionsOrFetcher) {
|
|
|
48
48
|
generation: 0,
|
|
49
49
|
expiresAt: void 0,
|
|
50
50
|
inFlight: void 0,
|
|
51
|
+
inFlightArgs: void 0,
|
|
51
52
|
controller: void 0
|
|
52
53
|
};
|
|
53
54
|
cache.set(key, state);
|
|
@@ -68,6 +69,9 @@ function resource(optionsOrFetcher) {
|
|
|
68
69
|
entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs) ? Date.now() + resolvedCacheOptions.ttlMs : void 0;
|
|
69
70
|
};
|
|
70
71
|
const startFetch = (entry, key, args, isRevalidating = false) => {
|
|
72
|
+
if (entry.inFlight && entry.inFlightArgs === args) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
71
75
|
entry.controller?.abort();
|
|
72
76
|
entry.inFlight = void 0;
|
|
73
77
|
const controller = new AbortController();
|
|
@@ -109,9 +113,11 @@ function resource(optionsOrFetcher) {
|
|
|
109
113
|
}
|
|
110
114
|
}).finally(() => {
|
|
111
115
|
entry.inFlight = void 0;
|
|
116
|
+
entry.inFlightArgs = void 0;
|
|
112
117
|
entry.controller = void 0;
|
|
113
118
|
});
|
|
114
119
|
entry.inFlight = fetchPromise;
|
|
120
|
+
entry.inFlightArgs = args;
|
|
115
121
|
onCleanup(() => {
|
|
116
122
|
if (resolvedCacheOptions.mode === "none") {
|
|
117
123
|
controller.abort();
|
|
@@ -147,6 +153,32 @@ function resource(optionsOrFetcher) {
|
|
|
147
153
|
startFetch(entry, key, args);
|
|
148
154
|
}
|
|
149
155
|
};
|
|
156
|
+
const mutate = (argsAccessor, value, options) => {
|
|
157
|
+
const args = readArgs(argsAccessor);
|
|
158
|
+
const key = options?.key ?? computeKey(args);
|
|
159
|
+
const entry = ensureEntry(key);
|
|
160
|
+
const prevValue = entry.data();
|
|
161
|
+
const nextValue = typeof value === "function" ? value(prevValue) : value;
|
|
162
|
+
entry.controller?.abort();
|
|
163
|
+
entry.inFlight = void 0;
|
|
164
|
+
entry.inFlightArgs = void 0;
|
|
165
|
+
entry.generation += 1;
|
|
166
|
+
entry.data(nextValue);
|
|
167
|
+
entry.hasValue = true;
|
|
168
|
+
entry.status = "success";
|
|
169
|
+
entry.loading(false);
|
|
170
|
+
entry.error(void 0);
|
|
171
|
+
markExpiry(entry);
|
|
172
|
+
entry.lastArgs = args;
|
|
173
|
+
entry.lastVersion = entry.version();
|
|
174
|
+
if (entry.pendingToken) {
|
|
175
|
+
entry.pendingToken.resolve();
|
|
176
|
+
entry.pendingToken = null;
|
|
177
|
+
}
|
|
178
|
+
if (options?.revalidate) {
|
|
179
|
+
entry.version(entry.version() + 1);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
150
182
|
return {
|
|
151
183
|
read(argsAccessor) {
|
|
152
184
|
const entryRef = createSignal(null);
|
|
@@ -204,7 +236,8 @@ function resource(optionsOrFetcher) {
|
|
|
204
236
|
};
|
|
205
237
|
},
|
|
206
238
|
invalidate,
|
|
207
|
-
prefetch
|
|
239
|
+
prefetch,
|
|
240
|
+
mutate
|
|
208
241
|
};
|
|
209
242
|
}
|
|
210
243
|
function lazy(loader, options = {}) {
|
package/dist/plus.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/resource.ts","../src/lazy.ts"],"names":["entry","createSuspenseToken"],"mappings":"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","file":"plus.js","sourcesContent":["/**\n * @fileoverview Async data fetching with caching and Suspense support.\n *\n * The `resource` function creates a reactive data fetcher that:\n * - Automatically cancels in-flight requests when args change\n * - Supports Suspense for loading states\n * - Provides caching with TTL and stale-while-revalidate\n * - Handles errors gracefully\n */\n\nimport { createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'\nimport { createSignal } from '@fictjs/runtime/advanced'\n\n/**\n * The result of reading a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n */\nexport interface ResourceResult<T> {\n /** The fetched data, or undefined if not yet loaded or on error */\n readonly data: T | undefined\n /** Whether the resource is currently loading (initial fetch or refetch) */\n readonly loading: boolean\n /**\n * Any error that occurred during fetching.\n * Type is unknown since errors can be any value in JavaScript.\n */\n readonly error: unknown\n /** Manually trigger a refetch of the resource */\n refresh: () => void\n}\n\n/**\n * Cache configuration options for a resource.\n */\nexport interface ResourceCacheOptions {\n /**\n * Caching mode:\n * - `'memory'`: Cache responses in memory (default)\n * - `'none'`: No caching, always refetch\n * @default 'memory'\n */\n mode?: 'memory' | 'none'\n\n /**\n * Time-to-live in milliseconds before cached data is considered stale.\n * @default Infinity\n */\n ttlMs?: number\n\n /**\n * If true, return stale cached data immediately while refetching in background.\n * @default false\n */\n staleWhileRevalidate?: boolean\n\n /**\n * If true, cache error responses as well.\n * @default false\n */\n cacheErrors?: boolean\n}\n\n/**\n * Configuration options for creating a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface ResourceOptions<T, Args> {\n /**\n * Custom cache key. Can be a static value or a function that computes\n * the key from the args. If not provided, args are used as the key.\n */\n key?: unknown | ((args: Args) => unknown)\n\n /**\n * The fetcher function that performs the async data retrieval.\n * Receives an AbortController signal for cancellation support.\n */\n fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>\n\n /**\n * If true, the resource will throw a Suspense token while loading,\n * enabling React-like Suspense boundaries.\n * @default false\n */\n suspense?: boolean\n\n /**\n * Cache configuration options.\n */\n cache?: ResourceCacheOptions\n\n /**\n * A value or reactive getter that, when changed, resets the resource.\n * Useful for clearing cache when certain conditions change.\n */\n reset?: unknown | (() => unknown)\n}\n\n/**\n * Return type of the resource factory.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface Resource<T, Args> {\n /**\n * Read the resource data, triggering a fetch if needed.\n * Can accept static args or a reactive getter.\n *\n * @param argsAccessor - Arguments or a getter returning arguments\n */\n read(argsAccessor: (() => Args) | Args): ResourceResult<T>\n\n /**\n * Invalidate cached data, causing the next read to refetch.\n *\n * @param key - Optional specific key to invalidate. If omitted, invalidates all.\n */\n invalidate(key?: unknown): void\n\n /**\n * Prefetch data without reading it. Useful for eager loading.\n *\n * @param args - Arguments to pass to the fetcher\n * @param keyOverride - Optional cache key override\n */\n prefetch(args: Args, keyOverride?: unknown): void\n}\n\n/**\n * Resource status values for tracking fetch lifecycle.\n * @internal\n */\nexport type ResourceStatus = 'idle' | 'pending' | 'success' | 'error'\n\n/**\n * Internal cache entry for a resource.\n * Tracks the reactive state and metadata for a single cached fetch.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n * @internal\n */\ninterface ResourceEntry<T, Args> {\n /** Reactive signal for the fetched data */\n data: ReturnType<typeof createSignal<T | undefined>>\n /** Reactive signal for loading state */\n loading: ReturnType<typeof createSignal<boolean>>\n /** Reactive signal for error state */\n error: ReturnType<typeof createSignal<unknown>>\n /** Version counter for invalidation */\n version: ReturnType<typeof createSignal<number>>\n /** Suspense token when using suspense mode */\n pendingToken: ReturnType<typeof createSuspenseToken> | null\n /** Last used arguments for change detection */\n lastArgs: Args | undefined\n /** Last seen version for change detection */\n lastVersion: number\n /** Last reset token value for change detection */\n lastReset: unknown\n /** Whether we have a valid cached value */\n hasValue: boolean\n /** Current fetch status */\n status: ResourceStatus\n /** Generation counter to handle race conditions */\n generation: number\n /** Timestamp when the cached value expires */\n expiresAt: number | undefined\n /** Currently in-flight fetch promise */\n inFlight: Promise<void> | undefined\n /** AbortController for cancelling in-flight requests */\n controller: AbortController | undefined\n}\n\nconst defaultCacheOptions: Required<ResourceCacheOptions> = {\n mode: 'memory',\n ttlMs: Number.POSITIVE_INFINITY,\n staleWhileRevalidate: false,\n cacheErrors: false,\n}\n\n/**\n * Create a reactive async data resource.\n *\n * Resources handle async data fetching with automatic caching, cancellation,\n * and optional Suspense integration.\n *\n * @param optionsOrFetcher - A fetcher function or full configuration object\n * @returns A resource factory with read, invalidate, and prefetch methods\n *\n * @example\n * ```tsx\n * import { resource } from 'fict'\n *\n * // Simple fetcher\n * const userResource = resource(\n * ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}`, { signal }).then(r => r.json())\n * )\n *\n * // With full options\n * const postsResource = resource({\n * fetch: ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}/posts`, { signal }).then(r => r.json()),\n * suspense: true,\n * cache: {\n * ttlMs: 60_000,\n * staleWhileRevalidate: true,\n * },\n * })\n *\n * // Usage in component\n * function UserProfile({ userId }: { userId: string }) {\n * const { data, loading, error, refresh } = userResource.read(() => userId)\n *\n * if (loading) return <Spinner />\n * if (error) return <ErrorMessage error={error} />\n * return <div>{data.name}</div>\n * }\n * ```\n *\n * @public\n */\nexport function resource<T, Args = void>(\n optionsOrFetcher:\n | ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)\n | ResourceOptions<T, Args>,\n): Resource<T, Args> {\n const fetcher = typeof optionsOrFetcher === 'function' ? optionsOrFetcher : optionsOrFetcher.fetch\n const useSuspense = typeof optionsOrFetcher === 'object' && !!optionsOrFetcher.suspense\n const cacheOptions: ResourceCacheOptions =\n typeof optionsOrFetcher === 'object' ? (optionsOrFetcher.cache ?? {}) : {}\n const resolvedCacheOptions = { ...defaultCacheOptions, ...cacheOptions }\n const cache = new Map<unknown, ResourceEntry<T, Args>>()\n\n const readArgs = (argsAccessor: (() => Args) | Args): Args =>\n typeof argsAccessor === 'function' ? (argsAccessor as () => Args)() : argsAccessor\n\n const computeKey = (argsAccessor: (() => Args) | Args): unknown => {\n const argsValue = readArgs(argsAccessor)\n if (typeof optionsOrFetcher === 'object' && optionsOrFetcher.key !== undefined) {\n const key = optionsOrFetcher.key\n return typeof key === 'function' ? (key as (args: Args) => unknown)(argsValue) : key\n }\n return argsValue\n }\n\n const readResetToken = (): unknown => {\n if (typeof optionsOrFetcher !== 'object') return undefined\n const reset = optionsOrFetcher.reset\n if (typeof reset === 'function' && (reset as () => unknown).length === 0) {\n return (reset as () => unknown)()\n }\n return reset\n }\n\n const ensureEntry = (key: unknown): ResourceEntry<T, Args> => {\n let state = cache.get(key)\n if (!state) {\n state = {\n data: createSignal<T | undefined>(undefined),\n loading: createSignal<boolean>(false),\n error: createSignal<unknown>(undefined),\n version: createSignal(0),\n pendingToken: null,\n lastArgs: undefined,\n lastVersion: -1,\n lastReset: undefined,\n hasValue: false,\n status: 'idle',\n generation: 0,\n expiresAt: undefined,\n inFlight: undefined,\n controller: undefined,\n }\n cache.set(key, state)\n }\n return state!\n }\n\n const isExpired = (entry: ResourceEntry<T, Args>): boolean => {\n if (resolvedCacheOptions.mode === 'none') return true\n if (!Number.isFinite(resolvedCacheOptions.ttlMs)) return false\n if (entry.expiresAt === undefined) return false\n return entry.expiresAt < Date.now()\n }\n\n const markExpiry = (entry: ResourceEntry<T, Args>) => {\n if (resolvedCacheOptions.mode === 'none') {\n entry.expiresAt = Date.now() - 1\n return\n }\n entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs)\n ? Date.now() + resolvedCacheOptions.ttlMs\n : undefined\n }\n\n const startFetch = (\n entry: ResourceEntry<T, Args>,\n key: unknown,\n args: Args,\n isRevalidating = false,\n ) => {\n entry.controller?.abort()\n entry.inFlight = undefined\n const controller = new AbortController()\n entry.controller = controller\n entry.status = 'pending'\n // For stale-while-revalidate: don't show loading if we already have data to display\n if (!isRevalidating) {\n entry.loading(true)\n }\n entry.error(undefined)\n entry.generation += 1\n const currentGen = entry.generation\n\n const shouldSuspend = useSuspense && !entry.hasValue\n entry.pendingToken = shouldSuspend ? createSuspenseToken() : null\n\n const fetchPromise = fetcher({ signal: controller.signal }, args)\n .then(res => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.data(res)\n entry.hasValue = true\n entry.status = 'success'\n entry.loading(false)\n markExpiry(entry)\n if (entry.pendingToken) {\n entry.pendingToken.resolve()\n entry.pendingToken = null\n }\n })\n .catch(err => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.error(err)\n entry.status = 'error'\n entry.loading(false)\n if (resolvedCacheOptions.cacheErrors) {\n markExpiry(entry)\n } else {\n entry.expiresAt = Date.now() - 1\n entry.hasValue = false\n }\n if (entry.pendingToken) {\n entry.pendingToken.reject(err)\n entry.pendingToken = null\n }\n })\n .finally(() => {\n entry.inFlight = undefined\n entry.controller = undefined\n })\n\n entry.inFlight = fetchPromise\n\n onCleanup(() => {\n if (resolvedCacheOptions.mode === 'none') {\n controller.abort()\n cache.delete(key)\n }\n })\n }\n\n const invalidate = (key?: unknown) => {\n if (key === undefined) {\n cache.forEach(entry => {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n })\n cache.clear()\n return\n }\n const entry = cache.get(key)\n if (entry) {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n cache.delete(key)\n }\n }\n\n const prefetch = (args: Args, keyOverride?: unknown) => {\n const key = keyOverride ?? computeKey(args)\n const entry = ensureEntry(key)\n const usableData = entry.hasValue && !isExpired(entry)\n if (!usableData) {\n entry.lastArgs = args\n entry.lastVersion = entry.version()\n startFetch(entry, key, args)\n }\n }\n\n return {\n read(argsAccessor: (() => Args) | Args): ResourceResult<T> {\n const entryRef = createSignal<ResourceEntry<T, Args> | null>(null)\n\n createEffect(() => {\n const key = computeKey(argsAccessor)\n const entry = ensureEntry(key)\n entryRef(entry)\n const args = readArgs(argsAccessor)\n const currentVersion = entry.version()\n const expired = isExpired(entry)\n const argsChanged = entry.lastArgs !== args\n const versionChanged = entry.lastVersion !== currentVersion\n const resetToken = readResetToken()\n const resetChanged = entry.lastReset !== resetToken\n // For stale-while-revalidate: if we have cached data, don't treat expired as requiring immediate refetch\n // We'll handle the revalidation separately to show stale data without loading state\n const canUseStaleData =\n resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired\n const shouldRefetch =\n (expired && !canUseStaleData) ||\n argsChanged ||\n versionChanged ||\n resetChanged ||\n (entry.status === 'error' && !resolvedCacheOptions.cacheErrors)\n\n entry.lastArgs = args\n entry.lastVersion = currentVersion\n entry.lastReset = resetToken\n\n if (shouldRefetch) {\n if (entry.inFlight && (argsChanged || versionChanged)) {\n entry.controller?.abort()\n entry.inFlight = undefined\n }\n if (resetChanged) {\n entry.hasValue = false\n entry.expiresAt = Date.now() - 1\n }\n startFetch(entry, key, args as Args)\n } else if (canUseStaleData && entry.inFlight === undefined) {\n // stale-while-revalidate: return stale data immediately, refresh in background\n // Pass isRevalidating=true to avoid showing loading state\n startFetch(entry, key, args as Args, true)\n }\n })\n\n return {\n get data() {\n const entry = entryRef()\n if (!entry) return undefined\n if (useSuspense && entry.pendingToken) {\n throw entry.pendingToken.token\n }\n return entry.data()\n },\n get loading() {\n const entry = entryRef()\n return entry ? entry.loading() : false\n },\n get error() {\n const entry = entryRef()\n return entry ? entry.error() : undefined\n },\n refresh: () => {\n const entry = entryRef()\n if (entry) entry.version(entry.version() + 1)\n },\n }\n },\n invalidate,\n prefetch,\n }\n}\n","/**\n * @fileoverview Lazy component loading with Suspense support.\n *\n * Creates a component that loads its implementation asynchronously,\n * suspending rendering until the module is loaded.\n */\n\nimport { createSuspenseToken } from '@fictjs/runtime'\nimport type { Component } from '@fictjs/runtime'\n\n/** Module shape expected from dynamic imports */\nexport interface LazyModule<TProps extends Record<string, unknown>> {\n default: Component<TProps>\n}\n\n/** Options for lazy loading behavior */\nexport interface LazyOptions {\n /**\n * Maximum number of retry attempts on load failure.\n * Set to 0 to disable retries (default behavior).\n * @default 0\n */\n maxRetries?: number\n\n /**\n * Delay in milliseconds between retry attempts.\n * Uses exponential backoff: delay * 2^(attempt - 1)\n * @default 1000\n */\n retryDelay?: number\n}\n\n/** Extended component with retry capability */\nexport interface LazyComponent<TProps extends Record<string, unknown>> extends Component<TProps> {\n /**\n * Reset the lazy component state, allowing it to retry loading.\n * Useful when used with ErrorBoundary reset functionality.\n */\n reset: () => void\n\n /**\n * Preload the component without rendering it.\n * Returns a promise that resolves when the component is loaded.\n */\n preload: () => Promise<void>\n}\n\n/**\n * Create a lazy component that suspends while loading.\n *\n * @param loader - Function that returns a promise resolving to the component module\n * @param options - Optional configuration for retry behavior\n * @returns A component that suspends during loading and supports retry on failure\n *\n * @example\n * ```tsx\n * import { lazy, Suspense } from 'fict'\n *\n * // Basic usage\n * const LazyChart = lazy(() => import('./Chart'))\n *\n * // With retry options\n * const LazyDashboard = lazy(() => import('./Dashboard'), {\n * maxRetries: 3,\n * retryDelay: 1000\n * })\n *\n * function App() {\n * return (\n * <Suspense fallback={<Loading />}>\n * <LazyChart />\n * </Suspense>\n * )\n * }\n *\n * // Reset on error (with ErrorBoundary)\n * <ErrorBoundary fallback={(err, reset) => (\n * <button onClick={() => { LazyChart.reset(); reset(); }}>Retry</button>\n * )}>\n * <LazyChart />\n * </ErrorBoundary>\n * ```\n *\n * @public\n */\nexport function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(\n loader: () => Promise<LazyModule<TProps> | { default: Component<TProps> }>,\n options: LazyOptions = {},\n): LazyComponent<TProps> {\n const { maxRetries = 0, retryDelay = 1000 } = options\n\n let loaded: Component<TProps> | null = null\n let loadError: unknown = null\n let loadingPromise: Promise<unknown> | null = null\n let pendingToken: ReturnType<typeof createSuspenseToken> | null = null\n let retryCount = 0\n\n const attemptLoad = (): Promise<void> => {\n return loader()\n .then(mod => {\n loaded = (mod as LazyModule<TProps>).default\n loadError = null\n retryCount = 0\n pendingToken?.resolve()\n })\n .catch((err: unknown) => {\n if (retryCount < maxRetries) {\n retryCount++\n const delay = retryDelay * Math.pow(2, retryCount - 1)\n return new Promise<void>(resolve => {\n setTimeout(() => {\n resolve(attemptLoad())\n }, delay)\n })\n }\n loadError = err\n pendingToken?.reject(err)\n return undefined\n })\n .finally(() => {\n loadingPromise = null\n pendingToken = null\n })\n }\n\n const component = ((props: TProps) => {\n if (loaded) {\n return loaded(props)\n }\n if (loadError) {\n throw loadError\n }\n if (!loadingPromise) {\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n }\n if (pendingToken) {\n throw pendingToken.token\n }\n // Should never hit if pendingToken exists, but fallback for type safety.\n throw new Error('Lazy component failed to start loading')\n }) as LazyComponent<TProps>\n\n /**\n * Reset the lazy component state, clearing any cached error.\n * Call this before triggering a re-render to retry loading.\n */\n component.reset = () => {\n loadError = null\n loadingPromise = null\n pendingToken = null\n retryCount = 0\n // Note: we don't clear `loaded` - if it was successfully loaded, keep it\n }\n\n /**\n * Preload the component without rendering.\n * Useful for eager loading on route prefetch.\n */\n component.preload = (): Promise<void> => {\n if (loaded) {\n return Promise.resolve()\n }\n if (loadingPromise) {\n return loadingPromise as Promise<void>\n }\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n return loadingPromise as Promise<void>\n }\n\n return component\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/resource.ts","../src/lazy.ts"],"names":["entry","createSuspenseToken"],"mappings":"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","file":"plus.js","sourcesContent":["/**\n * @fileoverview Async data fetching with caching and Suspense support.\n *\n * The `resource` function creates a reactive data fetcher that:\n * - Automatically cancels in-flight requests when args change\n * - Supports Suspense for loading states\n * - Provides caching with TTL and stale-while-revalidate\n * - Handles errors gracefully\n */\n\nimport { createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'\nimport { createSignal } from '@fictjs/runtime/advanced'\n\n/**\n * The result of reading a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n */\nexport interface ResourceResult<T> {\n /** The fetched data, or undefined if not yet loaded or on error */\n readonly data: T | undefined\n /** Whether the resource is currently loading (initial fetch or refetch) */\n readonly loading: boolean\n /**\n * Any error that occurred during fetching.\n * Type is unknown since errors can be any value in JavaScript.\n */\n readonly error: unknown\n /** Manually trigger a refetch of the resource */\n refresh: () => void\n}\n\n/**\n * Cache configuration options for a resource.\n */\nexport interface ResourceCacheOptions {\n /**\n * Caching mode:\n * - `'memory'`: Cache responses in memory (default)\n * - `'none'`: No caching, always refetch\n * @default 'memory'\n */\n mode?: 'memory' | 'none'\n\n /**\n * Time-to-live in milliseconds before cached data is considered stale.\n * @default Infinity\n */\n ttlMs?: number\n\n /**\n * If true, return stale cached data immediately while refetching in background.\n * @default false\n */\n staleWhileRevalidate?: boolean\n\n /**\n * If true, cache error responses as well.\n * @default false\n */\n cacheErrors?: boolean\n}\n\n/**\n * Configuration options for creating a resource.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface ResourceOptions<T, Args> {\n /**\n * Custom cache key. Can be a static value or a function that computes\n * the key from the args. If not provided, args are used as the key.\n */\n key?: unknown | ((args: Args) => unknown)\n\n /**\n * The fetcher function that performs the async data retrieval.\n * Receives an AbortController signal for cancellation support.\n */\n fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>\n\n /**\n * If true, the resource will throw a Suspense token while loading,\n * enabling React-like Suspense boundaries.\n * @default false\n */\n suspense?: boolean\n\n /**\n * Cache configuration options.\n */\n cache?: ResourceCacheOptions\n\n /**\n * A value or reactive getter that, when changed, resets the resource.\n * Useful for clearing cache when certain conditions change.\n */\n reset?: unknown | (() => unknown)\n}\n\n/**\n * Return type of the resource factory.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n */\nexport interface Resource<T, Args> {\n /**\n * Read the resource data, triggering a fetch if needed.\n * Can accept static args or a reactive getter.\n *\n * @param argsAccessor - Arguments or a getter returning arguments\n */\n read(argsAccessor: (() => Args) | Args): ResourceResult<T>\n\n /**\n * Invalidate cached data, causing the next read to refetch.\n *\n * @param key - Optional specific key to invalidate. If omitted, invalidates all.\n */\n invalidate(key?: unknown): void\n\n /**\n * Prefetch data without reading it. Useful for eager loading.\n *\n * @param args - Arguments to pass to the fetcher\n * @param keyOverride - Optional cache key override\n */\n prefetch(args: Args, keyOverride?: unknown): void\n\n /**\n * Optimistically update cached data for a given args/key.\n *\n * @param argsAccessor - Arguments or a getter returning arguments\n * @param value - New value or updater function\n * @param options - Optional settings (key override, revalidate)\n */\n mutate(\n argsAccessor: (() => Args) | Args,\n value: T | ((prev: T | undefined) => T),\n options?: { key?: unknown; revalidate?: boolean },\n ): void\n}\n\n/**\n * Resource status values for tracking fetch lifecycle.\n * @internal\n */\nexport type ResourceStatus = 'idle' | 'pending' | 'success' | 'error'\n\n/**\n * Internal cache entry for a resource.\n * Tracks the reactive state and metadata for a single cached fetch.\n *\n * @typeParam T - The type of data returned by the fetcher\n * @typeParam Args - The type of arguments passed to the fetcher\n * @internal\n */\ninterface ResourceEntry<T, Args> {\n /** Reactive signal for the fetched data */\n data: ReturnType<typeof createSignal<T | undefined>>\n /** Reactive signal for loading state */\n loading: ReturnType<typeof createSignal<boolean>>\n /** Reactive signal for error state */\n error: ReturnType<typeof createSignal<unknown>>\n /** Version counter for invalidation */\n version: ReturnType<typeof createSignal<number>>\n /** Suspense token when using suspense mode */\n pendingToken: ReturnType<typeof createSuspenseToken> | null\n /** Last used arguments for change detection */\n lastArgs: Args | undefined\n /** Last seen version for change detection */\n lastVersion: number\n /** Last reset token value for change detection */\n lastReset: unknown\n /** Whether we have a valid cached value */\n hasValue: boolean\n /** Current fetch status */\n status: ResourceStatus\n /** Generation counter to handle race conditions */\n generation: number\n /** Timestamp when the cached value expires */\n expiresAt: number | undefined\n /** Currently in-flight fetch promise */\n inFlight: Promise<void> | undefined\n /** Args used for the current in-flight request */\n inFlightArgs: Args | undefined\n /** AbortController for cancelling in-flight requests */\n controller: AbortController | undefined\n}\n\nconst defaultCacheOptions: Required<ResourceCacheOptions> = {\n mode: 'memory',\n ttlMs: Number.POSITIVE_INFINITY,\n staleWhileRevalidate: false,\n cacheErrors: false,\n}\n\n/**\n * Create a reactive async data resource.\n *\n * Resources handle async data fetching with automatic caching, cancellation,\n * and optional Suspense integration.\n *\n * @param optionsOrFetcher - A fetcher function or full configuration object\n * @returns A resource factory with read, invalidate, and prefetch methods\n *\n * @example\n * ```tsx\n * import { resource } from 'fict'\n *\n * // Simple fetcher\n * const userResource = resource(\n * ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}`, { signal }).then(r => r.json())\n * )\n *\n * // With full options\n * const postsResource = resource({\n * fetch: ({ signal }, userId: string) =>\n * fetch(`/api/users/${userId}/posts`, { signal }).then(r => r.json()),\n * suspense: true,\n * cache: {\n * ttlMs: 60_000,\n * staleWhileRevalidate: true,\n * },\n * })\n *\n * // Usage in component\n * function UserProfile({ userId }: { userId: string }) {\n * const { data, loading, error, refresh } = userResource.read(() => userId)\n *\n * if (loading) return <Spinner />\n * if (error) return <ErrorMessage error={error} />\n * return <div>{data.name}</div>\n * }\n * ```\n *\n * @public\n */\nexport function resource<T, Args = void>(\n optionsOrFetcher:\n | ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)\n | ResourceOptions<T, Args>,\n): Resource<T, Args> {\n const fetcher = typeof optionsOrFetcher === 'function' ? optionsOrFetcher : optionsOrFetcher.fetch\n const useSuspense = typeof optionsOrFetcher === 'object' && !!optionsOrFetcher.suspense\n const cacheOptions: ResourceCacheOptions =\n typeof optionsOrFetcher === 'object' ? (optionsOrFetcher.cache ?? {}) : {}\n const resolvedCacheOptions = { ...defaultCacheOptions, ...cacheOptions }\n const cache = new Map<unknown, ResourceEntry<T, Args>>()\n\n const readArgs = (argsAccessor: (() => Args) | Args): Args =>\n typeof argsAccessor === 'function' ? (argsAccessor as () => Args)() : argsAccessor\n\n const computeKey = (argsAccessor: (() => Args) | Args): unknown => {\n const argsValue = readArgs(argsAccessor)\n if (typeof optionsOrFetcher === 'object' && optionsOrFetcher.key !== undefined) {\n const key = optionsOrFetcher.key\n return typeof key === 'function' ? (key as (args: Args) => unknown)(argsValue) : key\n }\n return argsValue\n }\n\n const readResetToken = (): unknown => {\n if (typeof optionsOrFetcher !== 'object') return undefined\n const reset = optionsOrFetcher.reset\n if (typeof reset === 'function' && (reset as () => unknown).length === 0) {\n return (reset as () => unknown)()\n }\n return reset\n }\n\n const ensureEntry = (key: unknown): ResourceEntry<T, Args> => {\n let state = cache.get(key)\n if (!state) {\n state = {\n data: createSignal<T | undefined>(undefined),\n loading: createSignal<boolean>(false),\n error: createSignal<unknown>(undefined),\n version: createSignal(0),\n pendingToken: null,\n lastArgs: undefined,\n lastVersion: -1,\n lastReset: undefined,\n hasValue: false,\n status: 'idle',\n generation: 0,\n expiresAt: undefined,\n inFlight: undefined,\n inFlightArgs: undefined,\n controller: undefined,\n }\n cache.set(key, state)\n }\n return state!\n }\n\n const isExpired = (entry: ResourceEntry<T, Args>): boolean => {\n if (resolvedCacheOptions.mode === 'none') return true\n if (!Number.isFinite(resolvedCacheOptions.ttlMs)) return false\n if (entry.expiresAt === undefined) return false\n return entry.expiresAt < Date.now()\n }\n\n const markExpiry = (entry: ResourceEntry<T, Args>) => {\n if (resolvedCacheOptions.mode === 'none') {\n entry.expiresAt = Date.now() - 1\n return\n }\n entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs)\n ? Date.now() + resolvedCacheOptions.ttlMs\n : undefined\n }\n\n const startFetch = (\n entry: ResourceEntry<T, Args>,\n key: unknown,\n args: Args,\n isRevalidating = false,\n ) => {\n if (entry.inFlight && entry.inFlightArgs === args) {\n return\n }\n entry.controller?.abort()\n entry.inFlight = undefined\n const controller = new AbortController()\n entry.controller = controller\n entry.status = 'pending'\n // For stale-while-revalidate: don't show loading if we already have data to display\n if (!isRevalidating) {\n entry.loading(true)\n }\n entry.error(undefined)\n entry.generation += 1\n const currentGen = entry.generation\n\n const shouldSuspend = useSuspense && !entry.hasValue\n entry.pendingToken = shouldSuspend ? createSuspenseToken() : null\n\n const fetchPromise = fetcher({ signal: controller.signal }, args)\n .then(res => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.data(res)\n entry.hasValue = true\n entry.status = 'success'\n entry.loading(false)\n markExpiry(entry)\n if (entry.pendingToken) {\n entry.pendingToken.resolve()\n entry.pendingToken = null\n }\n })\n .catch(err => {\n if (controller.signal.aborted || entry.generation !== currentGen) return\n entry.error(err)\n entry.status = 'error'\n entry.loading(false)\n if (resolvedCacheOptions.cacheErrors) {\n markExpiry(entry)\n } else {\n entry.expiresAt = Date.now() - 1\n entry.hasValue = false\n }\n if (entry.pendingToken) {\n entry.pendingToken.reject(err)\n entry.pendingToken = null\n }\n })\n .finally(() => {\n entry.inFlight = undefined\n entry.inFlightArgs = undefined\n entry.controller = undefined\n })\n\n entry.inFlight = fetchPromise\n entry.inFlightArgs = args\n\n onCleanup(() => {\n if (resolvedCacheOptions.mode === 'none') {\n controller.abort()\n cache.delete(key)\n }\n })\n }\n\n const invalidate = (key?: unknown) => {\n if (key === undefined) {\n cache.forEach(entry => {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n })\n cache.clear()\n return\n }\n const entry = cache.get(key)\n if (entry) {\n entry.controller?.abort()\n entry.version(entry.version() + 1)\n entry.expiresAt = Date.now() - 1\n cache.delete(key)\n }\n }\n\n const prefetch = (args: Args, keyOverride?: unknown) => {\n const key = keyOverride ?? computeKey(args)\n const entry = ensureEntry(key)\n const usableData = entry.hasValue && !isExpired(entry)\n if (!usableData) {\n entry.lastArgs = args\n entry.lastVersion = entry.version()\n startFetch(entry, key, args)\n }\n }\n\n const mutate = (\n argsAccessor: (() => Args) | Args,\n value: T | ((prev: T | undefined) => T),\n options?: { key?: unknown; revalidate?: boolean },\n ) => {\n const args = readArgs(argsAccessor)\n const key = options?.key ?? computeKey(args)\n const entry = ensureEntry(key)\n const prevValue = entry.data()\n const nextValue =\n typeof value === 'function' ? (value as (prev: T | undefined) => T)(prevValue) : value\n\n entry.controller?.abort()\n entry.inFlight = undefined\n entry.inFlightArgs = undefined\n entry.generation += 1\n\n entry.data(nextValue)\n entry.hasValue = true\n entry.status = 'success'\n entry.loading(false)\n entry.error(undefined)\n markExpiry(entry)\n entry.lastArgs = args\n entry.lastVersion = entry.version()\n\n if (entry.pendingToken) {\n entry.pendingToken.resolve()\n entry.pendingToken = null\n }\n\n if (options?.revalidate) {\n entry.version(entry.version() + 1)\n }\n }\n\n return {\n read(argsAccessor: (() => Args) | Args): ResourceResult<T> {\n const entryRef = createSignal<ResourceEntry<T, Args> | null>(null)\n\n createEffect(() => {\n const key = computeKey(argsAccessor)\n const entry = ensureEntry(key)\n entryRef(entry)\n const args = readArgs(argsAccessor)\n const currentVersion = entry.version()\n const expired = isExpired(entry)\n const argsChanged = entry.lastArgs !== args\n const versionChanged = entry.lastVersion !== currentVersion\n const resetToken = readResetToken()\n const resetChanged = entry.lastReset !== resetToken\n // For stale-while-revalidate: if we have cached data, don't treat expired as requiring immediate refetch\n // We'll handle the revalidation separately to show stale data without loading state\n const canUseStaleData =\n resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired\n const shouldRefetch =\n (expired && !canUseStaleData) ||\n argsChanged ||\n versionChanged ||\n resetChanged ||\n (entry.status === 'error' && !resolvedCacheOptions.cacheErrors)\n\n entry.lastArgs = args\n entry.lastVersion = currentVersion\n entry.lastReset = resetToken\n\n if (shouldRefetch) {\n if (entry.inFlight && (argsChanged || versionChanged)) {\n entry.controller?.abort()\n entry.inFlight = undefined\n }\n if (resetChanged) {\n entry.hasValue = false\n entry.expiresAt = Date.now() - 1\n }\n startFetch(entry, key, args as Args)\n } else if (canUseStaleData && entry.inFlight === undefined) {\n // stale-while-revalidate: return stale data immediately, refresh in background\n // Pass isRevalidating=true to avoid showing loading state\n startFetch(entry, key, args as Args, true)\n }\n })\n\n return {\n get data() {\n const entry = entryRef()\n if (!entry) return undefined\n if (useSuspense && entry.pendingToken) {\n throw entry.pendingToken.token\n }\n return entry.data()\n },\n get loading() {\n const entry = entryRef()\n return entry ? entry.loading() : false\n },\n get error() {\n const entry = entryRef()\n return entry ? entry.error() : undefined\n },\n refresh: () => {\n const entry = entryRef()\n if (entry) entry.version(entry.version() + 1)\n },\n }\n },\n invalidate,\n prefetch,\n mutate,\n }\n}\n","/**\n * @fileoverview Lazy component loading with Suspense support.\n *\n * Creates a component that loads its implementation asynchronously,\n * suspending rendering until the module is loaded.\n */\n\nimport { createSuspenseToken } from '@fictjs/runtime'\nimport type { Component } from '@fictjs/runtime'\n\n/** Module shape expected from dynamic imports */\nexport interface LazyModule<TProps extends Record<string, unknown>> {\n default: Component<TProps>\n}\n\n/** Options for lazy loading behavior */\nexport interface LazyOptions {\n /**\n * Maximum number of retry attempts on load failure.\n * Set to 0 to disable retries (default behavior).\n * @default 0\n */\n maxRetries?: number\n\n /**\n * Delay in milliseconds between retry attempts.\n * Uses exponential backoff: delay * 2^(attempt - 1)\n * @default 1000\n */\n retryDelay?: number\n}\n\n/** Extended component with retry capability */\nexport interface LazyComponent<TProps extends Record<string, unknown>> extends Component<TProps> {\n /**\n * Reset the lazy component state, allowing it to retry loading.\n * Useful when used with ErrorBoundary reset functionality.\n */\n reset: () => void\n\n /**\n * Preload the component without rendering it.\n * Returns a promise that resolves when the component is loaded.\n */\n preload: () => Promise<void>\n}\n\n/**\n * Create a lazy component that suspends while loading.\n *\n * @param loader - Function that returns a promise resolving to the component module\n * @param options - Optional configuration for retry behavior\n * @returns A component that suspends during loading and supports retry on failure\n *\n * @example\n * ```tsx\n * import { lazy, Suspense } from 'fict'\n *\n * // Basic usage\n * const LazyChart = lazy(() => import('./Chart'))\n *\n * // With retry options\n * const LazyDashboard = lazy(() => import('./Dashboard'), {\n * maxRetries: 3,\n * retryDelay: 1000\n * })\n *\n * function App() {\n * return (\n * <Suspense fallback={<Loading />}>\n * <LazyChart />\n * </Suspense>\n * )\n * }\n *\n * // Reset on error (with ErrorBoundary)\n * <ErrorBoundary fallback={(err, reset) => (\n * <button onClick={() => { LazyChart.reset(); reset(); }}>Retry</button>\n * )}>\n * <LazyChart />\n * </ErrorBoundary>\n * ```\n *\n * @public\n */\nexport function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(\n loader: () => Promise<LazyModule<TProps> | { default: Component<TProps> }>,\n options: LazyOptions = {},\n): LazyComponent<TProps> {\n const { maxRetries = 0, retryDelay = 1000 } = options\n\n let loaded: Component<TProps> | null = null\n let loadError: unknown = null\n let loadingPromise: Promise<unknown> | null = null\n let pendingToken: ReturnType<typeof createSuspenseToken> | null = null\n let retryCount = 0\n\n const attemptLoad = (): Promise<void> => {\n return loader()\n .then(mod => {\n loaded = (mod as LazyModule<TProps>).default\n loadError = null\n retryCount = 0\n pendingToken?.resolve()\n })\n .catch((err: unknown) => {\n if (retryCount < maxRetries) {\n retryCount++\n const delay = retryDelay * Math.pow(2, retryCount - 1)\n return new Promise<void>(resolve => {\n setTimeout(() => {\n resolve(attemptLoad())\n }, delay)\n })\n }\n loadError = err\n pendingToken?.reject(err)\n return undefined\n })\n .finally(() => {\n loadingPromise = null\n pendingToken = null\n })\n }\n\n const component = ((props: TProps) => {\n if (loaded) {\n return loaded(props)\n }\n if (loadError) {\n throw loadError\n }\n if (!loadingPromise) {\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n }\n if (pendingToken) {\n throw pendingToken.token\n }\n // Should never hit if pendingToken exists, but fallback for type safety.\n throw new Error('Lazy component failed to start loading')\n }) as LazyComponent<TProps>\n\n /**\n * Reset the lazy component state, clearing any cached error.\n * Call this before triggering a re-render to retry loading.\n */\n component.reset = () => {\n loadError = null\n loadingPromise = null\n pendingToken = null\n retryCount = 0\n // Note: we don't clear `loaded` - if it was successfully loaded, keep it\n }\n\n /**\n * Preload the component without rendering.\n * Useful for eager loading on route prefetch.\n */\n component.preload = (): Promise<void> => {\n if (loaded) {\n return Promise.resolve()\n }\n if (loadingPromise) {\n return loadingPromise as Promise<void>\n }\n pendingToken = createSuspenseToken()\n loadingPromise = attemptLoad()\n return loadingPromise as Promise<void>\n }\n\n return component\n}\n"]}
|
package/dist/slim.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/slim.ts
|
|
4
|
+
function $state(_initialValue) {
|
|
5
|
+
throw new Error("$state() is a compiler macro and should be transformed at compile time");
|
|
6
|
+
}
|
|
7
|
+
function $effect(_fn) {
|
|
8
|
+
throw new Error("$effect() is a compiler macro and should be transformed at compile time");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.$effect = $effect;
|
|
12
|
+
exports.$state = $state;
|
|
13
|
+
//# sourceMappingURL=slim.cjs.map
|
|
14
|
+
//# sourceMappingURL=slim.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/slim.ts"],"names":[],"mappings":";;;AAcO,SAAS,OAAU,aAAA,EAAqB;AAC7C,EAAA,MAAM,IAAI,MAAM,wEAAwE,CAAA;AAC1F;AAMO,SAAS,QAAQ,GAAA,EAAsC;AAC5D,EAAA,MAAM,IAAI,MAAM,yEAAyE,CAAA;AAC3F","file":"slim.cjs","sourcesContent":["/**\n * @fileoverview Fict Slim entrypoint\n *\n * Exposes compiler macros only. Intended for users who want the smallest\n * runtime surface and rely on the compiler to erase macro calls.\n *\n * @public\n * @packageDocumentation\n */\n\n/**\n * Compiler macro for reactive state.\n * This is transformed at compile time and should never be called at runtime.\n */\nexport function $state<T>(_initialValue: T): T {\n throw new Error('$state() is a compiler macro and should be transformed at compile time')\n}\n\n/**\n * Compiler macro for reactive effects.\n * This is transformed at compile time and should never be called at runtime.\n */\nexport function $effect(_fn: () => void | (() => void)): void {\n throw new Error('$effect() is a compiler macro and should be transformed at compile time')\n}\n"]}
|
package/dist/slim.d.cts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fict Slim entrypoint
|
|
3
|
+
*
|
|
4
|
+
* Exposes compiler macros only. Intended for users who want the smallest
|
|
5
|
+
* runtime surface and rely on the compiler to erase macro calls.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Compiler macro for reactive state.
|
|
12
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
13
|
+
*/
|
|
14
|
+
declare function $state<T>(_initialValue: T): T;
|
|
15
|
+
/**
|
|
16
|
+
* Compiler macro for reactive effects.
|
|
17
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
18
|
+
*/
|
|
19
|
+
declare function $effect(_fn: () => void | (() => void)): void;
|
|
20
|
+
|
|
21
|
+
export { $effect, $state };
|
package/dist/slim.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fict Slim entrypoint
|
|
3
|
+
*
|
|
4
|
+
* Exposes compiler macros only. Intended for users who want the smallest
|
|
5
|
+
* runtime surface and rely on the compiler to erase macro calls.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Compiler macro for reactive state.
|
|
12
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
13
|
+
*/
|
|
14
|
+
declare function $state<T>(_initialValue: T): T;
|
|
15
|
+
/**
|
|
16
|
+
* Compiler macro for reactive effects.
|
|
17
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
18
|
+
*/
|
|
19
|
+
declare function $effect(_fn: () => void | (() => void)): void;
|
|
20
|
+
|
|
21
|
+
export { $effect, $state };
|
package/dist/slim.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/slim.ts
|
|
2
|
+
function $state(_initialValue) {
|
|
3
|
+
throw new Error("$state() is a compiler macro and should be transformed at compile time");
|
|
4
|
+
}
|
|
5
|
+
function $effect(_fn) {
|
|
6
|
+
throw new Error("$effect() is a compiler macro and should be transformed at compile time");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { $effect, $state };
|
|
10
|
+
//# sourceMappingURL=slim.js.map
|
|
11
|
+
//# sourceMappingURL=slim.js.map
|
package/dist/slim.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/slim.ts"],"names":[],"mappings":";AAcO,SAAS,OAAU,aAAA,EAAqB;AAC7C,EAAA,MAAM,IAAI,MAAM,wEAAwE,CAAA;AAC1F;AAMO,SAAS,QAAQ,GAAA,EAAsC;AAC5D,EAAA,MAAM,IAAI,MAAM,yEAAyE,CAAA;AAC3F","file":"slim.js","sourcesContent":["/**\n * @fileoverview Fict Slim entrypoint\n *\n * Exposes compiler macros only. Intended for users who want the smallest\n * runtime surface and rely on the compiler to erase macro calls.\n *\n * @public\n * @packageDocumentation\n */\n\n/**\n * Compiler macro for reactive state.\n * This is transformed at compile time and should never be called at runtime.\n */\nexport function $state<T>(_initialValue: T): T {\n throw new Error('$state() is a compiler macro and should be transformed at compile time')\n}\n\n/**\n * Compiler macro for reactive effects.\n * This is transformed at compile time and should never be called at runtime.\n */\nexport function $effect(_fn: () => void | (() => void)): void {\n throw new Error('$effect() is a compiler macro and should be transformed at compile time')\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fict",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Main package for the Fict framework",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
"types": "./dist/advanced.d.ts",
|
|
49
49
|
"import": "./dist/advanced.js",
|
|
50
50
|
"require": "./dist/advanced.cjs"
|
|
51
|
+
},
|
|
52
|
+
"./slim": {
|
|
53
|
+
"types": "./dist/slim.d.ts",
|
|
54
|
+
"import": "./dist/slim.js",
|
|
55
|
+
"require": "./dist/slim.cjs"
|
|
51
56
|
}
|
|
52
57
|
},
|
|
53
58
|
"sideEffects": false,
|
|
@@ -56,12 +61,12 @@
|
|
|
56
61
|
"src"
|
|
57
62
|
],
|
|
58
63
|
"dependencies": {
|
|
59
|
-
"@fictjs/runtime": "0.2.
|
|
64
|
+
"@fictjs/runtime": "0.2.2"
|
|
60
65
|
},
|
|
61
66
|
"devDependencies": {
|
|
62
67
|
"tsup": "^8.5.1",
|
|
63
68
|
"vite": "^7.3.0",
|
|
64
|
-
"@fictjs/vite-plugin": "0.2.
|
|
69
|
+
"@fictjs/vite-plugin": "0.2.2"
|
|
65
70
|
},
|
|
66
71
|
"scripts": {
|
|
67
72
|
"build": "tsup",
|
package/src/resource.ts
CHANGED
|
@@ -128,6 +128,19 @@ export interface Resource<T, Args> {
|
|
|
128
128
|
* @param keyOverride - Optional cache key override
|
|
129
129
|
*/
|
|
130
130
|
prefetch(args: Args, keyOverride?: unknown): void
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Optimistically update cached data for a given args/key.
|
|
134
|
+
*
|
|
135
|
+
* @param argsAccessor - Arguments or a getter returning arguments
|
|
136
|
+
* @param value - New value or updater function
|
|
137
|
+
* @param options - Optional settings (key override, revalidate)
|
|
138
|
+
*/
|
|
139
|
+
mutate(
|
|
140
|
+
argsAccessor: (() => Args) | Args,
|
|
141
|
+
value: T | ((prev: T | undefined) => T),
|
|
142
|
+
options?: { key?: unknown; revalidate?: boolean },
|
|
143
|
+
): void
|
|
131
144
|
}
|
|
132
145
|
|
|
133
146
|
/**
|
|
@@ -171,6 +184,8 @@ interface ResourceEntry<T, Args> {
|
|
|
171
184
|
expiresAt: number | undefined
|
|
172
185
|
/** Currently in-flight fetch promise */
|
|
173
186
|
inFlight: Promise<void> | undefined
|
|
187
|
+
/** Args used for the current in-flight request */
|
|
188
|
+
inFlightArgs: Args | undefined
|
|
174
189
|
/** AbortController for cancelling in-flight requests */
|
|
175
190
|
controller: AbortController | undefined
|
|
176
191
|
}
|
|
@@ -274,6 +289,7 @@ export function resource<T, Args = void>(
|
|
|
274
289
|
generation: 0,
|
|
275
290
|
expiresAt: undefined,
|
|
276
291
|
inFlight: undefined,
|
|
292
|
+
inFlightArgs: undefined,
|
|
277
293
|
controller: undefined,
|
|
278
294
|
}
|
|
279
295
|
cache.set(key, state)
|
|
@@ -304,6 +320,9 @@ export function resource<T, Args = void>(
|
|
|
304
320
|
args: Args,
|
|
305
321
|
isRevalidating = false,
|
|
306
322
|
) => {
|
|
323
|
+
if (entry.inFlight && entry.inFlightArgs === args) {
|
|
324
|
+
return
|
|
325
|
+
}
|
|
307
326
|
entry.controller?.abort()
|
|
308
327
|
entry.inFlight = undefined
|
|
309
328
|
const controller = new AbortController()
|
|
@@ -351,10 +370,12 @@ export function resource<T, Args = void>(
|
|
|
351
370
|
})
|
|
352
371
|
.finally(() => {
|
|
353
372
|
entry.inFlight = undefined
|
|
373
|
+
entry.inFlightArgs = undefined
|
|
354
374
|
entry.controller = undefined
|
|
355
375
|
})
|
|
356
376
|
|
|
357
377
|
entry.inFlight = fetchPromise
|
|
378
|
+
entry.inFlightArgs = args
|
|
358
379
|
|
|
359
380
|
onCleanup(() => {
|
|
360
381
|
if (resolvedCacheOptions.mode === 'none') {
|
|
@@ -394,6 +415,42 @@ export function resource<T, Args = void>(
|
|
|
394
415
|
}
|
|
395
416
|
}
|
|
396
417
|
|
|
418
|
+
const mutate = (
|
|
419
|
+
argsAccessor: (() => Args) | Args,
|
|
420
|
+
value: T | ((prev: T | undefined) => T),
|
|
421
|
+
options?: { key?: unknown; revalidate?: boolean },
|
|
422
|
+
) => {
|
|
423
|
+
const args = readArgs(argsAccessor)
|
|
424
|
+
const key = options?.key ?? computeKey(args)
|
|
425
|
+
const entry = ensureEntry(key)
|
|
426
|
+
const prevValue = entry.data()
|
|
427
|
+
const nextValue =
|
|
428
|
+
typeof value === 'function' ? (value as (prev: T | undefined) => T)(prevValue) : value
|
|
429
|
+
|
|
430
|
+
entry.controller?.abort()
|
|
431
|
+
entry.inFlight = undefined
|
|
432
|
+
entry.inFlightArgs = undefined
|
|
433
|
+
entry.generation += 1
|
|
434
|
+
|
|
435
|
+
entry.data(nextValue)
|
|
436
|
+
entry.hasValue = true
|
|
437
|
+
entry.status = 'success'
|
|
438
|
+
entry.loading(false)
|
|
439
|
+
entry.error(undefined)
|
|
440
|
+
markExpiry(entry)
|
|
441
|
+
entry.lastArgs = args
|
|
442
|
+
entry.lastVersion = entry.version()
|
|
443
|
+
|
|
444
|
+
if (entry.pendingToken) {
|
|
445
|
+
entry.pendingToken.resolve()
|
|
446
|
+
entry.pendingToken = null
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (options?.revalidate) {
|
|
450
|
+
entry.version(entry.version() + 1)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
397
454
|
return {
|
|
398
455
|
read(argsAccessor: (() => Args) | Args): ResourceResult<T> {
|
|
399
456
|
const entryRef = createSignal<ResourceEntry<T, Args> | null>(null)
|
|
@@ -466,5 +523,6 @@ export function resource<T, Args = void>(
|
|
|
466
523
|
},
|
|
467
524
|
invalidate,
|
|
468
525
|
prefetch,
|
|
526
|
+
mutate,
|
|
469
527
|
}
|
|
470
528
|
}
|
package/src/slim.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fict Slim entrypoint
|
|
3
|
+
*
|
|
4
|
+
* Exposes compiler macros only. Intended for users who want the smallest
|
|
5
|
+
* runtime surface and rely on the compiler to erase macro calls.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compiler macro for reactive state.
|
|
13
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export function $state<T>(_initialValue: T): T {
|
|
16
|
+
throw new Error('$state() is a compiler macro and should be transformed at compile time')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compiler macro for reactive effects.
|
|
21
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
22
|
+
*/
|
|
23
|
+
export function $effect(_fn: () => void | (() => void)): void {
|
|
24
|
+
throw new Error('$effect() is a compiler macro and should be transformed at compile time')
|
|
25
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -134,6 +134,15 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
134
134
|
boundMethods.set(prop, { ref: currentValue as AnyFn, bound })
|
|
135
135
|
return bound
|
|
136
136
|
}
|
|
137
|
+
{
|
|
138
|
+
const boundMethods = BOUND_METHOD_CACHE.get(target)
|
|
139
|
+
if (boundMethods && boundMethods.has(prop)) {
|
|
140
|
+
boundMethods.delete(prop)
|
|
141
|
+
if (boundMethods.size === 0) {
|
|
142
|
+
BOUND_METHOD_CACHE.delete(target)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
137
146
|
|
|
138
147
|
// If the value is an object/array, we recursively wrap it in a store
|
|
139
148
|
if (typeof currentValue === 'object' && currentValue !== null) {
|
|
@@ -145,6 +154,7 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
145
154
|
},
|
|
146
155
|
|
|
147
156
|
set(target, prop, newValue, receiver) {
|
|
157
|
+
const oldLength = Array.isArray(target) && prop === 'length' ? target.length : undefined
|
|
148
158
|
const oldValue = Reflect.get(target, prop, receiver)
|
|
149
159
|
const hadKey = Object.prototype.hasOwnProperty.call(target, prop)
|
|
150
160
|
|
|
@@ -159,6 +169,9 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
159
169
|
const boundMethods = BOUND_METHOD_CACHE.get(target)
|
|
160
170
|
if (boundMethods && boundMethods.has(prop)) {
|
|
161
171
|
boundMethods.delete(prop)
|
|
172
|
+
if (boundMethods.size === 0) {
|
|
173
|
+
BOUND_METHOD_CACHE.delete(target)
|
|
174
|
+
}
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
// Update the signal if it exists
|
|
@@ -182,8 +195,19 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
// If it's an array and length changed implicitly, we might need to handle it.
|
|
185
|
-
// But usually 'length' is set explicitly or handled by the runtime.
|
|
186
198
|
if (Array.isArray(target) && prop === 'length') {
|
|
199
|
+
const nextLength = target.length
|
|
200
|
+
if (typeof oldLength === 'number' && nextLength < oldLength) {
|
|
201
|
+
const signals = SIGNAL_CACHE.get(target)
|
|
202
|
+
if (signals) {
|
|
203
|
+
for (let i = nextLength; i < oldLength; i += 1) {
|
|
204
|
+
const key = String(i)
|
|
205
|
+
if (signals[key]) {
|
|
206
|
+
signals[key](undefined)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
187
211
|
triggerIteration(target)
|
|
188
212
|
}
|
|
189
213
|
|
|
@@ -204,6 +228,9 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
204
228
|
const boundMethods = BOUND_METHOD_CACHE.get(target)
|
|
205
229
|
if (boundMethods && boundMethods.has(prop)) {
|
|
206
230
|
boundMethods.delete(prop)
|
|
231
|
+
if (boundMethods.size === 0) {
|
|
232
|
+
BOUND_METHOD_CACHE.delete(target)
|
|
233
|
+
}
|
|
207
234
|
}
|
|
208
235
|
|
|
209
236
|
triggerIteration(target)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts"],"names":["signals"],"mappings":"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","file":"chunk-QPBAYI4X.js","sourcesContent":["/**\n * @fileoverview Deep reactive store implementation for Fict.\n *\n * $store creates a deeply reactive proxy that tracks property access at the path level.\n * Unlike $state (which is shallow), $store allows direct mutation of nested properties.\n *\n * @example\n * ```typescript\n * const user = $store({ name: 'Alice', address: { city: 'London' } })\n * user.address.city = 'Paris' // Fine-grained reactive update\n * ```\n */\n\nimport { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\n/** Function type for bound methods */\ntype AnyFn = (...args: unknown[]) => unknown\n\n/** Cache entry for bound methods to preserve identity */\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\n/** Type for objects with indexable properties */\ntype IndexableObject = Record<string | symbol, unknown>\n\n/** Cache of proxied objects to avoid duplicate proxies */\nconst PROXY_CACHE = new WeakMap<object, unknown>()\n\n/** Cache of signals per object property */\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\n\n/** Cache of bound methods to preserve function identity across reads */\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\n\n/** Special key for tracking iteration (Object.keys, for-in, etc.) */\nconst ITERATE_KEY = Symbol('iterate')\n\n/**\n * Get or create a signal for a specific property on a target object.\n * @internal\n */\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\n/**\n * Trigger iteration signal to notify consumers that keys have changed.\n * @internal\n */\nfunction triggerIteration(target: object): void {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\n/**\n * Create a deep reactive store using Proxy.\n *\n * Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:\n * - **Deep reactivity**: Nested objects are automatically wrapped in proxies\n * - **Direct mutation**: Modify properties directly without spread operators\n * - **Path-level tracking**: Only components reading changed paths re-render\n *\n * @param initialValue - The initial object to make reactive\n * @returns A reactive proxy of the object\n *\n * @example\n * ```tsx\n * import { $store } from 'fict'\n *\n * const form = $store({\n * user: { name: '', email: '' },\n * settings: { theme: 'light' }\n * })\n *\n * // Direct mutation works\n * form.user.name = 'Alice'\n *\n * // In JSX - only updates when form.user.name changes\n * <input value={form.user.name} />\n * ```\n *\n * @public\n */\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length(target.length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n // But usually 'length' is set explicitly or handled by the runtime.\n if (Array.isArray(target) && prop === 'length') {\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n"]}
|