valync 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Valync
2
+
3
+ **A lightweight, framework-agnostic async data handling library for React & Vue, inspired by Riverpod’s AsyncValue pattern and powered by ts-results-es.**
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Unified async state with `AsyncLoading`, `AsyncError`, and `AsyncData` wrapping `Option<T>`
10
+ - Built-in caching with auto revalidation & manual refresh
11
+ - Supports React & Vue with idiomatic hooks/composables
12
+ - Reactive `watch` dependencies to refetch on data change
13
+ - `setData()` for manual or partial UI updates
14
+ - Uses a strict API response shape for uniform error & data handling
15
+
16
+ ---
17
+
18
+ ## Installation
19
+
20
+ #### React
21
+ ```bash
22
+ pnpm add @epikoder/valync-react @epikoder/valync-core ts-results-es
23
+ ```
24
+
25
+ #### Vue 3
26
+ ```bash
27
+ pnpm add @epikoder/valync-vue @epikoder/valync-core ts-results-es
28
+ ```
29
+
30
+ ---
31
+
32
+ ## API Overview
33
+
34
+ ### `useValync<T>(key, options?)`
35
+
36
+ A hook/composable to fetch and manage async data.
37
+
38
+ - `key: string | Record<string, any>` — Unique cache key or URL.
39
+ - `options?`:
40
+
41
+ ```ts
42
+ {
43
+ cache?: boolean; // default true, enable/disable caching
44
+ fetchOnMount?: boolean; // default true, fetch automatically on mount
45
+ retryCount?: number; // retry count for failed requests
46
+ onData?: (data: T) => T; // transform data before setting
47
+ watch?: any[]; // reactive dependencies to trigger refetch
48
+ initialData?: ApiResponse<T>; // initial server-side data for hydration
49
+ }
50
+ ```
51
+ ---
52
+
53
+ ### Return tuple
54
+
55
+ ```ts
56
+ const [state, refetch, setData] = useValync<T>(key, options);
57
+ ```
58
+
59
+ ## AsyncValue States
60
+
61
+ ```ts
62
+ AsyncLoading<T>;
63
+ AsyncError<T>; // contains error { name, message, code? }
64
+ AsyncData<T>; // contains Option<T>: Some(value) or None
65
+ ```
66
+ ---
67
+
68
+ ## Example usage
69
+
70
+ ### React
71
+
72
+ ```tsx
73
+ import { useValync, AsyncValue } from "@epikoder/valync-react";
74
+
75
+ function UserProfile({ userId }: { userId: string }) {
76
+ const [state, refetch] = useValync<{ name: string; age: number }>(
77
+ `/api/user/${userId}`,
78
+ {
79
+ fetchOnMount: true,
80
+ retryCount: 2,
81
+ },
82
+ );
83
+
84
+ return state.when({
85
+ loading: () => <div>Loading...</div>,
86
+ error: (err) => <div>Error: {err.message}</div>,
87
+ data: (opt) =>
88
+ opt.some ? (
89
+ <div>
90
+ {opt.val.name} ({opt.val.age} years old)
91
+ </div>
92
+ ) : (
93
+ <div>No data available</div>
94
+ ),
95
+ });
96
+ }
97
+ ```
98
+
99
+ ### Vue 3
100
+
101
+ ```tsx
102
+ import { useValync, AsyncValue } from "@epikoder/valync-vue";
103
+ import { computed } from "vue";
104
+
105
+ export default {
106
+ setup() {
107
+ const [state, refetch] = useValync("/api/user/123", {
108
+ fetchOnMount: true,
109
+ });
110
+
111
+ const userDisplay = computed(() =>
112
+ state.value.when({
113
+ loading: () => "Loading...",
114
+ error: (e) => `Error: ${e.message}`,
115
+ data: (opt) => (opt.some ? `${opt.val.name}` : "No user"),
116
+ }),
117
+ );
118
+
119
+ return { userDisplay, refetch };
120
+ },
121
+ };
122
+ ```
@@ -0,0 +1,50 @@
1
+ import { Option } from "ts-results-es";
2
+ export type CacheKey = string | Record<string, any>;
3
+ export declare function normalizeKey(key: CacheKey): string;
4
+ export type ApiResponse<T> = {
5
+ status: "success";
6
+ data: T;
7
+ } | {
8
+ status: "failed";
9
+ error: {
10
+ name: string;
11
+ message: string;
12
+ code?: number | string;
13
+ };
14
+ };
15
+ export declare abstract class AsyncValue<T> {
16
+ abstract when<R>(handlers: {
17
+ loading: () => R;
18
+ error: (err: {
19
+ name: string;
20
+ message: string;
21
+ code?: number;
22
+ }) => R;
23
+ data: (value: Option<T>) => R;
24
+ }): R;
25
+ isLoading(): boolean;
26
+ isData(): boolean;
27
+ isError(): boolean;
28
+ }
29
+ export declare class AsyncLoading<T> extends AsyncValue<T> {
30
+ when<R>(h: any): R;
31
+ }
32
+ export declare class AsyncError<T> extends AsyncValue<T> {
33
+ error: {
34
+ name: string;
35
+ message: string;
36
+ code?: string | number;
37
+ };
38
+ constructor(error: {
39
+ name: string;
40
+ message: string;
41
+ code?: string | number;
42
+ });
43
+ when<R>(h: any): R;
44
+ }
45
+ export declare class AsyncData<T> extends AsyncValue<T> {
46
+ value: Option<T>;
47
+ constructor(value: Option<T>);
48
+ when<R>(h: any): R;
49
+ }
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAc,MAAM,eAAe,CAAC;AAGnD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAGpD,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAElD;AAGD,MAAM,MAAM,WAAW,CAAC,CAAC,IACnB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC9B;IACI,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACpE,CAAC;AAGR,8BAAsB,UAAU,CAAC,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC,CAAC;QACjB,KAAK,EAAE,CAAC,GAAG,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,CAAC,CAAC;QACpE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;KACjC,GAAG,CAAC;IAEL,SAAS;IAIT,MAAM;IAIN,OAAO;CAGV;AAGD,qBAAa,YAAY,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC;CAGrB;AAGD,qBAAa,UAAU,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IAEjC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE;gBAAhE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE;IAI3E,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC;CAGrB;AAGD,qBAAa,SAAS,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;gBAAhB,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAGnC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC;CAGrB"}
@@ -0,0 +1,43 @@
1
+ // Normalize cache key (convert object to stable string)
2
+ export function normalizeKey(key) {
3
+ return typeof key === "string" ? key : JSON.stringify(key);
4
+ }
5
+ // Base abstract class for async state
6
+ export class AsyncValue {
7
+ isLoading() {
8
+ return this instanceof AsyncLoading;
9
+ }
10
+ isData() {
11
+ return this instanceof AsyncData;
12
+ }
13
+ isError() {
14
+ return this instanceof AsyncError;
15
+ }
16
+ }
17
+ // Represents loading state
18
+ export class AsyncLoading extends AsyncValue {
19
+ when(h) {
20
+ return h.loading();
21
+ }
22
+ }
23
+ // Represents failed state with error
24
+ export class AsyncError extends AsyncValue {
25
+ constructor(error) {
26
+ super();
27
+ this.error = error;
28
+ }
29
+ when(h) {
30
+ return h.error(this.error);
31
+ }
32
+ }
33
+ // Represents data state—with Some(T) or None
34
+ export class AsyncData extends AsyncValue {
35
+ constructor(value) {
36
+ super();
37
+ this.value = value;
38
+ }
39
+ when(h) {
40
+ return h.data(this.value);
41
+ }
42
+ }
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAMA,wDAAwD;AACxD,MAAM,UAAU,YAAY,CAAC,GAAa;IACtC,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AAC/D,CAAC;AAUD,sCAAsC;AACtC,MAAM,OAAgB,UAAU;IAO5B,SAAS;QACL,OAAO,IAAI,YAAY,YAAY,CAAC;IACxC,CAAC;IAED,MAAM;QACF,OAAO,IAAI,YAAY,SAAS,CAAC;IACrC,CAAC;IAED,OAAO;QACH,OAAO,IAAI,YAAY,UAAU,CAAC;IACtC,CAAC;CACJ;AAED,2BAA2B;AAC3B,MAAM,OAAO,YAAgB,SAAQ,UAAa;IAC9C,IAAI,CAAI,CAAM;QACV,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;CACJ;AAED,qCAAqC;AACrC,MAAM,OAAO,UAAc,SAAQ,UAAa;IAC5C,YACW,KAAgE;QAEvE,KAAK,EAAE,CAAC;QAFD,UAAK,GAAL,KAAK,CAA2D;IAG3E,CAAC;IACD,IAAI,CAAI,CAAM;QACV,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;CACJ;AAED,6CAA6C;AAC7C,MAAM,OAAO,SAAa,SAAQ,UAAa;IAC3C,YAAmB,KAAgB;QAC/B,KAAK,EAAE,CAAC;QADO,UAAK,GAAL,KAAK,CAAW;IAEnC,CAAC;IACD,IAAI,CAAI,CAAM;QACV,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;CACJ"}
@@ -0,0 +1,4 @@
1
+ export * from "./core";
2
+ export * from "./react";
3
+ export * from "./vue";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./core";
2
+ export * from "./react";
3
+ export * from "./vue";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { ApiResponse, AsyncValue } from "../core";
2
+ type UseAsyncOptions<T> = {
3
+ cache?: boolean;
4
+ fetchOnMount?: boolean;
5
+ retryCount?: number;
6
+ onData?: (data: T) => T;
7
+ watch?: any[];
8
+ initialData?: ApiResponse<T>;
9
+ };
10
+ export declare function useValync<T>(key: string | Record<string, any>, options?: UseAsyncOptions<T>): [AsyncValue<T>, () => void, (updater: (prev: T | null) => T) => void];
11
+ export {};
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,WAAW,EACX,UAAU,EAIb,MAAM,SAAS,CAAC;AAEjB,KAAK,eAAe,CAAC,CAAC,IAAI;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IACxB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC;AAIF,wBAAgB,SAAS,CAAC,CAAC,EACvB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,OAAO,GAAE,eAAe,CAAC,CAAC,CAAM,GACjC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,KAAK,IAAI,CAAC,CAoHvE"}
@@ -0,0 +1,118 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { Some, None } from "ts-results-es";
3
+ import { normalizeKey, AsyncLoading, AsyncError, AsyncData, } from "../core";
4
+ const cache = new Map();
5
+ export function useValync(key, options = {}) {
6
+ const keyStr = normalizeKey(key);
7
+ const controllerRef = useRef(null);
8
+ const [state, setState] = useState(() => {
9
+ if (options.initialData) {
10
+ return options.initialData.status === "success"
11
+ ? new AsyncData(Some(options.initialData.data))
12
+ : new AsyncError(options.initialData.error);
13
+ }
14
+ if (options.cache !== false && cache.has(keyStr)) {
15
+ return cache.get(keyStr);
16
+ }
17
+ return new AsyncData(None);
18
+ });
19
+ const isClient = typeof window !== "undefined" && typeof AbortController !== "undefined";
20
+ const doFetch = () => {
21
+ controllerRef.current?.abort();
22
+ const ctrl = new AbortController();
23
+ controllerRef.current = ctrl;
24
+ if (options.cache !== false && cache.has(keyStr)) {
25
+ setState(cache.get(keyStr));
26
+ return;
27
+ }
28
+ setState(new AsyncLoading());
29
+ const attempt = (tries) => {
30
+ fetch(typeof key === "string" ? key : keyStr, {
31
+ signal: ctrl.signal,
32
+ })
33
+ .then(async (resp) => {
34
+ let json;
35
+ try {
36
+ json = await resp.json();
37
+ }
38
+ catch {
39
+ return {
40
+ status: "failed",
41
+ error: {
42
+ name: "ParseError",
43
+ message: "Invalid JSON",
44
+ },
45
+ };
46
+ }
47
+ if (!resp.ok || json.status === "failed") {
48
+ return {
49
+ status: "failed",
50
+ error: json.error ?? {
51
+ name: "HttpError",
52
+ message: resp.statusText,
53
+ code: resp.status,
54
+ },
55
+ };
56
+ }
57
+ return json;
58
+ })
59
+ .then((res) => {
60
+ if (ctrl.signal.aborted)
61
+ return;
62
+ if (res.status === "failed")
63
+ setState(new AsyncError(res.error));
64
+ else {
65
+ const data = options.onData
66
+ ? options.onData(res.data)
67
+ : res.data;
68
+ const sd = new AsyncData(Some(data));
69
+ if (options.cache !== false)
70
+ cache.set(keyStr, sd);
71
+ setState(sd);
72
+ }
73
+ })
74
+ .catch((err) => {
75
+ if (ctrl.signal.aborted)
76
+ return;
77
+ if (tries > 0)
78
+ return attempt(tries - 1);
79
+ setState(new AsyncError({
80
+ name: "NetworkError",
81
+ message: err.message,
82
+ }));
83
+ });
84
+ };
85
+ attempt(options.retryCount ?? 0);
86
+ };
87
+ useEffect(() => {
88
+ if (!isClient || options.initialData)
89
+ return;
90
+ if (options.fetchOnMount !== false)
91
+ doFetch();
92
+ return () => controllerRef.current?.abort();
93
+ }, [keyStr]);
94
+ if (options.watch) {
95
+ useEffect(() => {
96
+ if (isClient)
97
+ doFetch();
98
+ }, options.watch);
99
+ }
100
+ const refetch = () => {
101
+ if (isClient)
102
+ doFetch();
103
+ };
104
+ const setData = (updater) => {
105
+ setState((prev) => {
106
+ if (!(prev instanceof AsyncData))
107
+ return prev;
108
+ const current = prev.value.isSome() ? prev.value.unwrap() : null;
109
+ const updated = updater(current);
110
+ const newData = new AsyncData(Some(updated));
111
+ if (options.cache !== false)
112
+ cache.set(keyStr, newData);
113
+ return newData;
114
+ });
115
+ };
116
+ return [state, refetch, setData];
117
+ }
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { ApiResponse, AsyncValue } from "../core";
2
+ export declare function useValync<T>(key: string | Record<string, any>, options?: {
3
+ cache?: boolean;
4
+ fetchOnMount?: boolean;
5
+ retryCount?: number;
6
+ onData?: (data: T) => T;
7
+ watch?: any[];
8
+ initialData?: ApiResponse<T>;
9
+ }): readonly [import("vue").Ref<{
10
+ when: <R>(handlers: {
11
+ loading: () => R;
12
+ error: (err: {
13
+ name: string;
14
+ message: string;
15
+ code?: number;
16
+ }) => R;
17
+ data: (value: import("ts-results-es").Option<T>) => R;
18
+ }) => R;
19
+ isLoading: () => boolean;
20
+ isData: () => boolean;
21
+ isError: () => boolean;
22
+ }, AsyncValue<T> | {
23
+ when: <R>(handlers: {
24
+ loading: () => R;
25
+ error: (err: {
26
+ name: string;
27
+ message: string;
28
+ code?: number;
29
+ }) => R;
30
+ data: (value: import("ts-results-es").Option<T>) => R;
31
+ }) => R;
32
+ isLoading: () => boolean;
33
+ isData: () => boolean;
34
+ isError: () => boolean;
35
+ }>, () => void, (updater: (prev: T | null) => T) => void];
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,WAAW,EACX,UAAU,EAIb,MAAM,SAAS,CAAC;AAIjB,wBAAgB,SAAS,CAAC,CAAC,EACvB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,OAAO,GAAE;IACL,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IACxB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAC3B;;;;;;gBASM,CAAf;;;;;;;;;;;;;gBAAe,CAAf;;;;;;;0BA2F6B,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,WAalD"}
@@ -0,0 +1,112 @@
1
+ import { ref, onMounted, watch } from "vue";
2
+ import { Some, None } from "ts-results-es";
3
+ import { normalizeKey, AsyncLoading, AsyncError, AsyncData, } from "../core";
4
+ const cache = new Map();
5
+ export function useValync(key, options = {}) {
6
+ const keyStr = normalizeKey(key);
7
+ const controller = ref();
8
+ let initialValue;
9
+ if (options.initialData) {
10
+ initialValue =
11
+ options.initialData.status === "success"
12
+ ? new AsyncData(Some(options.initialData.data))
13
+ : new AsyncError(options.initialData.error);
14
+ }
15
+ else if (options.cache !== false && cache.has(keyStr)) {
16
+ initialValue = cache.get(keyStr);
17
+ }
18
+ else {
19
+ initialValue = new AsyncData(None);
20
+ }
21
+ const state = ref(initialValue);
22
+ const isClient = typeof window !== "undefined" && typeof AbortController !== "undefined";
23
+ const doFetch = () => {
24
+ controller.value?.abort();
25
+ controller.value = new AbortController();
26
+ if (options.cache !== false && cache.has(keyStr)) {
27
+ state.value = cache.get(keyStr);
28
+ return;
29
+ }
30
+ state.value = new AsyncLoading();
31
+ const attempt = (tries) => {
32
+ fetch(typeof key === "string" ? key : keyStr, {
33
+ signal: controller.value.signal,
34
+ })
35
+ .then(async (resp) => {
36
+ let json;
37
+ try {
38
+ json = await resp.json();
39
+ }
40
+ catch {
41
+ return {
42
+ status: "failed",
43
+ error: {
44
+ name: "ParseError",
45
+ message: "Invalid JSON",
46
+ },
47
+ };
48
+ }
49
+ if (!resp.ok || json.status === "failed") {
50
+ return {
51
+ status: "failed",
52
+ error: json.error ?? {
53
+ name: "HttpError",
54
+ message: resp.statusText,
55
+ code: resp.status,
56
+ },
57
+ };
58
+ }
59
+ return json;
60
+ })
61
+ .then((res) => {
62
+ if (controller.value.signal.aborted)
63
+ return;
64
+ if (res.status === "failed")
65
+ state.value = new AsyncError(res.error);
66
+ else {
67
+ const data = options.onData
68
+ ? options.onData(res.data)
69
+ : res.data;
70
+ const sd = new AsyncData(Some(data));
71
+ if (options.cache !== false)
72
+ cache.set(keyStr, sd);
73
+ state.value = sd;
74
+ }
75
+ })
76
+ .catch((err) => {
77
+ if (controller.value.signal.aborted)
78
+ return;
79
+ if (tries > 0)
80
+ return attempt(tries - 1);
81
+ state.value = new AsyncError({
82
+ name: "NetworkError",
83
+ message: err.message,
84
+ });
85
+ });
86
+ };
87
+ attempt(options.retryCount ?? 0);
88
+ };
89
+ if (isClient && options.fetchOnMount !== false && !options.initialData) {
90
+ onMounted(doFetch);
91
+ }
92
+ if (isClient && options.watch && options.watch.length > 0) {
93
+ watch(options.watch, doFetch);
94
+ }
95
+ const refetch = () => {
96
+ if (isClient)
97
+ doFetch();
98
+ };
99
+ const setData = (updater) => {
100
+ const currentVal = state.value instanceof AsyncData
101
+ ? state.value.value.isSome()
102
+ ? state.value.value.unwrap()
103
+ : null
104
+ : null;
105
+ const newVal = new AsyncData(Some(updater(currentVal)));
106
+ if (options.cache !== false)
107
+ cache.set(keyStr, newVal);
108
+ state.value = newVal;
109
+ };
110
+ return [state, refetch, setData];
111
+ }
112
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "valync",
3
+ "version": "0.1.0",
4
+ "main": "dist/core/index.js",
5
+ "types": "dist/core/index.d.ts",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/core/index.js",
9
+ "types": "./dist/core/index.d.ts"
10
+ },
11
+ "./react": {
12
+ "import": "./dist/react/index.js",
13
+ "types": "./dist/react/index.d.ts"
14
+ },
15
+ "./vue": {
16
+ "import": "./dist/vue/index.js",
17
+ "types": "./dist/vue/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "dependencies": {
24
+ "ts-results-es": "^5.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "@types/jest": "^30.0.0",
28
+ "@types/react": "^19.1.8",
29
+ "jest": "^30.0.2",
30
+ "ts-jest": "^29.4.0"
31
+ },
32
+ "peerDependencies": {
33
+ "vue": "^3.0.0",
34
+ "react": "^18.0.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc -b",
38
+ "test": "jest"
39
+ }
40
+ }