@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 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"]}
@@ -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 };
@@ -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
+ }