@absolutejs/absolute 0.19.0-beta.1070 → 0.19.0-beta.1072

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.
@@ -7,3 +7,5 @@ export { createTypedIsland } from './createIsland';
7
7
  export { useIslandStore } from './useIslandStore';
8
8
  export { Image } from './components/Image';
9
9
  export { StreamSlot, SuspenseSlot } from './components';
10
+ export { useResource } from './useResource';
11
+ export type { Resource, ResourceFetcher, ResourceMutator, ResourceOptions, ResourceStart } from './useResource';
@@ -0,0 +1,65 @@
1
+ import { type Ref } from 'vue';
2
+ export type ResourceFetcher<T> = (signal: AbortSignal) => Promise<T>;
3
+ /** Controls the resource's startup behavior.
4
+ *
5
+ * - `'immediate'` (default) — fire the fetcher synchronously at creation;
6
+ * `loading.value` starts `true`.
7
+ * - `'pending'` — don't fire the fetcher yet, but render as if a fetch is
8
+ * coming: `loading.value` starts `true`. Pair with a manual `refresh()`
9
+ * call from `onMounted` (or wherever the dependencies become available).
10
+ * Use this when the fetcher depends on state set after setup runs (e.g. a
11
+ * route param resolved asynchronously) — it avoids the blank-frame flash
12
+ * you'd get from `'idle'`.
13
+ * - `'idle'` — don't fire the fetcher and don't pretend you will:
14
+ * `loading.value` starts `false`. The resource is dormant until
15
+ * `refresh()` or `mutate()` is called.
16
+ */
17
+ export type ResourceStart = 'immediate' | 'pending' | 'idle';
18
+ export type ResourceOptions = {
19
+ /** When and how the fetcher fires on creation. Default: `'immediate'`. */
20
+ start?: ResourceStart;
21
+ };
22
+ export type ResourceMutator<T> = T | null | ((prev: T | null) => T | null);
23
+ export type Resource<T> = {
24
+ /** Latest resolved value, or `null` before the first successful load. */
25
+ data: Ref<T | null>;
26
+ /** Latest rejection reason, or `null` when the resource is healthy. */
27
+ error: Ref<unknown>;
28
+ /** True while a fetch is in flight, or when `start: 'pending'` was set
29
+ * and `refresh()` hasn't been called yet. */
30
+ loading: Ref<boolean>;
31
+ /** Re-runs the fetcher. Any in-flight request is aborted first. */
32
+ refresh: () => Promise<void>;
33
+ /** Aborts the in-flight request, if any. No-op otherwise. */
34
+ cancel: () => void;
35
+ /** Imperatively write the data ref without re-fetching. Accepts a new
36
+ * value or an updater function. Use after an edit action returns the new
37
+ * entity, so you avoid a wasteful re-fetch. Pending fetches are aborted
38
+ * so a slower response can't clobber the mutation. */
39
+ mutate: (next: ResourceMutator<T>) => void;
40
+ };
41
+ /** Ref-backed async data composable for AbsoluteJS Vue pages. Replaces the
42
+ * hand-rolled `onMounted(() => { loading.value = true; data.value = await
43
+ * fetch(); })` + `ref` boilerplate with a single call that also handles
44
+ * abort-on-teardown, refresh, and optimistic mutation.
45
+ *
46
+ * This is a per-component LOADER, not a cross-component cache — every call
47
+ * owns its own state and refetches on creation. For data that should survive
48
+ * navigation / be shared across components (so revisiting a route doesn't
49
+ * refetch), use a cache layer (e.g. TanStack Query) or `@absolutejs/sync`
50
+ * instead. Reach for `useResource` when a one-shot, component-scoped fetch is
51
+ * exactly what you want.
52
+ *
53
+ * ```ts
54
+ * const profile = useResource((signal) => api.profile.me.get({ signal }));
55
+ *
56
+ * // in template:
57
+ * // <Spinner v-if="profile.loading.value" />
58
+ * // <h1 v-else-if="profile.data.value">{{ profile.data.value.name }}</h1>
59
+ * ```
60
+ *
61
+ * The fetcher receives an `AbortSignal` it can pass to `fetch` — the signal
62
+ * aborts when the owning effect scope is disposed (component unmount) or on a
63
+ * new `refresh()` call. Call it during `setup()` so an effect scope is
64
+ * active; teardown won't be wired up otherwise. */
65
+ export declare const useResource: <T>(fetcher: ResourceFetcher<T>, options?: ResourceOptions) => Resource<T>;
package/dist/vue/index.js CHANGED
@@ -5237,7 +5237,74 @@ var useIslandStore = (store, selector) => {
5237
5237
  });
5238
5238
  return state;
5239
5239
  };
5240
+ // src/vue/useResource.ts
5241
+ import {
5242
+ getCurrentScope,
5243
+ onScopeDispose,
5244
+ ref as ref3,
5245
+ shallowRef
5246
+ } from "vue";
5247
+ var useResource = (fetcher, options = {}) => {
5248
+ const start = options.start ?? "immediate";
5249
+ const data = shallowRef(null);
5250
+ const error = shallowRef(null);
5251
+ const loading = ref3(start !== "idle");
5252
+ let controller = null;
5253
+ let destroyed = false;
5254
+ const cancel = () => {
5255
+ if (controller) {
5256
+ controller.abort();
5257
+ controller = null;
5258
+ }
5259
+ };
5260
+ const refresh = async () => {
5261
+ if (destroyed)
5262
+ return;
5263
+ cancel();
5264
+ const next = new AbortController;
5265
+ controller = next;
5266
+ loading.value = true;
5267
+ error.value = null;
5268
+ try {
5269
+ const result = await fetcher(next.signal);
5270
+ if (next.signal.aborted)
5271
+ return;
5272
+ data.value = result;
5273
+ } catch (cause) {
5274
+ if (next.signal.aborted)
5275
+ return;
5276
+ error.value = cause;
5277
+ } finally {
5278
+ if (controller === next) {
5279
+ controller = null;
5280
+ }
5281
+ if (!next.signal.aborted) {
5282
+ loading.value = false;
5283
+ }
5284
+ }
5285
+ };
5286
+ const mutate = (next) => {
5287
+ if (destroyed)
5288
+ return;
5289
+ cancel();
5290
+ error.value = null;
5291
+ loading.value = false;
5292
+ const resolved = typeof next === "function" ? next(data.value) : next;
5293
+ data.value = resolved;
5294
+ };
5295
+ if (getCurrentScope()) {
5296
+ onScopeDispose(() => {
5297
+ destroyed = true;
5298
+ cancel();
5299
+ });
5300
+ }
5301
+ if (start === "immediate") {
5302
+ refresh();
5303
+ }
5304
+ return { cancel, data, error, loading, mutate, refresh };
5305
+ };
5240
5306
  export {
5307
+ useResource,
5241
5308
  useIslandStore,
5242
5309
  handleVuePageRequest,
5243
5310
  defineVueSetupApp,
@@ -5250,5 +5317,5 @@ export {
5250
5317
  Image
5251
5318
  };
5252
5319
 
5253
- //# debugId=C239CE1A81D9CAA564756E2164756E21
5320
+ //# debugId=CDB2A0DB62376A8D64756E2164756E21
5254
5321
  //# sourceMappingURL=index.js.map