@pfm-platform/budgets-data-access 0.1.1
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/index.cjs +144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +297 -0
- package/dist/index.d.ts +297 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactQuery = require('@tanstack/react-query');
|
|
4
|
+
var shared = require('@pfm-platform/shared');
|
|
5
|
+
var axios = require('axios');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
10
|
+
|
|
11
|
+
// src/queries/useBudgets.ts
|
|
12
|
+
|
|
13
|
+
// src/keys.ts
|
|
14
|
+
var budgetKeys = {
|
|
15
|
+
all: ["budgets"],
|
|
16
|
+
lists: () => [...budgetKeys.all, "list"],
|
|
17
|
+
list: (userId, filters) => [...budgetKeys.lists(), userId, filters ?? {}],
|
|
18
|
+
details: () => [...budgetKeys.all, "detail"],
|
|
19
|
+
detail: (userId, budgetId) => [...budgetKeys.details(), userId, budgetId]
|
|
20
|
+
};
|
|
21
|
+
var api = axios__default.default.create({
|
|
22
|
+
baseURL: "/api",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json"
|
|
25
|
+
},
|
|
26
|
+
timeout: 1e4
|
|
27
|
+
});
|
|
28
|
+
api.interceptors.request.use(
|
|
29
|
+
(config) => {
|
|
30
|
+
const token = localStorage.getItem("auth_token");
|
|
31
|
+
if (token) {
|
|
32
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
33
|
+
}
|
|
34
|
+
return config;
|
|
35
|
+
},
|
|
36
|
+
(error) => Promise.reject(error)
|
|
37
|
+
);
|
|
38
|
+
api.interceptors.response.use(
|
|
39
|
+
(response) => response,
|
|
40
|
+
(error) => {
|
|
41
|
+
if (error.response?.status === 401) {
|
|
42
|
+
localStorage.removeItem("auth_token");
|
|
43
|
+
window.location.href = "/login";
|
|
44
|
+
}
|
|
45
|
+
return Promise.reject(error);
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// src/queries/useBudgets.ts
|
|
50
|
+
function useBudgets(params, options) {
|
|
51
|
+
const { userId, filters = {} } = params;
|
|
52
|
+
return reactQuery.useQuery({
|
|
53
|
+
queryKey: budgetKeys.list(userId, filters),
|
|
54
|
+
queryFn: async () => {
|
|
55
|
+
const queryParams = new URLSearchParams();
|
|
56
|
+
if (filters.start_date) {
|
|
57
|
+
queryParams.append("start_date", filters.start_date);
|
|
58
|
+
}
|
|
59
|
+
if (filters.end_date) {
|
|
60
|
+
queryParams.append("end_date", filters.end_date);
|
|
61
|
+
}
|
|
62
|
+
const queryString = queryParams.toString();
|
|
63
|
+
const url = `/users/${userId}/budgets${queryString ? `?${queryString}` : ""}`;
|
|
64
|
+
const response = await api.get(url);
|
|
65
|
+
return shared.BudgetsResponseSchema.parse(response.data);
|
|
66
|
+
},
|
|
67
|
+
staleTime: 1e3 * 60 * 5,
|
|
68
|
+
// 5 minutes - budgets change less frequently than transactions
|
|
69
|
+
...options
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function useBudget(params, options) {
|
|
73
|
+
const { userId, budgetId } = params;
|
|
74
|
+
return reactQuery.useQuery({
|
|
75
|
+
queryKey: budgetKeys.detail(userId, budgetId),
|
|
76
|
+
queryFn: async () => {
|
|
77
|
+
const response = await api.get(`/users/${userId}/budgets/${budgetId}`);
|
|
78
|
+
return shared.BudgetSchema.parse(response.data);
|
|
79
|
+
},
|
|
80
|
+
staleTime: 1e3 * 60 * 5,
|
|
81
|
+
// 5 minutes
|
|
82
|
+
...options
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function useCreateBudget(options) {
|
|
86
|
+
const queryClient = reactQuery.useQueryClient();
|
|
87
|
+
return reactQuery.useMutation({
|
|
88
|
+
mutationFn: async ({ userId, data }) => {
|
|
89
|
+
const validated = shared.BudgetCreateSchema.parse(data);
|
|
90
|
+
const response = await api.post(`/users/${userId}/budgets`, {
|
|
91
|
+
budget: validated
|
|
92
|
+
});
|
|
93
|
+
return shared.BudgetSchema.parse(response.data);
|
|
94
|
+
},
|
|
95
|
+
onSuccess: (_, { userId }) => {
|
|
96
|
+
queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });
|
|
97
|
+
},
|
|
98
|
+
...options
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function useUpdateBudget(options) {
|
|
102
|
+
const queryClient = reactQuery.useQueryClient();
|
|
103
|
+
return reactQuery.useMutation({
|
|
104
|
+
mutationFn: async ({ userId, budgetId, data }) => {
|
|
105
|
+
const validated = shared.BudgetUpdateSchema.parse(data);
|
|
106
|
+
const response = await api.put(`/users/${userId}/budgets/${budgetId}`, {
|
|
107
|
+
budget: validated
|
|
108
|
+
});
|
|
109
|
+
return shared.BudgetSchema.parse(response.data);
|
|
110
|
+
},
|
|
111
|
+
onSuccess: (_, { userId, budgetId }) => {
|
|
112
|
+
queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });
|
|
113
|
+
queryClient.invalidateQueries({
|
|
114
|
+
queryKey: budgetKeys.detail(userId, budgetId)
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
...options
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function useDeleteBudget(options) {
|
|
121
|
+
const queryClient = reactQuery.useQueryClient();
|
|
122
|
+
return reactQuery.useMutation({
|
|
123
|
+
mutationFn: async ({ userId, budgetId }) => {
|
|
124
|
+
await api.delete(`/users/${userId}/budgets/${budgetId}`);
|
|
125
|
+
},
|
|
126
|
+
onSuccess: (_, { userId, budgetId }) => {
|
|
127
|
+
queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });
|
|
128
|
+
queryClient.invalidateQueries({
|
|
129
|
+
queryKey: budgetKeys.detail(userId, budgetId)
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
...options
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
exports.api = api;
|
|
137
|
+
exports.budgetKeys = budgetKeys;
|
|
138
|
+
exports.useBudget = useBudget;
|
|
139
|
+
exports.useBudgets = useBudgets;
|
|
140
|
+
exports.useCreateBudget = useCreateBudget;
|
|
141
|
+
exports.useDeleteBudget = useDeleteBudget;
|
|
142
|
+
exports.useUpdateBudget = useUpdateBudget;
|
|
143
|
+
//# sourceMappingURL=index.cjs.map
|
|
144
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/keys.ts","../src/client.ts","../src/queries/useBudgets.ts","../src/queries/useBudget.ts","../src/mutations/useCreateBudget.ts","../src/mutations/useUpdateBudget.ts","../src/mutations/useDeleteBudget.ts"],"names":["axios","useQuery","BudgetsResponseSchema","BudgetSchema","useQueryClient","useMutation","BudgetCreateSchema","BudgetUpdateSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Query key factory for Budget domain\n * Hierarchical structure for efficient cache invalidation\n */\n\nexport interface BudgetDateRangeFilters {\n start_date?: string; // RFC 3339 format\n end_date?: string; // RFC 3339 format\n}\n\nexport const budgetKeys = {\n all: ['budgets'] as const,\n lists: () => [...budgetKeys.all, 'list'] as const,\n list: (userId: string, filters?: BudgetDateRangeFilters) =>\n [...budgetKeys.lists(), userId, filters ?? {}] as const,\n details: () => [...budgetKeys.all, 'detail'] as const,\n detail: (userId: string, budgetId: number) =>\n [...budgetKeys.details(), userId, budgetId] as const,\n};\n","/**\n * Axios client for Budget API calls\n * Reuses pattern from accounts/transactions data-access\n */\n\nimport axios from 'axios';\n\nexport const api = axios.create({\n baseURL: '/api',\n headers: {\n 'Content-Type': 'application/json',\n },\n timeout: 10000,\n});\n\n// Request interceptor - Add auth token\napi.interceptors.request.use(\n (config) => {\n const token = localStorage.getItem('auth_token');\n if (token) {\n config.headers.Authorization = `Bearer ${token}`;\n }\n return config;\n },\n (error) => Promise.reject(error)\n);\n\n// Response interceptor - Handle 401 unauthorized\napi.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response?.status === 401) {\n // Clear token and redirect to login\n localStorage.removeItem('auth_token');\n window.location.href = '/login';\n }\n return Promise.reject(error);\n }\n);\n","/**\n * Query hook for fetching budgets list\n * Legacy API: GET /users/{userId}/budgets\n * Optional filters: start_date, end_date (RFC 3339 format)\n */\n\nimport { useQuery, UseQueryOptions } from '@tanstack/react-query';\nimport { BudgetsResponseSchema, BudgetsResponse } from '@pfm-platform/shared';\nimport { budgetKeys, BudgetDateRangeFilters } from '../keys';\nimport { api } from '../client';\n\nexport interface UseBudgetsParams {\n userId: string;\n filters?: BudgetDateRangeFilters;\n}\n\n/**\n * Fetch budgets with optional date range filters\n * Uses Zod validation to ensure API response matches expected schema\n *\n * @param params - Query parameters with userId and optional date range filters\n * @param options - TanStack Query options\n * @returns Query result with validated budgets data\n *\n * @example\n * ```tsx\n * // Fetch all budgets\n * function BudgetsList() {\n * const { data, isLoading } = useBudgets({ userId: 'user123' });\n *\n * return (\n * <ul>\n * {data?.budgets.map(budget => (\n * <li key={budget.id}>{budget.name}</li>\n * ))}\n * </ul>\n * );\n * }\n *\n * // Fetch budgets for specific date range\n * function MonthBudgets() {\n * const { data } = useBudgets({\n * userId: 'user123',\n * filters: {\n * start_date: '2025-01-01T00:00:00Z',\n * end_date: '2025-01-31T23:59:59Z',\n * }\n * });\n *\n * return <BudgetList budgets={data?.budgets} />;\n * }\n * ```\n */\nexport function useBudgets(\n params: UseBudgetsParams,\n options?: Omit<UseQueryOptions<BudgetsResponse>, 'queryKey' | 'queryFn'>\n) {\n const { userId, filters = {} } = params;\n\n return useQuery({\n queryKey: budgetKeys.list(userId, filters),\n queryFn: async () => {\n // Build query parameters\n const queryParams = new URLSearchParams();\n\n if (filters.start_date) {\n queryParams.append('start_date', filters.start_date);\n }\n\n if (filters.end_date) {\n queryParams.append('end_date', filters.end_date);\n }\n\n const queryString = queryParams.toString();\n const url = `/users/${userId}/budgets${\n queryString ? `?${queryString}` : ''\n }`;\n\n const response = await api.get(url);\n\n // Validate response with Zod schema from Mini-Arc 1\n return BudgetsResponseSchema.parse(response.data);\n },\n staleTime: 1000 * 60 * 5, // 5 minutes - budgets change less frequently than transactions\n ...options,\n });\n}\n","/**\n * Query hook for fetching a single budget\n * Legacy API: GET /users/{userId}/budgets/{id}\n */\n\nimport { useQuery, UseQueryOptions } from '@tanstack/react-query';\nimport { BudgetSchema, Budget } from '@pfm-platform/shared';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface UseBudgetParams {\n userId: string;\n budgetId: number;\n}\n\n/**\n * Fetch a single budget by ID\n * Uses Zod validation to ensure API response matches expected schema\n *\n * @param params - Query parameters with userId and budgetId\n * @param options - TanStack Query options\n * @returns Query result with validated budget data\n *\n * @example\n * ```tsx\n * function BudgetDetail({ budgetId }: { budgetId: number }) {\n * const { data, isLoading, error } = useBudget({\n * userId: 'user123',\n * budgetId\n * });\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return (\n * <div>\n * <h1>{data.name}</h1>\n * <p>Amount: ${data.budget_amount}</p>\n * <p>Spent: ${data.spent}</p>\n * <p>State: {data.state}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport function useBudget(\n params: UseBudgetParams,\n options?: Omit<UseQueryOptions<Budget>, 'queryKey' | 'queryFn'>\n) {\n const { userId, budgetId } = params;\n\n return useQuery({\n queryKey: budgetKeys.detail(userId, budgetId),\n queryFn: async () => {\n const response = await api.get(`/users/${userId}/budgets/${budgetId}`);\n\n // Validate response with Zod schema from Mini-Arc 1\n return BudgetSchema.parse(response.data);\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n ...options,\n });\n}\n","/**\n * Mutation hook for creating a budget\n * Legacy API: POST /users/{userId}/budgets\n */\n\nimport {\n useMutation,\n UseMutationOptions,\n useQueryClient,\n} from '@tanstack/react-query';\nimport {\n BudgetCreateSchema,\n BudgetSchema,\n Budget,\n BudgetCreate,\n} from '@pfm-platform/shared';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface CreateBudgetParams {\n userId: string;\n data: BudgetCreate;\n}\n\n/**\n * Create a new budget\n * Validates input with Zod schema and invalidates related queries on success\n *\n * @param options - TanStack Mutation options\n * @returns Mutation result\n *\n * @example\n * ```tsx\n * function BudgetCreateForm() {\n * const createBudget = useCreateBudget();\n *\n * const handleSubmit = (formData) => {\n * createBudget.mutate({\n * userId: 'user123',\n * data: {\n * name: formData.name,\n * budget_amount: formData.amount,\n * tag_names: formData.tags,\n * account_list: formData.accounts,\n * show_on_dashboard: true,\n * other: {}\n * }\n * }, {\n * onSuccess: (newBudget) => {\n * toast.success('Budget created!');\n * router.push(`/budgets/${newBudget.id}`);\n * },\n * onError: (error) => {\n * toast.error(`Failed: ${error.message}`);\n * }\n * });\n * };\n *\n * return <form onSubmit={handleSubmit}>...</form>;\n * }\n * ```\n */\nexport function useCreateBudget(\n options?: Omit<UseMutationOptions<Budget, Error, CreateBudgetParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, data }: CreateBudgetParams) => {\n // Validate input with Zod schema from Mini-Arc 1\n const validated = BudgetCreateSchema.parse(data);\n\n const response = await api.post(`/users/${userId}/budgets`, {\n budget: validated,\n });\n\n // Validate response\n return BudgetSchema.parse(response.data);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate budget list queries for this user\n queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });\n },\n ...options,\n });\n}\n","/**\n * Mutation hook for updating a budget\n * Legacy API: PUT /users/{userId}/budgets/{id}\n */\n\nimport {\n useMutation,\n UseMutationOptions,\n useQueryClient,\n} from '@tanstack/react-query';\nimport {\n BudgetUpdateSchema,\n BudgetSchema,\n Budget,\n BudgetUpdate,\n} from '@pfm-platform/shared';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface UpdateBudgetParams {\n userId: string;\n budgetId: number;\n data: BudgetUpdate;\n}\n\n/**\n * Update an existing budget\n * Validates input with Zod schema and invalidates related queries on success\n *\n * @param options - TanStack Mutation options\n * @returns Mutation result\n *\n * @example\n * ```tsx\n * function BudgetEditForm({ budget }: { budget: Budget }) {\n * const updateBudget = useUpdateBudget();\n *\n * const handleSubmit = (formData) => {\n * updateBudget.mutate({\n * userId: 'user123',\n * budgetId: budget.id,\n * data: {\n * name: formData.name,\n * budget_amount: formData.amount,\n * tag_names: formData.tags,\n * account_list: formData.accounts,\n * show_on_dashboard: formData.showOnDashboard,\n * other: formData.metadata\n * }\n * }, {\n * onSuccess: () => {\n * toast.success('Budget updated!');\n * },\n * onError: (error) => {\n * toast.error(`Failed: ${error.message}`);\n * }\n * });\n * };\n *\n * return <form onSubmit={handleSubmit}>...</form>;\n * }\n * ```\n */\nexport function useUpdateBudget(\n options?: Omit<UseMutationOptions<Budget, Error, UpdateBudgetParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, budgetId, data }: UpdateBudgetParams) => {\n // Validate input with Zod schema from Mini-Arc 1\n const validated = BudgetUpdateSchema.parse(data);\n\n const response = await api.put(`/users/${userId}/budgets/${budgetId}`, {\n budget: validated,\n });\n\n // Validate response\n return BudgetSchema.parse(response.data);\n },\n onSuccess: (_, { userId, budgetId }) => {\n // Invalidate list queries\n queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });\n // Invalidate this specific budget\n queryClient.invalidateQueries({\n queryKey: budgetKeys.detail(userId, budgetId),\n });\n },\n ...options,\n });\n}\n","/**\n * Mutation hook for deleting a budget\n * Legacy API: DELETE /users/{userId}/budgets/{id}\n */\n\nimport {\n useMutation,\n UseMutationOptions,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface DeleteBudgetParams {\n userId: string;\n budgetId: number;\n}\n\n/**\n * Delete a budget\n * Removes budget from user's budgets list\n * Invalidates related queries on success\n *\n * @param options - TanStack Mutation options\n * @returns Mutation result\n *\n * @example\n * ```tsx\n * function BudgetDeleteButton({ budgetId }: { budgetId: number }) {\n * const deleteBudget = useDeleteBudget();\n *\n * const handleDelete = () => {\n * if (confirm('Are you sure you want to delete this budget?')) {\n * deleteBudget.mutate({\n * userId: 'user123',\n * budgetId\n * }, {\n * onSuccess: () => {\n * toast.success('Budget deleted');\n * router.push('/budgets');\n * },\n * onError: (error) => {\n * toast.error(`Failed: ${error.message}`);\n * }\n * });\n * }\n * };\n *\n * return (\n * <button\n * onClick={handleDelete}\n * disabled={deleteBudget.isPending}\n * >\n * {deleteBudget.isPending ? 'Deleting...' : 'Delete'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useDeleteBudget(\n options?: Omit<UseMutationOptions<void, Error, DeleteBudgetParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, budgetId }: DeleteBudgetParams) => {\n await api.delete(`/users/${userId}/budgets/${budgetId}`);\n // DELETE returns 204 No Content - no response body to validate\n },\n onSuccess: (_, { userId, budgetId }) => {\n // Invalidate list queries\n queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });\n // Invalidate this specific budget\n queryClient.invalidateQueries({\n queryKey: budgetKeys.detail(userId, budgetId),\n });\n },\n ...options,\n });\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
|
3
|
+
import { BudgetsResponse, Budget, BudgetCreate, BudgetUpdate } from '@pfm-platform/shared';
|
|
4
|
+
import * as axios from 'axios';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Query key factory for Budget domain
|
|
8
|
+
* Hierarchical structure for efficient cache invalidation
|
|
9
|
+
*/
|
|
10
|
+
interface BudgetDateRangeFilters {
|
|
11
|
+
start_date?: string;
|
|
12
|
+
end_date?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const budgetKeys: {
|
|
15
|
+
all: readonly ["budgets"];
|
|
16
|
+
lists: () => readonly ["budgets", "list"];
|
|
17
|
+
list: (userId: string, filters?: BudgetDateRangeFilters) => readonly ["budgets", "list", string, BudgetDateRangeFilters];
|
|
18
|
+
details: () => readonly ["budgets", "detail"];
|
|
19
|
+
detail: (userId: string, budgetId: number) => readonly ["budgets", "detail", string, number];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface UseBudgetsParams {
|
|
23
|
+
userId: string;
|
|
24
|
+
filters?: BudgetDateRangeFilters;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetch budgets with optional date range filters
|
|
28
|
+
* Uses Zod validation to ensure API response matches expected schema
|
|
29
|
+
*
|
|
30
|
+
* @param params - Query parameters with userId and optional date range filters
|
|
31
|
+
* @param options - TanStack Query options
|
|
32
|
+
* @returns Query result with validated budgets data
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Fetch all budgets
|
|
37
|
+
* function BudgetsList() {
|
|
38
|
+
* const { data, isLoading } = useBudgets({ userId: 'user123' });
|
|
39
|
+
*
|
|
40
|
+
* return (
|
|
41
|
+
* <ul>
|
|
42
|
+
* {data?.budgets.map(budget => (
|
|
43
|
+
* <li key={budget.id}>{budget.name}</li>
|
|
44
|
+
* ))}
|
|
45
|
+
* </ul>
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* // Fetch budgets for specific date range
|
|
50
|
+
* function MonthBudgets() {
|
|
51
|
+
* const { data } = useBudgets({
|
|
52
|
+
* userId: 'user123',
|
|
53
|
+
* filters: {
|
|
54
|
+
* start_date: '2025-01-01T00:00:00Z',
|
|
55
|
+
* end_date: '2025-01-31T23:59:59Z',
|
|
56
|
+
* }
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* return <BudgetList budgets={data?.budgets} />;
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function useBudgets(params: UseBudgetsParams, options?: Omit<UseQueryOptions<BudgetsResponse>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<{
|
|
64
|
+
budgets: {
|
|
65
|
+
id: number;
|
|
66
|
+
month: number;
|
|
67
|
+
year: number;
|
|
68
|
+
name: string;
|
|
69
|
+
state: "under" | "risk" | "over";
|
|
70
|
+
spent: number;
|
|
71
|
+
budget_amount: number;
|
|
72
|
+
tag_names: string[];
|
|
73
|
+
links: {
|
|
74
|
+
accounts: number[];
|
|
75
|
+
budget_histories: number[];
|
|
76
|
+
};
|
|
77
|
+
}[];
|
|
78
|
+
}, Error>;
|
|
79
|
+
|
|
80
|
+
interface UseBudgetParams {
|
|
81
|
+
userId: string;
|
|
82
|
+
budgetId: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fetch a single budget by ID
|
|
86
|
+
* Uses Zod validation to ensure API response matches expected schema
|
|
87
|
+
*
|
|
88
|
+
* @param params - Query parameters with userId and budgetId
|
|
89
|
+
* @param options - TanStack Query options
|
|
90
|
+
* @returns Query result with validated budget data
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* function BudgetDetail({ budgetId }: { budgetId: number }) {
|
|
95
|
+
* const { data, isLoading, error } = useBudget({
|
|
96
|
+
* userId: 'user123',
|
|
97
|
+
* budgetId
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
101
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
102
|
+
*
|
|
103
|
+
* return (
|
|
104
|
+
* <div>
|
|
105
|
+
* <h1>{data.name}</h1>
|
|
106
|
+
* <p>Amount: ${data.budget_amount}</p>
|
|
107
|
+
* <p>Spent: ${data.spent}</p>
|
|
108
|
+
* <p>State: {data.state}</p>
|
|
109
|
+
* </div>
|
|
110
|
+
* );
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
declare function useBudget(params: UseBudgetParams, options?: Omit<UseQueryOptions<Budget>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<{
|
|
115
|
+
id: number;
|
|
116
|
+
month: number;
|
|
117
|
+
year: number;
|
|
118
|
+
name: string;
|
|
119
|
+
state: "under" | "risk" | "over";
|
|
120
|
+
spent: number;
|
|
121
|
+
budget_amount: number;
|
|
122
|
+
tag_names: string[];
|
|
123
|
+
links: {
|
|
124
|
+
accounts: number[];
|
|
125
|
+
budget_histories: number[];
|
|
126
|
+
};
|
|
127
|
+
}, Error>;
|
|
128
|
+
|
|
129
|
+
interface CreateBudgetParams {
|
|
130
|
+
userId: string;
|
|
131
|
+
data: BudgetCreate;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create a new budget
|
|
135
|
+
* Validates input with Zod schema and invalidates related queries on success
|
|
136
|
+
*
|
|
137
|
+
* @param options - TanStack Mutation options
|
|
138
|
+
* @returns Mutation result
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* function BudgetCreateForm() {
|
|
143
|
+
* const createBudget = useCreateBudget();
|
|
144
|
+
*
|
|
145
|
+
* const handleSubmit = (formData) => {
|
|
146
|
+
* createBudget.mutate({
|
|
147
|
+
* userId: 'user123',
|
|
148
|
+
* data: {
|
|
149
|
+
* name: formData.name,
|
|
150
|
+
* budget_amount: formData.amount,
|
|
151
|
+
* tag_names: formData.tags,
|
|
152
|
+
* account_list: formData.accounts,
|
|
153
|
+
* show_on_dashboard: true,
|
|
154
|
+
* other: {}
|
|
155
|
+
* }
|
|
156
|
+
* }, {
|
|
157
|
+
* onSuccess: (newBudget) => {
|
|
158
|
+
* toast.success('Budget created!');
|
|
159
|
+
* router.push(`/budgets/${newBudget.id}`);
|
|
160
|
+
* },
|
|
161
|
+
* onError: (error) => {
|
|
162
|
+
* toast.error(`Failed: ${error.message}`);
|
|
163
|
+
* }
|
|
164
|
+
* });
|
|
165
|
+
* };
|
|
166
|
+
*
|
|
167
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function useCreateBudget(options?: Omit<UseMutationOptions<Budget, Error, CreateBudgetParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
172
|
+
id: number;
|
|
173
|
+
month: number;
|
|
174
|
+
year: number;
|
|
175
|
+
name: string;
|
|
176
|
+
state: "under" | "risk" | "over";
|
|
177
|
+
spent: number;
|
|
178
|
+
budget_amount: number;
|
|
179
|
+
tag_names: string[];
|
|
180
|
+
links: {
|
|
181
|
+
accounts: number[];
|
|
182
|
+
budget_histories: number[];
|
|
183
|
+
};
|
|
184
|
+
}, Error, CreateBudgetParams, unknown>;
|
|
185
|
+
|
|
186
|
+
interface UpdateBudgetParams {
|
|
187
|
+
userId: string;
|
|
188
|
+
budgetId: number;
|
|
189
|
+
data: BudgetUpdate;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Update an existing budget
|
|
193
|
+
* Validates input with Zod schema and invalidates related queries on success
|
|
194
|
+
*
|
|
195
|
+
* @param options - TanStack Mutation options
|
|
196
|
+
* @returns Mutation result
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```tsx
|
|
200
|
+
* function BudgetEditForm({ budget }: { budget: Budget }) {
|
|
201
|
+
* const updateBudget = useUpdateBudget();
|
|
202
|
+
*
|
|
203
|
+
* const handleSubmit = (formData) => {
|
|
204
|
+
* updateBudget.mutate({
|
|
205
|
+
* userId: 'user123',
|
|
206
|
+
* budgetId: budget.id,
|
|
207
|
+
* data: {
|
|
208
|
+
* name: formData.name,
|
|
209
|
+
* budget_amount: formData.amount,
|
|
210
|
+
* tag_names: formData.tags,
|
|
211
|
+
* account_list: formData.accounts,
|
|
212
|
+
* show_on_dashboard: formData.showOnDashboard,
|
|
213
|
+
* other: formData.metadata
|
|
214
|
+
* }
|
|
215
|
+
* }, {
|
|
216
|
+
* onSuccess: () => {
|
|
217
|
+
* toast.success('Budget updated!');
|
|
218
|
+
* },
|
|
219
|
+
* onError: (error) => {
|
|
220
|
+
* toast.error(`Failed: ${error.message}`);
|
|
221
|
+
* }
|
|
222
|
+
* });
|
|
223
|
+
* };
|
|
224
|
+
*
|
|
225
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
226
|
+
* }
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function useUpdateBudget(options?: Omit<UseMutationOptions<Budget, Error, UpdateBudgetParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
230
|
+
id: number;
|
|
231
|
+
month: number;
|
|
232
|
+
year: number;
|
|
233
|
+
name: string;
|
|
234
|
+
state: "under" | "risk" | "over";
|
|
235
|
+
spent: number;
|
|
236
|
+
budget_amount: number;
|
|
237
|
+
tag_names: string[];
|
|
238
|
+
links: {
|
|
239
|
+
accounts: number[];
|
|
240
|
+
budget_histories: number[];
|
|
241
|
+
};
|
|
242
|
+
}, Error, UpdateBudgetParams, unknown>;
|
|
243
|
+
|
|
244
|
+
interface DeleteBudgetParams {
|
|
245
|
+
userId: string;
|
|
246
|
+
budgetId: number;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Delete a budget
|
|
250
|
+
* Removes budget from user's budgets list
|
|
251
|
+
* Invalidates related queries on success
|
|
252
|
+
*
|
|
253
|
+
* @param options - TanStack Mutation options
|
|
254
|
+
* @returns Mutation result
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```tsx
|
|
258
|
+
* function BudgetDeleteButton({ budgetId }: { budgetId: number }) {
|
|
259
|
+
* const deleteBudget = useDeleteBudget();
|
|
260
|
+
*
|
|
261
|
+
* const handleDelete = () => {
|
|
262
|
+
* if (confirm('Are you sure you want to delete this budget?')) {
|
|
263
|
+
* deleteBudget.mutate({
|
|
264
|
+
* userId: 'user123',
|
|
265
|
+
* budgetId
|
|
266
|
+
* }, {
|
|
267
|
+
* onSuccess: () => {
|
|
268
|
+
* toast.success('Budget deleted');
|
|
269
|
+
* router.push('/budgets');
|
|
270
|
+
* },
|
|
271
|
+
* onError: (error) => {
|
|
272
|
+
* toast.error(`Failed: ${error.message}`);
|
|
273
|
+
* }
|
|
274
|
+
* });
|
|
275
|
+
* }
|
|
276
|
+
* };
|
|
277
|
+
*
|
|
278
|
+
* return (
|
|
279
|
+
* <button
|
|
280
|
+
* onClick={handleDelete}
|
|
281
|
+
* disabled={deleteBudget.isPending}
|
|
282
|
+
* >
|
|
283
|
+
* {deleteBudget.isPending ? 'Deleting...' : 'Delete'}
|
|
284
|
+
* </button>
|
|
285
|
+
* );
|
|
286
|
+
* }
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
declare function useDeleteBudget(options?: Omit<UseMutationOptions<void, Error, DeleteBudgetParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<void, Error, DeleteBudgetParams, unknown>;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Axios client for Budget API calls
|
|
293
|
+
* Reuses pattern from accounts/transactions data-access
|
|
294
|
+
*/
|
|
295
|
+
declare const api: axios.AxiosInstance;
|
|
296
|
+
|
|
297
|
+
export { type BudgetDateRangeFilters, type CreateBudgetParams, type DeleteBudgetParams, type UpdateBudgetParams, type UseBudgetParams, type UseBudgetsParams, api, budgetKeys, useBudget, useBudgets, useCreateBudget, useDeleteBudget, useUpdateBudget };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
|
3
|
+
import { BudgetsResponse, Budget, BudgetCreate, BudgetUpdate } from '@pfm-platform/shared';
|
|
4
|
+
import * as axios from 'axios';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Query key factory for Budget domain
|
|
8
|
+
* Hierarchical structure for efficient cache invalidation
|
|
9
|
+
*/
|
|
10
|
+
interface BudgetDateRangeFilters {
|
|
11
|
+
start_date?: string;
|
|
12
|
+
end_date?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const budgetKeys: {
|
|
15
|
+
all: readonly ["budgets"];
|
|
16
|
+
lists: () => readonly ["budgets", "list"];
|
|
17
|
+
list: (userId: string, filters?: BudgetDateRangeFilters) => readonly ["budgets", "list", string, BudgetDateRangeFilters];
|
|
18
|
+
details: () => readonly ["budgets", "detail"];
|
|
19
|
+
detail: (userId: string, budgetId: number) => readonly ["budgets", "detail", string, number];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface UseBudgetsParams {
|
|
23
|
+
userId: string;
|
|
24
|
+
filters?: BudgetDateRangeFilters;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetch budgets with optional date range filters
|
|
28
|
+
* Uses Zod validation to ensure API response matches expected schema
|
|
29
|
+
*
|
|
30
|
+
* @param params - Query parameters with userId and optional date range filters
|
|
31
|
+
* @param options - TanStack Query options
|
|
32
|
+
* @returns Query result with validated budgets data
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Fetch all budgets
|
|
37
|
+
* function BudgetsList() {
|
|
38
|
+
* const { data, isLoading } = useBudgets({ userId: 'user123' });
|
|
39
|
+
*
|
|
40
|
+
* return (
|
|
41
|
+
* <ul>
|
|
42
|
+
* {data?.budgets.map(budget => (
|
|
43
|
+
* <li key={budget.id}>{budget.name}</li>
|
|
44
|
+
* ))}
|
|
45
|
+
* </ul>
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* // Fetch budgets for specific date range
|
|
50
|
+
* function MonthBudgets() {
|
|
51
|
+
* const { data } = useBudgets({
|
|
52
|
+
* userId: 'user123',
|
|
53
|
+
* filters: {
|
|
54
|
+
* start_date: '2025-01-01T00:00:00Z',
|
|
55
|
+
* end_date: '2025-01-31T23:59:59Z',
|
|
56
|
+
* }
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* return <BudgetList budgets={data?.budgets} />;
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function useBudgets(params: UseBudgetsParams, options?: Omit<UseQueryOptions<BudgetsResponse>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<{
|
|
64
|
+
budgets: {
|
|
65
|
+
id: number;
|
|
66
|
+
month: number;
|
|
67
|
+
year: number;
|
|
68
|
+
name: string;
|
|
69
|
+
state: "under" | "risk" | "over";
|
|
70
|
+
spent: number;
|
|
71
|
+
budget_amount: number;
|
|
72
|
+
tag_names: string[];
|
|
73
|
+
links: {
|
|
74
|
+
accounts: number[];
|
|
75
|
+
budget_histories: number[];
|
|
76
|
+
};
|
|
77
|
+
}[];
|
|
78
|
+
}, Error>;
|
|
79
|
+
|
|
80
|
+
interface UseBudgetParams {
|
|
81
|
+
userId: string;
|
|
82
|
+
budgetId: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fetch a single budget by ID
|
|
86
|
+
* Uses Zod validation to ensure API response matches expected schema
|
|
87
|
+
*
|
|
88
|
+
* @param params - Query parameters with userId and budgetId
|
|
89
|
+
* @param options - TanStack Query options
|
|
90
|
+
* @returns Query result with validated budget data
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* function BudgetDetail({ budgetId }: { budgetId: number }) {
|
|
95
|
+
* const { data, isLoading, error } = useBudget({
|
|
96
|
+
* userId: 'user123',
|
|
97
|
+
* budgetId
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
101
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
102
|
+
*
|
|
103
|
+
* return (
|
|
104
|
+
* <div>
|
|
105
|
+
* <h1>{data.name}</h1>
|
|
106
|
+
* <p>Amount: ${data.budget_amount}</p>
|
|
107
|
+
* <p>Spent: ${data.spent}</p>
|
|
108
|
+
* <p>State: {data.state}</p>
|
|
109
|
+
* </div>
|
|
110
|
+
* );
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
declare function useBudget(params: UseBudgetParams, options?: Omit<UseQueryOptions<Budget>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<{
|
|
115
|
+
id: number;
|
|
116
|
+
month: number;
|
|
117
|
+
year: number;
|
|
118
|
+
name: string;
|
|
119
|
+
state: "under" | "risk" | "over";
|
|
120
|
+
spent: number;
|
|
121
|
+
budget_amount: number;
|
|
122
|
+
tag_names: string[];
|
|
123
|
+
links: {
|
|
124
|
+
accounts: number[];
|
|
125
|
+
budget_histories: number[];
|
|
126
|
+
};
|
|
127
|
+
}, Error>;
|
|
128
|
+
|
|
129
|
+
interface CreateBudgetParams {
|
|
130
|
+
userId: string;
|
|
131
|
+
data: BudgetCreate;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create a new budget
|
|
135
|
+
* Validates input with Zod schema and invalidates related queries on success
|
|
136
|
+
*
|
|
137
|
+
* @param options - TanStack Mutation options
|
|
138
|
+
* @returns Mutation result
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* function BudgetCreateForm() {
|
|
143
|
+
* const createBudget = useCreateBudget();
|
|
144
|
+
*
|
|
145
|
+
* const handleSubmit = (formData) => {
|
|
146
|
+
* createBudget.mutate({
|
|
147
|
+
* userId: 'user123',
|
|
148
|
+
* data: {
|
|
149
|
+
* name: formData.name,
|
|
150
|
+
* budget_amount: formData.amount,
|
|
151
|
+
* tag_names: formData.tags,
|
|
152
|
+
* account_list: formData.accounts,
|
|
153
|
+
* show_on_dashboard: true,
|
|
154
|
+
* other: {}
|
|
155
|
+
* }
|
|
156
|
+
* }, {
|
|
157
|
+
* onSuccess: (newBudget) => {
|
|
158
|
+
* toast.success('Budget created!');
|
|
159
|
+
* router.push(`/budgets/${newBudget.id}`);
|
|
160
|
+
* },
|
|
161
|
+
* onError: (error) => {
|
|
162
|
+
* toast.error(`Failed: ${error.message}`);
|
|
163
|
+
* }
|
|
164
|
+
* });
|
|
165
|
+
* };
|
|
166
|
+
*
|
|
167
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function useCreateBudget(options?: Omit<UseMutationOptions<Budget, Error, CreateBudgetParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
172
|
+
id: number;
|
|
173
|
+
month: number;
|
|
174
|
+
year: number;
|
|
175
|
+
name: string;
|
|
176
|
+
state: "under" | "risk" | "over";
|
|
177
|
+
spent: number;
|
|
178
|
+
budget_amount: number;
|
|
179
|
+
tag_names: string[];
|
|
180
|
+
links: {
|
|
181
|
+
accounts: number[];
|
|
182
|
+
budget_histories: number[];
|
|
183
|
+
};
|
|
184
|
+
}, Error, CreateBudgetParams, unknown>;
|
|
185
|
+
|
|
186
|
+
interface UpdateBudgetParams {
|
|
187
|
+
userId: string;
|
|
188
|
+
budgetId: number;
|
|
189
|
+
data: BudgetUpdate;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Update an existing budget
|
|
193
|
+
* Validates input with Zod schema and invalidates related queries on success
|
|
194
|
+
*
|
|
195
|
+
* @param options - TanStack Mutation options
|
|
196
|
+
* @returns Mutation result
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```tsx
|
|
200
|
+
* function BudgetEditForm({ budget }: { budget: Budget }) {
|
|
201
|
+
* const updateBudget = useUpdateBudget();
|
|
202
|
+
*
|
|
203
|
+
* const handleSubmit = (formData) => {
|
|
204
|
+
* updateBudget.mutate({
|
|
205
|
+
* userId: 'user123',
|
|
206
|
+
* budgetId: budget.id,
|
|
207
|
+
* data: {
|
|
208
|
+
* name: formData.name,
|
|
209
|
+
* budget_amount: formData.amount,
|
|
210
|
+
* tag_names: formData.tags,
|
|
211
|
+
* account_list: formData.accounts,
|
|
212
|
+
* show_on_dashboard: formData.showOnDashboard,
|
|
213
|
+
* other: formData.metadata
|
|
214
|
+
* }
|
|
215
|
+
* }, {
|
|
216
|
+
* onSuccess: () => {
|
|
217
|
+
* toast.success('Budget updated!');
|
|
218
|
+
* },
|
|
219
|
+
* onError: (error) => {
|
|
220
|
+
* toast.error(`Failed: ${error.message}`);
|
|
221
|
+
* }
|
|
222
|
+
* });
|
|
223
|
+
* };
|
|
224
|
+
*
|
|
225
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
226
|
+
* }
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function useUpdateBudget(options?: Omit<UseMutationOptions<Budget, Error, UpdateBudgetParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
230
|
+
id: number;
|
|
231
|
+
month: number;
|
|
232
|
+
year: number;
|
|
233
|
+
name: string;
|
|
234
|
+
state: "under" | "risk" | "over";
|
|
235
|
+
spent: number;
|
|
236
|
+
budget_amount: number;
|
|
237
|
+
tag_names: string[];
|
|
238
|
+
links: {
|
|
239
|
+
accounts: number[];
|
|
240
|
+
budget_histories: number[];
|
|
241
|
+
};
|
|
242
|
+
}, Error, UpdateBudgetParams, unknown>;
|
|
243
|
+
|
|
244
|
+
interface DeleteBudgetParams {
|
|
245
|
+
userId: string;
|
|
246
|
+
budgetId: number;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Delete a budget
|
|
250
|
+
* Removes budget from user's budgets list
|
|
251
|
+
* Invalidates related queries on success
|
|
252
|
+
*
|
|
253
|
+
* @param options - TanStack Mutation options
|
|
254
|
+
* @returns Mutation result
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```tsx
|
|
258
|
+
* function BudgetDeleteButton({ budgetId }: { budgetId: number }) {
|
|
259
|
+
* const deleteBudget = useDeleteBudget();
|
|
260
|
+
*
|
|
261
|
+
* const handleDelete = () => {
|
|
262
|
+
* if (confirm('Are you sure you want to delete this budget?')) {
|
|
263
|
+
* deleteBudget.mutate({
|
|
264
|
+
* userId: 'user123',
|
|
265
|
+
* budgetId
|
|
266
|
+
* }, {
|
|
267
|
+
* onSuccess: () => {
|
|
268
|
+
* toast.success('Budget deleted');
|
|
269
|
+
* router.push('/budgets');
|
|
270
|
+
* },
|
|
271
|
+
* onError: (error) => {
|
|
272
|
+
* toast.error(`Failed: ${error.message}`);
|
|
273
|
+
* }
|
|
274
|
+
* });
|
|
275
|
+
* }
|
|
276
|
+
* };
|
|
277
|
+
*
|
|
278
|
+
* return (
|
|
279
|
+
* <button
|
|
280
|
+
* onClick={handleDelete}
|
|
281
|
+
* disabled={deleteBudget.isPending}
|
|
282
|
+
* >
|
|
283
|
+
* {deleteBudget.isPending ? 'Deleting...' : 'Delete'}
|
|
284
|
+
* </button>
|
|
285
|
+
* );
|
|
286
|
+
* }
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
declare function useDeleteBudget(options?: Omit<UseMutationOptions<void, Error, DeleteBudgetParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<void, Error, DeleteBudgetParams, unknown>;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Axios client for Budget API calls
|
|
293
|
+
* Reuses pattern from accounts/transactions data-access
|
|
294
|
+
*/
|
|
295
|
+
declare const api: axios.AxiosInstance;
|
|
296
|
+
|
|
297
|
+
export { type BudgetDateRangeFilters, type CreateBudgetParams, type DeleteBudgetParams, type UpdateBudgetParams, type UseBudgetParams, type UseBudgetsParams, api, budgetKeys, useBudget, useBudgets, useCreateBudget, useDeleteBudget, useUpdateBudget };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { BudgetsResponseSchema, BudgetSchema, BudgetCreateSchema, BudgetUpdateSchema } from '@pfm-platform/shared';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
|
|
5
|
+
// src/queries/useBudgets.ts
|
|
6
|
+
|
|
7
|
+
// src/keys.ts
|
|
8
|
+
var budgetKeys = {
|
|
9
|
+
all: ["budgets"],
|
|
10
|
+
lists: () => [...budgetKeys.all, "list"],
|
|
11
|
+
list: (userId, filters) => [...budgetKeys.lists(), userId, filters ?? {}],
|
|
12
|
+
details: () => [...budgetKeys.all, "detail"],
|
|
13
|
+
detail: (userId, budgetId) => [...budgetKeys.details(), userId, budgetId]
|
|
14
|
+
};
|
|
15
|
+
var api = axios.create({
|
|
16
|
+
baseURL: "/api",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json"
|
|
19
|
+
},
|
|
20
|
+
timeout: 1e4
|
|
21
|
+
});
|
|
22
|
+
api.interceptors.request.use(
|
|
23
|
+
(config) => {
|
|
24
|
+
const token = localStorage.getItem("auth_token");
|
|
25
|
+
if (token) {
|
|
26
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
27
|
+
}
|
|
28
|
+
return config;
|
|
29
|
+
},
|
|
30
|
+
(error) => Promise.reject(error)
|
|
31
|
+
);
|
|
32
|
+
api.interceptors.response.use(
|
|
33
|
+
(response) => response,
|
|
34
|
+
(error) => {
|
|
35
|
+
if (error.response?.status === 401) {
|
|
36
|
+
localStorage.removeItem("auth_token");
|
|
37
|
+
window.location.href = "/login";
|
|
38
|
+
}
|
|
39
|
+
return Promise.reject(error);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// src/queries/useBudgets.ts
|
|
44
|
+
function useBudgets(params, options) {
|
|
45
|
+
const { userId, filters = {} } = params;
|
|
46
|
+
return useQuery({
|
|
47
|
+
queryKey: budgetKeys.list(userId, filters),
|
|
48
|
+
queryFn: async () => {
|
|
49
|
+
const queryParams = new URLSearchParams();
|
|
50
|
+
if (filters.start_date) {
|
|
51
|
+
queryParams.append("start_date", filters.start_date);
|
|
52
|
+
}
|
|
53
|
+
if (filters.end_date) {
|
|
54
|
+
queryParams.append("end_date", filters.end_date);
|
|
55
|
+
}
|
|
56
|
+
const queryString = queryParams.toString();
|
|
57
|
+
const url = `/users/${userId}/budgets${queryString ? `?${queryString}` : ""}`;
|
|
58
|
+
const response = await api.get(url);
|
|
59
|
+
return BudgetsResponseSchema.parse(response.data);
|
|
60
|
+
},
|
|
61
|
+
staleTime: 1e3 * 60 * 5,
|
|
62
|
+
// 5 minutes - budgets change less frequently than transactions
|
|
63
|
+
...options
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function useBudget(params, options) {
|
|
67
|
+
const { userId, budgetId } = params;
|
|
68
|
+
return useQuery({
|
|
69
|
+
queryKey: budgetKeys.detail(userId, budgetId),
|
|
70
|
+
queryFn: async () => {
|
|
71
|
+
const response = await api.get(`/users/${userId}/budgets/${budgetId}`);
|
|
72
|
+
return BudgetSchema.parse(response.data);
|
|
73
|
+
},
|
|
74
|
+
staleTime: 1e3 * 60 * 5,
|
|
75
|
+
// 5 minutes
|
|
76
|
+
...options
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function useCreateBudget(options) {
|
|
80
|
+
const queryClient = useQueryClient();
|
|
81
|
+
return useMutation({
|
|
82
|
+
mutationFn: async ({ userId, data }) => {
|
|
83
|
+
const validated = BudgetCreateSchema.parse(data);
|
|
84
|
+
const response = await api.post(`/users/${userId}/budgets`, {
|
|
85
|
+
budget: validated
|
|
86
|
+
});
|
|
87
|
+
return BudgetSchema.parse(response.data);
|
|
88
|
+
},
|
|
89
|
+
onSuccess: (_, { userId }) => {
|
|
90
|
+
queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });
|
|
91
|
+
},
|
|
92
|
+
...options
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function useUpdateBudget(options) {
|
|
96
|
+
const queryClient = useQueryClient();
|
|
97
|
+
return useMutation({
|
|
98
|
+
mutationFn: async ({ userId, budgetId, data }) => {
|
|
99
|
+
const validated = BudgetUpdateSchema.parse(data);
|
|
100
|
+
const response = await api.put(`/users/${userId}/budgets/${budgetId}`, {
|
|
101
|
+
budget: validated
|
|
102
|
+
});
|
|
103
|
+
return BudgetSchema.parse(response.data);
|
|
104
|
+
},
|
|
105
|
+
onSuccess: (_, { userId, budgetId }) => {
|
|
106
|
+
queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });
|
|
107
|
+
queryClient.invalidateQueries({
|
|
108
|
+
queryKey: budgetKeys.detail(userId, budgetId)
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
...options
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function useDeleteBudget(options) {
|
|
115
|
+
const queryClient = useQueryClient();
|
|
116
|
+
return useMutation({
|
|
117
|
+
mutationFn: async ({ userId, budgetId }) => {
|
|
118
|
+
await api.delete(`/users/${userId}/budgets/${budgetId}`);
|
|
119
|
+
},
|
|
120
|
+
onSuccess: (_, { userId, budgetId }) => {
|
|
121
|
+
queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });
|
|
122
|
+
queryClient.invalidateQueries({
|
|
123
|
+
queryKey: budgetKeys.detail(userId, budgetId)
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
...options
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { api, budgetKeys, useBudget, useBudgets, useCreateBudget, useDeleteBudget, useUpdateBudget };
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
132
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/keys.ts","../src/client.ts","../src/queries/useBudgets.ts","../src/queries/useBudget.ts","../src/mutations/useCreateBudget.ts","../src/mutations/useUpdateBudget.ts","../src/mutations/useDeleteBudget.ts"],"names":["useQuery","BudgetSchema","useQueryClient","useMutation"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Query key factory for Budget domain\n * Hierarchical structure for efficient cache invalidation\n */\n\nexport interface BudgetDateRangeFilters {\n start_date?: string; // RFC 3339 format\n end_date?: string; // RFC 3339 format\n}\n\nexport const budgetKeys = {\n all: ['budgets'] as const,\n lists: () => [...budgetKeys.all, 'list'] as const,\n list: (userId: string, filters?: BudgetDateRangeFilters) =>\n [...budgetKeys.lists(), userId, filters ?? {}] as const,\n details: () => [...budgetKeys.all, 'detail'] as const,\n detail: (userId: string, budgetId: number) =>\n [...budgetKeys.details(), userId, budgetId] as const,\n};\n","/**\n * Axios client for Budget API calls\n * Reuses pattern from accounts/transactions data-access\n */\n\nimport axios from 'axios';\n\nexport const api = axios.create({\n baseURL: '/api',\n headers: {\n 'Content-Type': 'application/json',\n },\n timeout: 10000,\n});\n\n// Request interceptor - Add auth token\napi.interceptors.request.use(\n (config) => {\n const token = localStorage.getItem('auth_token');\n if (token) {\n config.headers.Authorization = `Bearer ${token}`;\n }\n return config;\n },\n (error) => Promise.reject(error)\n);\n\n// Response interceptor - Handle 401 unauthorized\napi.interceptors.response.use(\n (response) => response,\n (error) => {\n if (error.response?.status === 401) {\n // Clear token and redirect to login\n localStorage.removeItem('auth_token');\n window.location.href = '/login';\n }\n return Promise.reject(error);\n }\n);\n","/**\n * Query hook for fetching budgets list\n * Legacy API: GET /users/{userId}/budgets\n * Optional filters: start_date, end_date (RFC 3339 format)\n */\n\nimport { useQuery, UseQueryOptions } from '@tanstack/react-query';\nimport { BudgetsResponseSchema, BudgetsResponse } from '@pfm-platform/shared';\nimport { budgetKeys, BudgetDateRangeFilters } from '../keys';\nimport { api } from '../client';\n\nexport interface UseBudgetsParams {\n userId: string;\n filters?: BudgetDateRangeFilters;\n}\n\n/**\n * Fetch budgets with optional date range filters\n * Uses Zod validation to ensure API response matches expected schema\n *\n * @param params - Query parameters with userId and optional date range filters\n * @param options - TanStack Query options\n * @returns Query result with validated budgets data\n *\n * @example\n * ```tsx\n * // Fetch all budgets\n * function BudgetsList() {\n * const { data, isLoading } = useBudgets({ userId: 'user123' });\n *\n * return (\n * <ul>\n * {data?.budgets.map(budget => (\n * <li key={budget.id}>{budget.name}</li>\n * ))}\n * </ul>\n * );\n * }\n *\n * // Fetch budgets for specific date range\n * function MonthBudgets() {\n * const { data } = useBudgets({\n * userId: 'user123',\n * filters: {\n * start_date: '2025-01-01T00:00:00Z',\n * end_date: '2025-01-31T23:59:59Z',\n * }\n * });\n *\n * return <BudgetList budgets={data?.budgets} />;\n * }\n * ```\n */\nexport function useBudgets(\n params: UseBudgetsParams,\n options?: Omit<UseQueryOptions<BudgetsResponse>, 'queryKey' | 'queryFn'>\n) {\n const { userId, filters = {} } = params;\n\n return useQuery({\n queryKey: budgetKeys.list(userId, filters),\n queryFn: async () => {\n // Build query parameters\n const queryParams = new URLSearchParams();\n\n if (filters.start_date) {\n queryParams.append('start_date', filters.start_date);\n }\n\n if (filters.end_date) {\n queryParams.append('end_date', filters.end_date);\n }\n\n const queryString = queryParams.toString();\n const url = `/users/${userId}/budgets${\n queryString ? `?${queryString}` : ''\n }`;\n\n const response = await api.get(url);\n\n // Validate response with Zod schema from Mini-Arc 1\n return BudgetsResponseSchema.parse(response.data);\n },\n staleTime: 1000 * 60 * 5, // 5 minutes - budgets change less frequently than transactions\n ...options,\n });\n}\n","/**\n * Query hook for fetching a single budget\n * Legacy API: GET /users/{userId}/budgets/{id}\n */\n\nimport { useQuery, UseQueryOptions } from '@tanstack/react-query';\nimport { BudgetSchema, Budget } from '@pfm-platform/shared';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface UseBudgetParams {\n userId: string;\n budgetId: number;\n}\n\n/**\n * Fetch a single budget by ID\n * Uses Zod validation to ensure API response matches expected schema\n *\n * @param params - Query parameters with userId and budgetId\n * @param options - TanStack Query options\n * @returns Query result with validated budget data\n *\n * @example\n * ```tsx\n * function BudgetDetail({ budgetId }: { budgetId: number }) {\n * const { data, isLoading, error } = useBudget({\n * userId: 'user123',\n * budgetId\n * });\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return (\n * <div>\n * <h1>{data.name}</h1>\n * <p>Amount: ${data.budget_amount}</p>\n * <p>Spent: ${data.spent}</p>\n * <p>State: {data.state}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport function useBudget(\n params: UseBudgetParams,\n options?: Omit<UseQueryOptions<Budget>, 'queryKey' | 'queryFn'>\n) {\n const { userId, budgetId } = params;\n\n return useQuery({\n queryKey: budgetKeys.detail(userId, budgetId),\n queryFn: async () => {\n const response = await api.get(`/users/${userId}/budgets/${budgetId}`);\n\n // Validate response with Zod schema from Mini-Arc 1\n return BudgetSchema.parse(response.data);\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n ...options,\n });\n}\n","/**\n * Mutation hook for creating a budget\n * Legacy API: POST /users/{userId}/budgets\n */\n\nimport {\n useMutation,\n UseMutationOptions,\n useQueryClient,\n} from '@tanstack/react-query';\nimport {\n BudgetCreateSchema,\n BudgetSchema,\n Budget,\n BudgetCreate,\n} from '@pfm-platform/shared';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface CreateBudgetParams {\n userId: string;\n data: BudgetCreate;\n}\n\n/**\n * Create a new budget\n * Validates input with Zod schema and invalidates related queries on success\n *\n * @param options - TanStack Mutation options\n * @returns Mutation result\n *\n * @example\n * ```tsx\n * function BudgetCreateForm() {\n * const createBudget = useCreateBudget();\n *\n * const handleSubmit = (formData) => {\n * createBudget.mutate({\n * userId: 'user123',\n * data: {\n * name: formData.name,\n * budget_amount: formData.amount,\n * tag_names: formData.tags,\n * account_list: formData.accounts,\n * show_on_dashboard: true,\n * other: {}\n * }\n * }, {\n * onSuccess: (newBudget) => {\n * toast.success('Budget created!');\n * router.push(`/budgets/${newBudget.id}`);\n * },\n * onError: (error) => {\n * toast.error(`Failed: ${error.message}`);\n * }\n * });\n * };\n *\n * return <form onSubmit={handleSubmit}>...</form>;\n * }\n * ```\n */\nexport function useCreateBudget(\n options?: Omit<UseMutationOptions<Budget, Error, CreateBudgetParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, data }: CreateBudgetParams) => {\n // Validate input with Zod schema from Mini-Arc 1\n const validated = BudgetCreateSchema.parse(data);\n\n const response = await api.post(`/users/${userId}/budgets`, {\n budget: validated,\n });\n\n // Validate response\n return BudgetSchema.parse(response.data);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate budget list queries for this user\n queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });\n },\n ...options,\n });\n}\n","/**\n * Mutation hook for updating a budget\n * Legacy API: PUT /users/{userId}/budgets/{id}\n */\n\nimport {\n useMutation,\n UseMutationOptions,\n useQueryClient,\n} from '@tanstack/react-query';\nimport {\n BudgetUpdateSchema,\n BudgetSchema,\n Budget,\n BudgetUpdate,\n} from '@pfm-platform/shared';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface UpdateBudgetParams {\n userId: string;\n budgetId: number;\n data: BudgetUpdate;\n}\n\n/**\n * Update an existing budget\n * Validates input with Zod schema and invalidates related queries on success\n *\n * @param options - TanStack Mutation options\n * @returns Mutation result\n *\n * @example\n * ```tsx\n * function BudgetEditForm({ budget }: { budget: Budget }) {\n * const updateBudget = useUpdateBudget();\n *\n * const handleSubmit = (formData) => {\n * updateBudget.mutate({\n * userId: 'user123',\n * budgetId: budget.id,\n * data: {\n * name: formData.name,\n * budget_amount: formData.amount,\n * tag_names: formData.tags,\n * account_list: formData.accounts,\n * show_on_dashboard: formData.showOnDashboard,\n * other: formData.metadata\n * }\n * }, {\n * onSuccess: () => {\n * toast.success('Budget updated!');\n * },\n * onError: (error) => {\n * toast.error(`Failed: ${error.message}`);\n * }\n * });\n * };\n *\n * return <form onSubmit={handleSubmit}>...</form>;\n * }\n * ```\n */\nexport function useUpdateBudget(\n options?: Omit<UseMutationOptions<Budget, Error, UpdateBudgetParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, budgetId, data }: UpdateBudgetParams) => {\n // Validate input with Zod schema from Mini-Arc 1\n const validated = BudgetUpdateSchema.parse(data);\n\n const response = await api.put(`/users/${userId}/budgets/${budgetId}`, {\n budget: validated,\n });\n\n // Validate response\n return BudgetSchema.parse(response.data);\n },\n onSuccess: (_, { userId, budgetId }) => {\n // Invalidate list queries\n queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });\n // Invalidate this specific budget\n queryClient.invalidateQueries({\n queryKey: budgetKeys.detail(userId, budgetId),\n });\n },\n ...options,\n });\n}\n","/**\n * Mutation hook for deleting a budget\n * Legacy API: DELETE /users/{userId}/budgets/{id}\n */\n\nimport {\n useMutation,\n UseMutationOptions,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { budgetKeys } from '../keys';\nimport { api } from '../client';\n\nexport interface DeleteBudgetParams {\n userId: string;\n budgetId: number;\n}\n\n/**\n * Delete a budget\n * Removes budget from user's budgets list\n * Invalidates related queries on success\n *\n * @param options - TanStack Mutation options\n * @returns Mutation result\n *\n * @example\n * ```tsx\n * function BudgetDeleteButton({ budgetId }: { budgetId: number }) {\n * const deleteBudget = useDeleteBudget();\n *\n * const handleDelete = () => {\n * if (confirm('Are you sure you want to delete this budget?')) {\n * deleteBudget.mutate({\n * userId: 'user123',\n * budgetId\n * }, {\n * onSuccess: () => {\n * toast.success('Budget deleted');\n * router.push('/budgets');\n * },\n * onError: (error) => {\n * toast.error(`Failed: ${error.message}`);\n * }\n * });\n * }\n * };\n *\n * return (\n * <button\n * onClick={handleDelete}\n * disabled={deleteBudget.isPending}\n * >\n * {deleteBudget.isPending ? 'Deleting...' : 'Delete'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useDeleteBudget(\n options?: Omit<UseMutationOptions<void, Error, DeleteBudgetParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, budgetId }: DeleteBudgetParams) => {\n await api.delete(`/users/${userId}/budgets/${budgetId}`);\n // DELETE returns 204 No Content - no response body to validate\n },\n onSuccess: (_, { userId, budgetId }) => {\n // Invalidate list queries\n queryClient.invalidateQueries({ queryKey: budgetKeys.lists() });\n // Invalidate this specific budget\n queryClient.invalidateQueries({\n queryKey: budgetKeys.detail(userId, budgetId),\n });\n },\n ...options,\n });\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pfm-platform/budgets-data-access",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"require": "./dist/index.cjs"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"axios": "^1.13.2",
|
|
14
|
+
"zod": "4.1.12",
|
|
15
|
+
"@pfm-platform/shared": "0.0.1"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@testing-library/react": "^16.3.0",
|
|
19
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
20
|
+
"@vitest/coverage-v8": "^4.0.9",
|
|
21
|
+
"jsdom": "^27.2.0",
|
|
22
|
+
"react-dom": "19.2.0",
|
|
23
|
+
"typescript": "5.9.3",
|
|
24
|
+
"vitest": "4.0.9"
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"description": "Personal Finance Management - BUDGETS data-access layer",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"pfm",
|
|
36
|
+
"finance",
|
|
37
|
+
"budgets",
|
|
38
|
+
"data-access",
|
|
39
|
+
"react",
|
|
40
|
+
"typescript"
|
|
41
|
+
],
|
|
42
|
+
"author": "Lenny Miller",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/lennylmiller/pfm-research",
|
|
47
|
+
"directory": "packages/budgets/data-access"
|
|
48
|
+
},
|
|
49
|
+
"bugs": "https://github.com/lennylmiller/pfm-research/issues",
|
|
50
|
+
"homepage": "https://github.com/lennylmiller/pfm-research#readme",
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@tanstack/react-query": "5.90.9",
|
|
53
|
+
"react": "19.2.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"test:coverage": "vitest run --coverage",
|
|
59
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean"
|
|
60
|
+
}
|
|
61
|
+
}
|