fict 0.0.1 → 0.0.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/plus.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { Component } from '@fictjs/runtime';
2
+ export { startTransition, startTransition as transition, untrack, useDeferredValue, useTransition } from '@fictjs/runtime';
3
+
4
+ declare function $store<T extends object>(initialValue: T): T;
5
+
6
+ interface ResourceResult<T> {
7
+ data: T | undefined;
8
+ loading: boolean;
9
+ error: unknown;
10
+ refresh: () => void;
11
+ }
12
+ interface ResourceCacheOptions {
13
+ mode?: 'memory' | 'none';
14
+ ttlMs?: number;
15
+ staleWhileRevalidate?: boolean;
16
+ cacheErrors?: boolean;
17
+ }
18
+ interface ResourceOptions<T, Args> {
19
+ key?: unknown;
20
+ fetch: (ctx: {
21
+ signal: AbortSignal;
22
+ }, args: Args) => Promise<T>;
23
+ suspense?: boolean;
24
+ cache?: ResourceCacheOptions;
25
+ reset?: unknown | (() => unknown);
26
+ }
27
+ /**
28
+ * Creates a resource factory that can be read with arguments.
29
+ *
30
+ * @param optionsOrFetcher - Configuration object or fetcher function
31
+ */
32
+ declare function resource<T, Args = void>(optionsOrFetcher: ((ctx: {
33
+ signal: AbortSignal;
34
+ }, args: Args) => Promise<T>) | ResourceOptions<T, Args>): {
35
+ read(argsAccessor: (() => Args) | Args): ResourceResult<T>;
36
+ invalidate: (key?: unknown) => void;
37
+ prefetch: (args: Args, keyOverride?: unknown) => void;
38
+ };
39
+
40
+ interface LazyModule<TProps extends Record<string, unknown>> {
41
+ default: Component<TProps>;
42
+ }
43
+ /**
44
+ * Create a lazy component that suspends while loading.
45
+ */
46
+ declare function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(loader: () => Promise<LazyModule<TProps> | {
47
+ default: Component<TProps>;
48
+ }>): Component<TProps>;
49
+
50
+ export { $store, lazy, resource };
package/dist/plus.js ADDED
@@ -0,0 +1,358 @@
1
+ import { createSignal, createEffect, createSuspenseToken, onCleanup } from '@fictjs/runtime';
2
+ export { startTransition, startTransition as transition, untrack, useDeferredValue, useTransition } from '@fictjs/runtime';
3
+
4
+ // src/store.ts
5
+ var PROXY_CACHE = /* @__PURE__ */ new WeakMap();
6
+ var SIGNAL_CACHE = /* @__PURE__ */ new WeakMap();
7
+ var BOUND_METHOD_CACHE = /* @__PURE__ */ new WeakMap();
8
+ var ITERATE_KEY = Symbol("iterate");
9
+ function getSignal(target, prop) {
10
+ let signals = SIGNAL_CACHE.get(target);
11
+ if (!signals) {
12
+ signals = {};
13
+ SIGNAL_CACHE.set(target, signals);
14
+ }
15
+ if (!signals[prop]) {
16
+ const initial = prop === ITERATE_KEY ? 0 : target[prop];
17
+ signals[prop] = createSignal(initial);
18
+ }
19
+ return signals[prop];
20
+ }
21
+ function triggerIteration(target) {
22
+ const signals = SIGNAL_CACHE.get(target);
23
+ if (signals && signals[ITERATE_KEY]) {
24
+ const current = signals[ITERATE_KEY]();
25
+ signals[ITERATE_KEY](current + 1);
26
+ }
27
+ }
28
+ function $store(initialValue) {
29
+ if (typeof initialValue !== "object" || initialValue === null) {
30
+ return initialValue;
31
+ }
32
+ if (PROXY_CACHE.has(initialValue)) {
33
+ return PROXY_CACHE.get(initialValue);
34
+ }
35
+ const proxy = new Proxy(initialValue, {
36
+ get(target, prop, receiver) {
37
+ const signal = getSignal(target, prop);
38
+ const trackedValue = signal();
39
+ const currentValue = Reflect.get(target, prop, receiver ?? proxy);
40
+ if (currentValue !== trackedValue) {
41
+ signal(currentValue);
42
+ }
43
+ if (typeof currentValue === "function") {
44
+ let boundMethods = BOUND_METHOD_CACHE.get(target);
45
+ if (!boundMethods) {
46
+ boundMethods = /* @__PURE__ */ new Map();
47
+ BOUND_METHOD_CACHE.set(target, boundMethods);
48
+ }
49
+ const cached = boundMethods.get(prop);
50
+ if (cached && cached.ref === currentValue) {
51
+ return cached.bound;
52
+ }
53
+ const bound = currentValue.bind(receiver ?? proxy);
54
+ boundMethods.set(prop, { ref: currentValue, bound });
55
+ return bound;
56
+ }
57
+ if (typeof currentValue === "object" && currentValue !== null) {
58
+ return $store(currentValue);
59
+ }
60
+ return currentValue;
61
+ },
62
+ set(target, prop, newValue, receiver) {
63
+ const oldValue = Reflect.get(target, prop, receiver);
64
+ const hadKey = Object.prototype.hasOwnProperty.call(target, prop);
65
+ if (oldValue === newValue && hadKey) {
66
+ return true;
67
+ }
68
+ const result = Reflect.set(target, prop, newValue, receiver);
69
+ const boundMethods = BOUND_METHOD_CACHE.get(target);
70
+ if (boundMethods && boundMethods.has(prop)) {
71
+ boundMethods.delete(prop);
72
+ }
73
+ const signals = SIGNAL_CACHE.get(target);
74
+ if (signals && signals[prop]) {
75
+ signals[prop](newValue);
76
+ }
77
+ if (!hadKey) {
78
+ triggerIteration(target);
79
+ }
80
+ if (Array.isArray(target) && prop !== "length") {
81
+ const signals2 = SIGNAL_CACHE.get(target);
82
+ if (signals2 && signals2.length) {
83
+ signals2.length(target.length);
84
+ }
85
+ }
86
+ if (Array.isArray(target) && prop === "length") {
87
+ triggerIteration(target);
88
+ }
89
+ return result;
90
+ },
91
+ deleteProperty(target, prop) {
92
+ const hadKey = Object.prototype.hasOwnProperty.call(target, prop);
93
+ const result = Reflect.deleteProperty(target, prop);
94
+ if (result && hadKey) {
95
+ const signals = SIGNAL_CACHE.get(target);
96
+ if (signals && signals[prop]) {
97
+ signals[prop](void 0);
98
+ }
99
+ const boundMethods = BOUND_METHOD_CACHE.get(target);
100
+ if (boundMethods && boundMethods.has(prop)) {
101
+ boundMethods.delete(prop);
102
+ }
103
+ triggerIteration(target);
104
+ }
105
+ return result;
106
+ },
107
+ ownKeys(target) {
108
+ getSignal(target, ITERATE_KEY)();
109
+ return Reflect.ownKeys(target);
110
+ },
111
+ has(target, prop) {
112
+ getSignal(target, prop)();
113
+ return Reflect.has(target, prop);
114
+ }
115
+ });
116
+ PROXY_CACHE.set(initialValue, proxy);
117
+ return proxy;
118
+ }
119
+ var defaultCacheOptions = {
120
+ mode: "memory",
121
+ ttlMs: Number.POSITIVE_INFINITY,
122
+ staleWhileRevalidate: false,
123
+ cacheErrors: false
124
+ };
125
+ function resource(optionsOrFetcher) {
126
+ const fetcher = typeof optionsOrFetcher === "function" ? optionsOrFetcher : optionsOrFetcher.fetch;
127
+ const useSuspense = typeof optionsOrFetcher === "object" && !!optionsOrFetcher.suspense;
128
+ const cacheOptions = typeof optionsOrFetcher === "object" ? optionsOrFetcher.cache ?? {} : {};
129
+ const resolvedCacheOptions = { ...defaultCacheOptions, ...cacheOptions };
130
+ const cache = /* @__PURE__ */ new Map();
131
+ const readArgs = (argsAccessor) => typeof argsAccessor === "function" ? argsAccessor() : argsAccessor;
132
+ const computeKey = (argsAccessor) => {
133
+ const argsValue = readArgs(argsAccessor);
134
+ if (typeof optionsOrFetcher === "object" && optionsOrFetcher.key !== void 0) {
135
+ const key = optionsOrFetcher.key;
136
+ return typeof key === "function" ? key(argsValue) : key;
137
+ }
138
+ return argsValue;
139
+ };
140
+ const readResetToken = () => {
141
+ if (typeof optionsOrFetcher !== "object") return void 0;
142
+ const reset = optionsOrFetcher.reset;
143
+ if (typeof reset === "function" && reset.length === 0) {
144
+ return reset();
145
+ }
146
+ return reset;
147
+ };
148
+ const ensureEntry = (key) => {
149
+ let state = cache.get(key);
150
+ if (!state) {
151
+ state = {
152
+ data: createSignal(void 0),
153
+ loading: createSignal(false),
154
+ error: createSignal(void 0),
155
+ version: createSignal(0),
156
+ pendingToken: null,
157
+ lastArgs: void 0,
158
+ lastVersion: -1,
159
+ lastReset: void 0,
160
+ hasValue: false,
161
+ status: "idle",
162
+ generation: 0,
163
+ expiresAt: void 0,
164
+ inFlight: void 0,
165
+ controller: void 0
166
+ };
167
+ cache.set(key, state);
168
+ }
169
+ return state;
170
+ };
171
+ const isExpired = (entry) => {
172
+ if (resolvedCacheOptions.mode === "none") return true;
173
+ if (!Number.isFinite(resolvedCacheOptions.ttlMs)) return false;
174
+ if (entry.expiresAt === void 0) return false;
175
+ return entry.expiresAt < Date.now();
176
+ };
177
+ const markExpiry = (entry) => {
178
+ if (resolvedCacheOptions.mode === "none") {
179
+ entry.expiresAt = Date.now() - 1;
180
+ return;
181
+ }
182
+ entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs) ? Date.now() + resolvedCacheOptions.ttlMs : void 0;
183
+ };
184
+ const startFetch = (entry, key, args) => {
185
+ entry.controller?.abort();
186
+ entry.inFlight = void 0;
187
+ const controller = new AbortController();
188
+ entry.controller = controller;
189
+ entry.status = "pending";
190
+ entry.loading(true);
191
+ entry.error(void 0);
192
+ entry.generation += 1;
193
+ const currentGen = entry.generation;
194
+ const shouldSuspend = useSuspense && !entry.hasValue;
195
+ entry.pendingToken = shouldSuspend ? createSuspenseToken() : null;
196
+ const fetchPromise = fetcher({ signal: controller.signal }, args).then((res) => {
197
+ if (controller.signal.aborted || entry.generation !== currentGen) return;
198
+ entry.data(res);
199
+ entry.hasValue = true;
200
+ entry.status = "success";
201
+ entry.loading(false);
202
+ markExpiry(entry);
203
+ if (entry.pendingToken) {
204
+ entry.pendingToken.resolve();
205
+ entry.pendingToken = null;
206
+ }
207
+ }).catch((err) => {
208
+ if (controller.signal.aborted || entry.generation !== currentGen) return;
209
+ entry.error(err);
210
+ entry.status = "error";
211
+ entry.loading(false);
212
+ if (resolvedCacheOptions.cacheErrors) {
213
+ markExpiry(entry);
214
+ } else {
215
+ entry.expiresAt = Date.now() - 1;
216
+ entry.hasValue = false;
217
+ }
218
+ if (entry.pendingToken) {
219
+ entry.pendingToken.reject(err);
220
+ entry.pendingToken = null;
221
+ }
222
+ }).finally(() => {
223
+ entry.inFlight = void 0;
224
+ entry.controller = void 0;
225
+ });
226
+ entry.inFlight = fetchPromise;
227
+ onCleanup(() => {
228
+ if (resolvedCacheOptions.mode === "none") {
229
+ controller.abort();
230
+ cache.delete(key);
231
+ }
232
+ });
233
+ };
234
+ const invalidate = (key) => {
235
+ if (key === void 0) {
236
+ cache.forEach((entry2) => {
237
+ entry2.controller?.abort();
238
+ entry2.version(entry2.version() + 1);
239
+ entry2.expiresAt = Date.now() - 1;
240
+ });
241
+ cache.clear();
242
+ return;
243
+ }
244
+ const entry = cache.get(key);
245
+ if (entry) {
246
+ entry.controller?.abort();
247
+ entry.version(entry.version() + 1);
248
+ entry.expiresAt = Date.now() - 1;
249
+ cache.delete(key);
250
+ }
251
+ };
252
+ const prefetch = (args, keyOverride) => {
253
+ const key = keyOverride ?? computeKey(args);
254
+ const entry = ensureEntry(key);
255
+ const usableData = entry.hasValue && !isExpired(entry);
256
+ if (!usableData) {
257
+ entry.lastArgs = args;
258
+ entry.lastVersion = entry.version();
259
+ startFetch(entry, key, args);
260
+ }
261
+ };
262
+ return {
263
+ read(argsAccessor) {
264
+ const entryRef = createSignal(null);
265
+ createEffect(() => {
266
+ const key = computeKey(argsAccessor);
267
+ const entry = ensureEntry(key);
268
+ entryRef(entry);
269
+ const args = readArgs(argsAccessor);
270
+ const currentVersion = entry.version();
271
+ const expired = isExpired(entry);
272
+ const argsChanged = entry.lastArgs !== args;
273
+ const versionChanged = entry.lastVersion !== currentVersion;
274
+ const resetToken = readResetToken();
275
+ const resetChanged = entry.lastReset !== resetToken;
276
+ const shouldRefetch = expired || argsChanged || versionChanged || resetChanged || entry.status === "error" && !resolvedCacheOptions.cacheErrors;
277
+ entry.lastArgs = args;
278
+ entry.lastVersion = currentVersion;
279
+ entry.lastReset = resetToken;
280
+ if (shouldRefetch) {
281
+ if (entry.inFlight && (argsChanged || versionChanged)) {
282
+ entry.controller?.abort();
283
+ entry.inFlight = void 0;
284
+ }
285
+ if (resetChanged) {
286
+ entry.hasValue = false;
287
+ entry.expiresAt = Date.now() - 1;
288
+ }
289
+ startFetch(entry, key, args);
290
+ } else if (entry.inFlight === void 0 && resolvedCacheOptions.staleWhileRevalidate && expired && entry.hasValue) {
291
+ startFetch(entry, key, args);
292
+ }
293
+ if (resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired) {
294
+ entry.loading(true);
295
+ }
296
+ });
297
+ return {
298
+ get data() {
299
+ const entry = entryRef();
300
+ if (!entry) return void 0;
301
+ if (useSuspense && entry.pendingToken) {
302
+ throw entry.pendingToken.token;
303
+ }
304
+ return entry.data();
305
+ },
306
+ get loading() {
307
+ const entry = entryRef();
308
+ return entry ? entry.loading() : false;
309
+ },
310
+ get error() {
311
+ const entry = entryRef();
312
+ return entry ? entry.error() : void 0;
313
+ },
314
+ refresh: () => {
315
+ const entry = entryRef();
316
+ if (entry) entry.version(entry.version() + 1);
317
+ }
318
+ };
319
+ },
320
+ invalidate,
321
+ prefetch
322
+ };
323
+ }
324
+ function lazy(loader) {
325
+ let loaded = null;
326
+ let loadError = null;
327
+ let loadingPromise = null;
328
+ let pendingToken = null;
329
+ return (props) => {
330
+ if (loaded) {
331
+ return loaded(props);
332
+ }
333
+ if (loadError) {
334
+ throw loadError;
335
+ }
336
+ if (!loadingPromise) {
337
+ pendingToken = createSuspenseToken();
338
+ loadingPromise = loader().then((mod) => {
339
+ loaded = mod.default;
340
+ pendingToken?.resolve();
341
+ }).catch((err) => {
342
+ loadError = err;
343
+ pendingToken?.reject(err);
344
+ }).finally(() => {
345
+ loadingPromise = null;
346
+ pendingToken = null;
347
+ });
348
+ }
349
+ if (pendingToken) {
350
+ throw pendingToken.token;
351
+ }
352
+ throw new Error("Lazy component failed to start loading");
353
+ };
354
+ }
355
+
356
+ export { $store, lazy, resource };
357
+ //# sourceMappingURL=plus.js.map
358
+ //# sourceMappingURL=plus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/resource.ts","../src/lazy.ts"],"names":["signals","createSignal","entry","createSuspenseToken"],"mappings":"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","file":"plus.js","sourcesContent":["import { createSignal, type Signal } from '@fictjs/runtime'\n\ntype AnyFn = (...args: unknown[]) => unknown\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\nconst PROXY_CACHE = new WeakMap<object, unknown>()\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\nconst ITERATE_KEY = Symbol('iterate')\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 Record<string | symbol, unknown>)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\nfunction triggerIteration(target: object) {\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\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 as unknown as { length: number }).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","import { createSignal, createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'\n\nexport interface ResourceResult<T> {\n data: T | undefined\n loading: boolean\n error: unknown\n refresh: () => void\n}\n\nexport interface ResourceCacheOptions {\n mode?: 'memory' | 'none'\n ttlMs?: number\n staleWhileRevalidate?: boolean\n cacheErrors?: boolean\n}\n\nexport interface ResourceOptions<T, Args> {\n key?: unknown\n fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>\n suspense?: boolean\n cache?: ResourceCacheOptions\n reset?: unknown | (() => unknown)\n}\n\ninterface ResourceEntry<T, Args> {\n data: ReturnType<typeof createSignal<T | undefined>>\n loading: ReturnType<typeof createSignal<boolean>>\n error: ReturnType<typeof createSignal<unknown>>\n version: ReturnType<typeof createSignal<number>>\n pendingToken: ReturnType<typeof createSuspenseToken> | null\n lastArgs: Args | undefined\n lastVersion: number\n lastReset: unknown\n hasValue: boolean\n status: 'idle' | 'pending' | 'success' | 'error'\n generation: number\n expiresAt: number | undefined\n inFlight: Promise<void> | undefined\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 * Creates a resource factory that can be read with arguments.\n *\n * @param optionsOrFetcher - Configuration object or fetcher function\n */\nexport function resource<T, Args = void>(\n optionsOrFetcher:\n | ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)\n | ResourceOptions<T, Args>,\n) {\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 = (entry: ResourceEntry<T, Args>, key: unknown, args: Args) => {\n entry.controller?.abort()\n entry.inFlight = undefined\n const controller = new AbortController()\n entry.controller = controller\n entry.status = 'pending'\n entry.loading(true)\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 const shouldRefetch =\n expired ||\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 (\n entry.inFlight === undefined &&\n resolvedCacheOptions.staleWhileRevalidate &&\n expired &&\n entry.hasValue\n ) {\n // stale-while-revalidate: return stale data but refresh.\n startFetch(entry, key, args as Args)\n }\n\n if (resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired) {\n entry.loading(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","import { createSuspenseToken } from '@fictjs/runtime'\nimport type { Component } from '@fictjs/runtime'\n\nexport interface LazyModule<TProps extends Record<string, unknown>> {\n default: Component<TProps>\n}\n\n/**\n * Create a lazy component that suspends while loading.\n */\nexport function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(\n loader: () => Promise<LazyModule<TProps> | { default: Component<TProps> }>,\n): Component<TProps> {\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\n return props => {\n if (loaded) {\n return loaded(props)\n }\n if (loadError) {\n throw loadError\n }\n if (!loadingPromise) {\n pendingToken = createSuspenseToken()\n loadingPromise = loader()\n .then(mod => {\n loaded = (mod as LazyModule<TProps>).default\n pendingToken?.resolve()\n })\n .catch(err => {\n loadError = err\n pendingToken?.reject(err)\n })\n .finally(() => {\n loadingPromise = null\n pendingToken = null\n })\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 }\n}\n"]}
package/dist/slim.cjs ADDED
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ var slim = require('@fictjs/runtime/slim');
4
+
5
+
6
+
7
+ Object.keys(slim).forEach(function (k) {
8
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
9
+ enumerable: true,
10
+ get: function () { return slim[k]; }
11
+ });
12
+ });
13
+ //# sourceMappingURL=slim.cjs.map
14
+ //# sourceMappingURL=slim.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"slim.cjs","sourcesContent":[]}
@@ -0,0 +1 @@
1
+ export * from '@fictjs/runtime/slim';
package/dist/slim.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from '@fictjs/runtime/slim';
package/dist/slim.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from '@fictjs/runtime/slim';
2
+ //# sourceMappingURL=slim.js.map
3
+ //# sourceMappingURL=slim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"slim.js","sourcesContent":[]}
package/package.json CHANGED
@@ -1,11 +1,71 @@
1
1
  {
2
2
  "name": "fict",
3
- "version": "0.0.1",
4
- "main": "index.js",
3
+ "version": "0.0.2",
4
+ "description": "Main package for the Fict framework",
5
+ "keywords": [
6
+ "fict",
7
+ "framework",
8
+ "ui",
9
+ "reactive"
10
+ ],
11
+ "author": "Michael Lin",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/fictjs/fict.git",
16
+ "directory": "packages/fict"
17
+ },
18
+ "type": "module",
19
+ "main": "dist/index.cjs",
20
+ "module": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/index.cjs"
27
+ },
28
+ "./slim": {
29
+ "types": "./dist/slim.d.ts",
30
+ "import": "./dist/slim.js",
31
+ "require": "./dist/slim.cjs"
32
+ },
33
+ "./jsx-runtime": {
34
+ "types": "./dist/jsx-runtime.d.ts",
35
+ "import": "./dist/jsx-runtime.js",
36
+ "require": "./dist/jsx-runtime.cjs"
37
+ },
38
+ "./jsx-dev-runtime": {
39
+ "types": "./dist/jsx-dev-runtime.d.ts",
40
+ "import": "./dist/jsx-dev-runtime.js",
41
+ "require": "./dist/jsx-dev-runtime.cjs"
42
+ },
43
+ "./plus": {
44
+ "types": "./dist/plus.d.ts",
45
+ "import": "./dist/plus.js",
46
+ "require": "./dist/plus.cjs"
47
+ }
48
+ },
49
+ "sideEffects": false,
50
+ "files": [
51
+ "dist",
52
+ "src"
53
+ ],
5
54
  "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
55
+ "build": "tsup",
56
+ "dev": "tsup --watch",
57
+ "lint": "eslint src",
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "vitest run",
60
+ "test:e2e": "playwright test --reporter=list",
61
+ "clean": "rm -rf dist"
62
+ },
63
+ "dependencies": {
64
+ "@fictjs/runtime": "workspace:*",
65
+ "@fictjs/vite-plugin": "workspace:*"
7
66
  },
8
- "author": "unadlib",
9
- "license": "ISC",
10
- "description": ""
67
+ "devDependencies": {
68
+ "tsup": "^8.5.1",
69
+ "vite": "^7.2.7"
70
+ }
11
71
  }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from '@fictjs/runtime'
@@ -0,0 +1 @@
1
+ export * from '@fictjs/runtime/jsx-dev-runtime'
@@ -0,0 +1 @@
1
+ export * from '@fictjs/runtime/jsx-runtime'
package/src/lazy.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { createSuspenseToken } from '@fictjs/runtime'
2
+ import type { Component } from '@fictjs/runtime'
3
+
4
+ export interface LazyModule<TProps extends Record<string, unknown>> {
5
+ default: Component<TProps>
6
+ }
7
+
8
+ /**
9
+ * Create a lazy component that suspends while loading.
10
+ */
11
+ export function lazy<TProps extends Record<string, unknown> = Record<string, unknown>>(
12
+ loader: () => Promise<LazyModule<TProps> | { default: Component<TProps> }>,
13
+ ): Component<TProps> {
14
+ let loaded: Component<TProps> | null = null
15
+ let loadError: unknown = null
16
+ let loadingPromise: Promise<unknown> | null = null
17
+ let pendingToken: ReturnType<typeof createSuspenseToken> | null = null
18
+
19
+ return props => {
20
+ if (loaded) {
21
+ return loaded(props)
22
+ }
23
+ if (loadError) {
24
+ throw loadError
25
+ }
26
+ if (!loadingPromise) {
27
+ pendingToken = createSuspenseToken()
28
+ loadingPromise = loader()
29
+ .then(mod => {
30
+ loaded = (mod as LazyModule<TProps>).default
31
+ pendingToken?.resolve()
32
+ })
33
+ .catch(err => {
34
+ loadError = err
35
+ pendingToken?.reject(err)
36
+ })
37
+ .finally(() => {
38
+ loadingPromise = null
39
+ pendingToken = null
40
+ })
41
+ }
42
+ if (pendingToken) {
43
+ throw pendingToken.token
44
+ }
45
+ // Should never hit if pendingToken exists, but fallback for type safety.
46
+ throw new Error('Lazy component failed to start loading')
47
+ }
48
+ }
package/src/plus.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { $store } from './store'
2
+ export { resource } from './resource'
3
+ export { lazy } from './lazy'
4
+ export {
5
+ untrack,
6
+ startTransition,
7
+ useTransition,
8
+ useDeferredValue,
9
+ startTransition as transition,
10
+ } from '@fictjs/runtime'