@pfm-platform/expenses-data-access 0.1.1 → 0.2.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 CHANGED
@@ -2,19 +2,8 @@
2
2
 
3
3
  var reactQuery = require('@tanstack/react-query');
4
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
5
 
11
6
  // src/queries/useExpenses.ts
12
- var api = axios__default.default.create({
13
- baseURL: "/api",
14
- headers: {
15
- "Content-Type": "application/json"
16
- }
17
- });
18
7
 
19
8
  // src/keys.ts
20
9
  var expenseKeys = {
@@ -28,40 +17,29 @@ function useExpenses({ userId, filters }, options) {
28
17
  return reactQuery.useQuery({
29
18
  queryKey: expenseKeys.list(userId, filters),
30
19
  queryFn: async () => {
31
- let validatedFilters = filters;
32
20
  if (filters) {
33
- validatedFilters = shared.ExpenseSearchParamsSchema.parse(filters);
34
- }
35
- const params = new URLSearchParams();
36
- if (validatedFilters?.begin_on) {
37
- params.append("begin_on", validatedFilters.begin_on);
38
- }
39
- if (validatedFilters?.end_on) {
40
- params.append("end_on", validatedFilters.end_on);
21
+ shared.ExpenseSearchParamsSchema.parse(filters);
41
22
  }
42
- if (validatedFilters?.threshold !== void 0) {
43
- params.append("threshold", validatedFilters.threshold.toString());
23
+ let query = shared.supabase.from("expenses_by_tag").select("*").eq("user_id", userId);
24
+ if (filters?.threshold !== void 0) {
25
+ query = query.gte("amount", filters.threshold);
44
26
  }
45
- const queryString = params.toString();
46
- const url = `/users/${userId}/expenses${queryString ? `?${queryString}` : ""}`;
47
- const response = await api.get(url);
48
- const validated = shared.ExpensesResponseSchema.parse(response.data);
49
- return validated.expenses;
27
+ const { data, error } = await query;
28
+ if (error) throw new Error(error.message);
29
+ const rows = shared.ExpensesViewResponseSchema.parse(data);
30
+ return rows.filter(
31
+ (r) => r.tag != null && r.amount != null
32
+ ).map((r) => ({ tag: r.tag, amount: r.amount.toFixed(2) }));
50
33
  },
51
34
  staleTime: 1e3 * 60 * 5,
52
- // 5 minutes (aggregated data, moderate update frequency)
53
35
  ...options
54
36
  });
55
37
  }
56
38
  function useCreateExpense(options) {
57
39
  const queryClient = reactQuery.useQueryClient();
58
- const { mode } = shared.useAppMode();
59
40
  return reactQuery.useMutation({
60
- mutationFn: async ({ userId, data }) => {
61
- const schema = mode === "admin" ? shared.ExpenseCreateSchemaAdmin : shared.ExpenseCreateSchemaUser;
62
- const validated = schema.parse(data);
63
- const response = await api.post(`/users/${userId}/expenses`, validated);
64
- return shared.ExpenseSchema.parse(response.data);
41
+ mutationFn: async (_params) => {
42
+ throw new Error("Expenses are read-only (computed from transactions)");
65
43
  },
66
44
  onSuccess: (_, { userId }) => {
67
45
  queryClient.invalidateQueries({
@@ -73,16 +51,9 @@ function useCreateExpense(options) {
73
51
  }
74
52
  function useUpdateExpense(options) {
75
53
  const queryClient = reactQuery.useQueryClient();
76
- const { mode } = shared.useAppMode();
77
54
  return reactQuery.useMutation({
78
- mutationFn: async ({ userId, tag, data }) => {
79
- const schema = mode === "admin" ? shared.ExpenseUpdateSchemaAdmin : shared.ExpenseUpdateSchemaUser;
80
- const validated = schema.parse(data);
81
- const response = await api.put(
82
- `/users/${userId}/expenses/${encodeURIComponent(tag)}`,
83
- validated
84
- );
85
- return shared.ExpenseSchema.parse(response.data);
55
+ mutationFn: async (_params) => {
56
+ throw new Error("Expenses are read-only (computed from transactions)");
86
57
  },
87
58
  onSuccess: (_, { userId }) => {
88
59
  queryClient.invalidateQueries({
@@ -95,9 +66,8 @@ function useUpdateExpense(options) {
95
66
  function useDeleteExpense(options) {
96
67
  const queryClient = reactQuery.useQueryClient();
97
68
  return reactQuery.useMutation({
98
- mutationFn: async ({ userId, tag }) => {
99
- const validated = shared.ExpenseDeleteSchema.parse({ tag });
100
- await api.delete(`/users/${userId}/expenses/${encodeURIComponent(validated.tag)}`);
69
+ mutationFn: async (_params) => {
70
+ throw new Error("Expenses are read-only (computed from transactions)");
101
71
  },
102
72
  onSuccess: (_, { userId }) => {
103
73
  queryClient.invalidateQueries({
@@ -108,7 +78,6 @@ function useDeleteExpense(options) {
108
78
  });
109
79
  }
110
80
 
111
- exports.api = api;
112
81
  exports.expenseKeys = expenseKeys;
113
82
  exports.useCreateExpense = useCreateExpense;
114
83
  exports.useDeleteExpense = useDeleteExpense;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/keys.ts","../src/queries/useExpenses.ts","../src/mutations/useCreateExpense.ts","../src/mutations/useUpdateExpense.ts","../src/mutations/useDeleteExpense.ts"],"names":["axios","useQuery","ExpenseSearchParamsSchema","ExpensesResponseSchema","useQueryClient","useAppMode","useMutation","ExpenseCreateSchemaAdmin","ExpenseCreateSchemaUser","ExpenseSchema","ExpenseUpdateSchemaAdmin","ExpenseUpdateSchemaUser","ExpenseDeleteSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["import axios from 'axios';\n\n/**\n * Axios instance for Expenses API\n * Base URL configured through environment variables or defaults to relative path\n */\nexport const api = axios.create({\n baseURL: '/api',\n headers: {\n 'Content-Type': 'application/json',\n },\n});\n","export const expenseKeys = {\n all: ['expenses'] as const,\n lists: () => [...expenseKeys.all, 'list'] as const,\n list: (\n userId: string,\n filters?: { begin_on?: string; end_on?: string; threshold?: number }\n ) => [...expenseKeys.lists(), userId, filters] as const,\n};\n","import { useQuery } from '@tanstack/react-query';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport {\n ExpensesResponseSchema,\n ExpenseSearchParamsSchema,\n type Expense,\n type ExpenseSearchParams,\n} from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\ninterface UseExpensesParams {\n userId: string;\n filters?: ExpenseSearchParams;\n}\n\n/**\n * Fetch expenses for a user with optional filters\n *\n * @param params - User ID and optional filters (begin_on, end_on, threshold)\n * @param options - Additional useQuery options\n * @returns Query result with expenses array\n */\nexport function useExpenses(\n { userId, filters }: UseExpensesParams,\n options?: Omit<UseQueryOptions<Expense[]>, 'queryKey' | 'queryFn'>\n) {\n return useQuery({\n queryKey: expenseKeys.list(userId, filters),\n queryFn: async () => {\n // Validate filters if provided\n let validatedFilters = filters;\n if (filters) {\n validatedFilters = ExpenseSearchParamsSchema.parse(filters);\n }\n\n const params = new URLSearchParams();\n\n if (validatedFilters?.begin_on) {\n params.append('begin_on', validatedFilters.begin_on);\n }\n if (validatedFilters?.end_on) {\n params.append('end_on', validatedFilters.end_on);\n }\n if (validatedFilters?.threshold !== undefined) {\n params.append('threshold', validatedFilters.threshold.toString());\n }\n\n const queryString = params.toString();\n const url = `/users/${userId}/expenses${queryString ? `?${queryString}` : ''}`;\n\n const response = await api.get(url);\n const validated = ExpensesResponseSchema.parse(response.data);\n return validated.expenses;\n },\n staleTime: 1000 * 60 * 5, // 5 minutes (aggregated data, moderate update frequency)\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport {\n ExpenseCreateSchemaAdmin,\n ExpenseCreateSchemaUser,\n ExpenseSchema,\n useAppMode,\n type Expense,\n} from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\n/**\n * Expense create data interface\n */\nexport interface ExpenseCreate {\n tag: string;\n amount: string;\n}\n\n/**\n * Parameters for useCreateExpense mutation\n */\nexport interface CreateExpenseParams {\n userId: string;\n data: ExpenseCreate;\n}\n\n/**\n * Mutation hook for creating a new expense\n *\n * Creates an expense record manually for testing purposes.\n * Uses mode switching for validation:\n * - Admin mode: Allows manual expense creation for test data\n * - User mode: Blocked (expenses are computed from transactions)\n *\n * @example\n * ```tsx\n * const createExpense = useCreateExpense();\n *\n * createExpense.mutate({\n * userId: 'user123',\n * data: {\n * tag: 'groceries',\n * amount: '123.45'\n * }\n * });\n * ```\n */\nexport function useCreateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n const { mode } = useAppMode();\n\n return useMutation({\n mutationFn: async ({ userId, data }: CreateExpenseParams) => {\n // Select schema based on mode\n const schema =\n mode === 'admin'\n ? ExpenseCreateSchemaAdmin\n : ExpenseCreateSchemaUser;\n\n // Validate expense data\n const validated = schema.parse(data);\n\n // POST to create new expense\n const response = await api.post(`/users/${userId}/expenses`, validated);\n\n // Return created expense\n return ExpenseSchema.parse(response.data);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate expenses query to refetch updated list\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport {\n ExpenseUpdateSchemaAdmin,\n ExpenseUpdateSchemaUser,\n ExpenseSchema,\n useAppMode,\n type Expense,\n} from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\n/**\n * Expense update data interface\n */\nexport interface ExpenseUpdate {\n amount?: string;\n}\n\n/**\n * Parameters for useUpdateExpense mutation\n */\nexport interface UpdateExpenseParams {\n userId: string;\n tag: string;\n data: ExpenseUpdate;\n}\n\n/**\n * Mutation hook for updating an expense\n *\n * Updates expense fields for testing purposes.\n * Uses mode switching for validation:\n * - Admin mode: Allows updating expenses for test scenarios\n * - User mode: Blocked (expenses are computed from transactions)\n *\n * @example\n * ```tsx\n * const updateExpense = useUpdateExpense();\n *\n * updateExpense.mutate({\n * userId: 'user123',\n * tag: 'groceries',\n * data: {\n * amount: '200.00'\n * }\n * });\n * ```\n */\nexport function useUpdateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n const { mode } = useAppMode();\n\n return useMutation({\n mutationFn: async ({ userId, tag, data }: UpdateExpenseParams) => {\n // Select schema based on mode\n const schema =\n mode === 'admin'\n ? ExpenseUpdateSchemaAdmin\n : ExpenseUpdateSchemaUser;\n\n // Validate expense update data\n const validated = schema.parse(data);\n\n // PUT to update expense\n const response = await api.put(\n `/users/${userId}/expenses/${encodeURIComponent(tag)}`,\n validated\n );\n\n // Return updated expense\n return ExpenseSchema.parse(response.data);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate expenses query to refetch updated list\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport { ExpenseDeleteSchema } from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\n/**\n * Parameters for useDeleteExpense mutation\n */\nexport interface DeleteExpenseParams {\n userId: string;\n tag: string;\n}\n\n/**\n * Mutation hook for deleting an expense\n *\n * Deletes an expense record from the system.\n * Available in both admin and user modes (for test data cleanup).\n *\n * @example\n * ```tsx\n * const deleteExpense = useDeleteExpense();\n *\n * deleteExpense.mutate({\n * userId: 'user123',\n * tag: 'groceries'\n * });\n * ```\n */\nexport function useDeleteExpense(\n options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, tag }: DeleteExpenseParams) => {\n // Validate expense tag\n const validated = ExpenseDeleteSchema.parse({ tag });\n\n // DELETE expense\n await api.delete(`/users/${userId}/expenses/${encodeURIComponent(validated.tag)}`);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate expenses query to refetch updated list\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/keys.ts","../src/queries/useExpenses.ts","../src/mutations/useCreateExpense.ts","../src/mutations/useUpdateExpense.ts","../src/mutations/useDeleteExpense.ts"],"names":["useQuery","ExpenseSearchParamsSchema","supabase","ExpensesViewResponseSchema","useQueryClient","useMutation"],"mappings":"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","file":"index.cjs","sourcesContent":["export const expenseKeys = {\n all: ['expenses'] as const,\n lists: () => [...expenseKeys.all, 'list'] as const,\n list: (\n userId: string,\n filters?: { begin_on?: string; end_on?: string; threshold?: number }\n ) => [...expenseKeys.lists(), userId, filters] as const,\n};\n","import { useQuery } from '@tanstack/react-query';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport {\n ExpensesViewResponseSchema,\n ExpenseSearchParamsSchema,\n type Expense,\n type ExpenseSearchParams,\n} from '@pfm-platform/shared';\nimport { supabase } from '../client';\nimport { expenseKeys } from '../keys';\n\ninterface UseExpensesParams {\n userId: string;\n filters?: ExpenseSearchParams;\n}\n\n/**\n * Fetch expenses for a user from expenses_by_tag Supabase VIEW\n *\n * The view aggregates debit transactions by tag. All data is read-only.\n * Amount comes back as number from the view and is converted to string format.\n *\n * @param params - User ID and optional filters (threshold only; begin_on/end_on are no-ops)\n * @param options - Additional useQuery options\n * @returns Query result with expenses array\n */\nexport function useExpenses(\n { userId, filters }: UseExpensesParams,\n options?: Omit<UseQueryOptions<Expense[]>, 'queryKey' | 'queryFn'>\n) {\n return useQuery({\n queryKey: expenseKeys.list(userId, filters),\n queryFn: async () => {\n // Validate filters if provided\n if (filters) {\n ExpenseSearchParamsSchema.parse(filters);\n }\n\n let query = supabase\n .from('expenses_by_tag')\n .select('*')\n .eq('user_id', userId);\n\n // Apply threshold filter if provided (view has amount column)\n if (filters?.threshold !== undefined) {\n query = query.gte('amount', filters.threshold);\n }\n\n const { data, error } = await query;\n\n if (error) throw new Error(error.message);\n\n // Validate raw view data (nullable columns)\n const rows = ExpensesViewResponseSchema.parse(data);\n\n // Transform: filter out nulls, convert amount number → string with 2 decimal places\n return rows\n .filter(\n (r): r is { user_id: string; tag: string; amount: number } =>\n r.tag != null && r.amount != null\n )\n .map((r) => ({ tag: r.tag, amount: r.amount.toFixed(2) }));\n },\n staleTime: 1000 * 60 * 5,\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport type { Expense } from '@pfm-platform/shared';\nimport { expenseKeys } from '../keys';\n\nexport interface ExpenseCreate {\n tag: string;\n amount: string;\n}\n\nexport interface CreateExpenseParams {\n userId: string;\n data: ExpenseCreate;\n}\n\n/**\n * Stub mutation — expenses are read-only (computed from transactions via view).\n * Kept exported so UI components that import it still compile.\n */\nexport function useCreateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (_params: CreateExpenseParams): Promise<Expense> => {\n throw new Error('Expenses are read-only (computed from transactions)');\n },\n onSuccess: (_, { userId }) => {\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport type { Expense } from '@pfm-platform/shared';\nimport { expenseKeys } from '../keys';\n\nexport interface ExpenseUpdate {\n amount?: string;\n}\n\nexport interface UpdateExpenseParams {\n userId: string;\n tag: string;\n data: ExpenseUpdate;\n}\n\n/**\n * Stub mutation — expenses are read-only (computed from transactions via view).\n * Kept exported so UI components that import it still compile.\n */\nexport function useUpdateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (_params: UpdateExpenseParams): Promise<Expense> => {\n throw new Error('Expenses are read-only (computed from transactions)');\n },\n onSuccess: (_, { userId }) => {\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport { expenseKeys } from '../keys';\n\nexport interface DeleteExpenseParams {\n userId: string;\n tag: string;\n}\n\n/**\n * Stub mutation — expenses are read-only (computed from transactions via view).\n * Kept exported so UI components that import it still compile.\n */\nexport function useDeleteExpense(\n options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (_params: DeleteExpenseParams): Promise<void> => {\n throw new Error('Expenses are read-only (computed from transactions)');\n },\n onSuccess: (_, { userId }) => {\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,16 +1,18 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
2
  import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
3
3
  import { ExpenseSearchParams, Expense } from '@pfm-platform/shared';
4
- import * as axios from 'axios';
5
4
 
6
5
  interface UseExpensesParams {
7
6
  userId: string;
8
7
  filters?: ExpenseSearchParams;
9
8
  }
10
9
  /**
11
- * Fetch expenses for a user with optional filters
10
+ * Fetch expenses for a user from expenses_by_tag Supabase VIEW
12
11
  *
13
- * @param params - User ID and optional filters (begin_on, end_on, threshold)
12
+ * The view aggregates debit transactions by tag. All data is read-only.
13
+ * Amount comes back as number from the view and is converted to string format.
14
+ *
15
+ * @param params - User ID and optional filters (threshold only; begin_on/end_on are no-ops)
14
16
  * @param options - Additional useQuery options
15
17
  * @returns Query result with expenses array
16
18
  */
@@ -19,108 +21,47 @@ declare function useExpenses({ userId, filters }: UseExpensesParams, options?: O
19
21
  amount: string;
20
22
  }[], Error>;
21
23
 
22
- /**
23
- * Expense create data interface
24
- */
25
24
  interface ExpenseCreate {
26
25
  tag: string;
27
26
  amount: string;
28
27
  }
29
- /**
30
- * Parameters for useCreateExpense mutation
31
- */
32
28
  interface CreateExpenseParams {
33
29
  userId: string;
34
30
  data: ExpenseCreate;
35
31
  }
36
32
  /**
37
- * Mutation hook for creating a new expense
38
- *
39
- * Creates an expense record manually for testing purposes.
40
- * Uses mode switching for validation:
41
- * - Admin mode: Allows manual expense creation for test data
42
- * - User mode: Blocked (expenses are computed from transactions)
43
- *
44
- * @example
45
- * ```tsx
46
- * const createExpense = useCreateExpense();
47
- *
48
- * createExpense.mutate({
49
- * userId: 'user123',
50
- * data: {
51
- * tag: 'groceries',
52
- * amount: '123.45'
53
- * }
54
- * });
55
- * ```
33
+ * Stub mutation expenses are read-only (computed from transactions via view).
34
+ * Kept exported so UI components that import it still compile.
56
35
  */
57
36
  declare function useCreateExpense(options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
58
37
  tag: string;
59
38
  amount: string;
60
39
  }, Error, CreateExpenseParams, unknown>;
61
40
 
62
- /**
63
- * Expense update data interface
64
- */
65
41
  interface ExpenseUpdate {
66
42
  amount?: string;
67
43
  }
68
- /**
69
- * Parameters for useUpdateExpense mutation
70
- */
71
44
  interface UpdateExpenseParams {
72
45
  userId: string;
73
46
  tag: string;
74
47
  data: ExpenseUpdate;
75
48
  }
76
49
  /**
77
- * Mutation hook for updating an expense
78
- *
79
- * Updates expense fields for testing purposes.
80
- * Uses mode switching for validation:
81
- * - Admin mode: Allows updating expenses for test scenarios
82
- * - User mode: Blocked (expenses are computed from transactions)
83
- *
84
- * @example
85
- * ```tsx
86
- * const updateExpense = useUpdateExpense();
87
- *
88
- * updateExpense.mutate({
89
- * userId: 'user123',
90
- * tag: 'groceries',
91
- * data: {
92
- * amount: '200.00'
93
- * }
94
- * });
95
- * ```
50
+ * Stub mutation expenses are read-only (computed from transactions via view).
51
+ * Kept exported so UI components that import it still compile.
96
52
  */
97
53
  declare function useUpdateExpense(options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
98
54
  tag: string;
99
55
  amount: string;
100
56
  }, Error, UpdateExpenseParams, unknown>;
101
57
 
102
- /**
103
- * Parameters for useDeleteExpense mutation
104
- */
105
58
  interface DeleteExpenseParams {
106
59
  userId: string;
107
60
  tag: string;
108
61
  }
109
62
  /**
110
- * Mutation hook for deleting an expense
111
- *
112
- * Deletes an expense record from the system.
113
- * Available in both admin and user modes (for test data cleanup).
114
- *
115
- * @example
116
- * ```tsx
117
- * const deleteExpense = useDeleteExpense();
118
- *
119
- * deleteExpense.mutate({
120
- * userId: 'user123',
121
- * tag: 'groceries'
122
- * });
123
- * ```
63
+ * Stub mutation expenses are read-only (computed from transactions via view).
64
+ * Kept exported so UI components that import it still compile.
124
65
  */
125
66
  declare function useDeleteExpense(options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<void, Error, DeleteExpenseParams, unknown>;
126
67
 
@@ -138,10 +79,4 @@ declare const expenseKeys: {
138
79
  } | undefined];
139
80
  };
140
81
 
141
- /**
142
- * Axios instance for Expenses API
143
- * Base URL configured through environment variables or defaults to relative path
144
- */
145
- declare const api: axios.AxiosInstance;
146
-
147
- export { type CreateExpenseParams, type DeleteExpenseParams, type ExpenseCreate, type ExpenseUpdate, type UpdateExpenseParams, api, expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
82
+ export { type CreateExpenseParams, type DeleteExpenseParams, type ExpenseCreate, type ExpenseUpdate, type UpdateExpenseParams, expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,18 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
2
  import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
3
3
  import { ExpenseSearchParams, Expense } from '@pfm-platform/shared';
4
- import * as axios from 'axios';
5
4
 
6
5
  interface UseExpensesParams {
7
6
  userId: string;
8
7
  filters?: ExpenseSearchParams;
9
8
  }
10
9
  /**
11
- * Fetch expenses for a user with optional filters
10
+ * Fetch expenses for a user from expenses_by_tag Supabase VIEW
12
11
  *
13
- * @param params - User ID and optional filters (begin_on, end_on, threshold)
12
+ * The view aggregates debit transactions by tag. All data is read-only.
13
+ * Amount comes back as number from the view and is converted to string format.
14
+ *
15
+ * @param params - User ID and optional filters (threshold only; begin_on/end_on are no-ops)
14
16
  * @param options - Additional useQuery options
15
17
  * @returns Query result with expenses array
16
18
  */
@@ -19,108 +21,47 @@ declare function useExpenses({ userId, filters }: UseExpensesParams, options?: O
19
21
  amount: string;
20
22
  }[], Error>;
21
23
 
22
- /**
23
- * Expense create data interface
24
- */
25
24
  interface ExpenseCreate {
26
25
  tag: string;
27
26
  amount: string;
28
27
  }
29
- /**
30
- * Parameters for useCreateExpense mutation
31
- */
32
28
  interface CreateExpenseParams {
33
29
  userId: string;
34
30
  data: ExpenseCreate;
35
31
  }
36
32
  /**
37
- * Mutation hook for creating a new expense
38
- *
39
- * Creates an expense record manually for testing purposes.
40
- * Uses mode switching for validation:
41
- * - Admin mode: Allows manual expense creation for test data
42
- * - User mode: Blocked (expenses are computed from transactions)
43
- *
44
- * @example
45
- * ```tsx
46
- * const createExpense = useCreateExpense();
47
- *
48
- * createExpense.mutate({
49
- * userId: 'user123',
50
- * data: {
51
- * tag: 'groceries',
52
- * amount: '123.45'
53
- * }
54
- * });
55
- * ```
33
+ * Stub mutation expenses are read-only (computed from transactions via view).
34
+ * Kept exported so UI components that import it still compile.
56
35
  */
57
36
  declare function useCreateExpense(options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
58
37
  tag: string;
59
38
  amount: string;
60
39
  }, Error, CreateExpenseParams, unknown>;
61
40
 
62
- /**
63
- * Expense update data interface
64
- */
65
41
  interface ExpenseUpdate {
66
42
  amount?: string;
67
43
  }
68
- /**
69
- * Parameters for useUpdateExpense mutation
70
- */
71
44
  interface UpdateExpenseParams {
72
45
  userId: string;
73
46
  tag: string;
74
47
  data: ExpenseUpdate;
75
48
  }
76
49
  /**
77
- * Mutation hook for updating an expense
78
- *
79
- * Updates expense fields for testing purposes.
80
- * Uses mode switching for validation:
81
- * - Admin mode: Allows updating expenses for test scenarios
82
- * - User mode: Blocked (expenses are computed from transactions)
83
- *
84
- * @example
85
- * ```tsx
86
- * const updateExpense = useUpdateExpense();
87
- *
88
- * updateExpense.mutate({
89
- * userId: 'user123',
90
- * tag: 'groceries',
91
- * data: {
92
- * amount: '200.00'
93
- * }
94
- * });
95
- * ```
50
+ * Stub mutation expenses are read-only (computed from transactions via view).
51
+ * Kept exported so UI components that import it still compile.
96
52
  */
97
53
  declare function useUpdateExpense(options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
98
54
  tag: string;
99
55
  amount: string;
100
56
  }, Error, UpdateExpenseParams, unknown>;
101
57
 
102
- /**
103
- * Parameters for useDeleteExpense mutation
104
- */
105
58
  interface DeleteExpenseParams {
106
59
  userId: string;
107
60
  tag: string;
108
61
  }
109
62
  /**
110
- * Mutation hook for deleting an expense
111
- *
112
- * Deletes an expense record from the system.
113
- * Available in both admin and user modes (for test data cleanup).
114
- *
115
- * @example
116
- * ```tsx
117
- * const deleteExpense = useDeleteExpense();
118
- *
119
- * deleteExpense.mutate({
120
- * userId: 'user123',
121
- * tag: 'groceries'
122
- * });
123
- * ```
63
+ * Stub mutation expenses are read-only (computed from transactions via view).
64
+ * Kept exported so UI components that import it still compile.
124
65
  */
125
66
  declare function useDeleteExpense(options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<void, Error, DeleteExpenseParams, unknown>;
126
67
 
@@ -138,10 +79,4 @@ declare const expenseKeys: {
138
79
  } | undefined];
139
80
  };
140
81
 
141
- /**
142
- * Axios instance for Expenses API
143
- * Base URL configured through environment variables or defaults to relative path
144
- */
145
- declare const api: axios.AxiosInstance;
146
-
147
- export { type CreateExpenseParams, type DeleteExpenseParams, type ExpenseCreate, type ExpenseUpdate, type UpdateExpenseParams, api, expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
82
+ export { type CreateExpenseParams, type DeleteExpenseParams, type ExpenseCreate, type ExpenseUpdate, type UpdateExpenseParams, expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
package/dist/index.js CHANGED
@@ -1,14 +1,7 @@
1
1
  import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
2
- import { ExpenseSearchParamsSchema, ExpensesResponseSchema, useAppMode, ExpenseCreateSchemaAdmin, ExpenseCreateSchemaUser, ExpenseSchema, ExpenseUpdateSchemaAdmin, ExpenseUpdateSchemaUser, ExpenseDeleteSchema } from '@pfm-platform/shared';
3
- import axios from 'axios';
2
+ import { ExpenseSearchParamsSchema, supabase, ExpensesViewResponseSchema } from '@pfm-platform/shared';
4
3
 
5
4
  // src/queries/useExpenses.ts
6
- var api = axios.create({
7
- baseURL: "/api",
8
- headers: {
9
- "Content-Type": "application/json"
10
- }
11
- });
12
5
 
13
6
  // src/keys.ts
14
7
  var expenseKeys = {
@@ -22,40 +15,29 @@ function useExpenses({ userId, filters }, options) {
22
15
  return useQuery({
23
16
  queryKey: expenseKeys.list(userId, filters),
24
17
  queryFn: async () => {
25
- let validatedFilters = filters;
26
18
  if (filters) {
27
- validatedFilters = ExpenseSearchParamsSchema.parse(filters);
19
+ ExpenseSearchParamsSchema.parse(filters);
28
20
  }
29
- const params = new URLSearchParams();
30
- if (validatedFilters?.begin_on) {
31
- params.append("begin_on", validatedFilters.begin_on);
21
+ let query = supabase.from("expenses_by_tag").select("*").eq("user_id", userId);
22
+ if (filters?.threshold !== void 0) {
23
+ query = query.gte("amount", filters.threshold);
32
24
  }
33
- if (validatedFilters?.end_on) {
34
- params.append("end_on", validatedFilters.end_on);
35
- }
36
- if (validatedFilters?.threshold !== void 0) {
37
- params.append("threshold", validatedFilters.threshold.toString());
38
- }
39
- const queryString = params.toString();
40
- const url = `/users/${userId}/expenses${queryString ? `?${queryString}` : ""}`;
41
- const response = await api.get(url);
42
- const validated = ExpensesResponseSchema.parse(response.data);
43
- return validated.expenses;
25
+ const { data, error } = await query;
26
+ if (error) throw new Error(error.message);
27
+ const rows = ExpensesViewResponseSchema.parse(data);
28
+ return rows.filter(
29
+ (r) => r.tag != null && r.amount != null
30
+ ).map((r) => ({ tag: r.tag, amount: r.amount.toFixed(2) }));
44
31
  },
45
32
  staleTime: 1e3 * 60 * 5,
46
- // 5 minutes (aggregated data, moderate update frequency)
47
33
  ...options
48
34
  });
49
35
  }
50
36
  function useCreateExpense(options) {
51
37
  const queryClient = useQueryClient();
52
- const { mode } = useAppMode();
53
38
  return useMutation({
54
- mutationFn: async ({ userId, data }) => {
55
- const schema = mode === "admin" ? ExpenseCreateSchemaAdmin : ExpenseCreateSchemaUser;
56
- const validated = schema.parse(data);
57
- const response = await api.post(`/users/${userId}/expenses`, validated);
58
- return ExpenseSchema.parse(response.data);
39
+ mutationFn: async (_params) => {
40
+ throw new Error("Expenses are read-only (computed from transactions)");
59
41
  },
60
42
  onSuccess: (_, { userId }) => {
61
43
  queryClient.invalidateQueries({
@@ -67,16 +49,9 @@ function useCreateExpense(options) {
67
49
  }
68
50
  function useUpdateExpense(options) {
69
51
  const queryClient = useQueryClient();
70
- const { mode } = useAppMode();
71
52
  return useMutation({
72
- mutationFn: async ({ userId, tag, data }) => {
73
- const schema = mode === "admin" ? ExpenseUpdateSchemaAdmin : ExpenseUpdateSchemaUser;
74
- const validated = schema.parse(data);
75
- const response = await api.put(
76
- `/users/${userId}/expenses/${encodeURIComponent(tag)}`,
77
- validated
78
- );
79
- return ExpenseSchema.parse(response.data);
53
+ mutationFn: async (_params) => {
54
+ throw new Error("Expenses are read-only (computed from transactions)");
80
55
  },
81
56
  onSuccess: (_, { userId }) => {
82
57
  queryClient.invalidateQueries({
@@ -89,9 +64,8 @@ function useUpdateExpense(options) {
89
64
  function useDeleteExpense(options) {
90
65
  const queryClient = useQueryClient();
91
66
  return useMutation({
92
- mutationFn: async ({ userId, tag }) => {
93
- const validated = ExpenseDeleteSchema.parse({ tag });
94
- await api.delete(`/users/${userId}/expenses/${encodeURIComponent(validated.tag)}`);
67
+ mutationFn: async (_params) => {
68
+ throw new Error("Expenses are read-only (computed from transactions)");
95
69
  },
96
70
  onSuccess: (_, { userId }) => {
97
71
  queryClient.invalidateQueries({
@@ -102,6 +76,6 @@ function useDeleteExpense(options) {
102
76
  });
103
77
  }
104
78
 
105
- export { api, expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
79
+ export { expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
106
80
  //# sourceMappingURL=index.js.map
107
81
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/keys.ts","../src/queries/useExpenses.ts","../src/mutations/useCreateExpense.ts","../src/mutations/useUpdateExpense.ts","../src/mutations/useDeleteExpense.ts"],"names":["useQueryClient","useAppMode","useMutation","ExpenseSchema"],"mappings":";;;;;AAMO,IAAM,GAAA,GAAM,MAAM,MAAA,CAAO;AAAA,EAC9B,OAAA,EAAS,MAAA;AAAA,EACT,OAAA,EAAS;AAAA,IACP,cAAA,EAAgB;AAAA;AAEpB,CAAC;;;ACXM,IAAM,WAAA,GAAc;AAAA,EACzB,GAAA,EAAK,CAAC,UAAU,CAAA;AAAA,EAChB,OAAO,MAAM,CAAC,GAAG,WAAA,CAAY,KAAK,MAAM,CAAA;AAAA,EACxC,IAAA,EAAM,CACJ,MAAA,EACA,OAAA,KACG,CAAC,GAAG,WAAA,CAAY,KAAA,EAAM,EAAG,MAAA,EAAQ,OAAO;AAC/C;;;ACgBO,SAAS,WAAA,CACd,EAAE,MAAA,EAAQ,OAAA,IACV,OAAA,EACA;AACA,EAAA,OAAO,QAAA,CAAS;AAAA,IACd,QAAA,EAAU,WAAA,CAAY,IAAA,CAAK,MAAA,EAAQ,OAAO,CAAA;AAAA,IAC1C,SAAS,YAAY;AAEnB,MAAA,IAAI,gBAAA,GAAmB,OAAA;AACvB,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,gBAAA,GAAmB,yBAAA,CAA0B,MAAM,OAAO,CAAA;AAAA,MAC5D;AAEA,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AAEnC,MAAA,IAAI,kBAAkB,QAAA,EAAU;AAC9B,QAAA,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,gBAAA,CAAiB,QAAQ,CAAA;AAAA,MACrD;AACA,MAAA,IAAI,kBAAkB,MAAA,EAAQ;AAC5B,QAAA,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,gBAAA,CAAiB,MAAM,CAAA;AAAA,MACjD;AACA,MAAA,IAAI,gBAAA,EAAkB,cAAc,MAAA,EAAW;AAC7C,QAAA,MAAA,CAAO,MAAA,CAAO,WAAA,EAAa,gBAAA,CAAiB,SAAA,CAAU,UAAU,CAAA;AAAA,MAClE;AAEA,MAAA,MAAM,WAAA,GAAc,OAAO,QAAA,EAAS;AACpC,MAAA,MAAM,GAAA,GAAM,UAAU,MAAM,CAAA,SAAA,EAAY,cAAc,CAAA,CAAA,EAAI,WAAW,KAAK,EAAE,CAAA,CAAA;AAE5E,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAClC,MAAA,MAAM,SAAA,GAAY,sBAAA,CAAuB,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA;AAC5D,MAAA,OAAO,SAAA,CAAU,QAAA;AAAA,IACnB,CAAA;AAAA,IACA,SAAA,EAAW,MAAO,EAAA,GAAK,CAAA;AAAA;AAAA,IACvB,GAAG;AAAA,GACJ,CAAA;AACH;ACNO,SAAS,iBACd,OAAA,EACA;AACA,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,UAAA,EAAW;AAE5B,EAAA,OAAO,WAAA,CAAY;AAAA,IACjB,UAAA,EAAY,OAAO,EAAE,MAAA,EAAQ,MAAK,KAA2B;AAE3D,MAAA,MAAM,MAAA,GACJ,IAAA,KAAS,OAAA,GACL,wBAAA,GACA,uBAAA;AAGN,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAGnC,MAAA,MAAM,WAAW,MAAM,GAAA,CAAI,KAAK,CAAA,OAAA,EAAU,MAAM,aAAa,SAAS,CAAA;AAGtE,MAAA,OAAO,aAAA,CAAc,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA;AAAA,IAC1C,CAAA;AAAA,IACA,SAAA,EAAW,CAAC,CAAA,EAAG,EAAE,QAAO,KAAM;AAE5B,MAAA,WAAA,CAAY,iBAAA,CAAkB;AAAA,QAC5B,QAAA,EAAU,WAAA,CAAY,IAAA,CAAK,MAAM;AAAA,OAClC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,GAAG;AAAA,GACJ,CAAA;AACH;AC/BO,SAAS,iBACd,OAAA,EACA;AACA,EAAA,MAAM,cAAcA,cAAAA,EAAe;AACnC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,UAAAA,EAAW;AAE5B,EAAA,OAAOC,WAAAA,CAAY;AAAA,IACjB,YAAY,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,MAAK,KAA2B;AAEhE,MAAA,MAAM,MAAA,GACJ,IAAA,KAAS,OAAA,GACL,wBAAA,GACA,uBAAA;AAGN,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAGnC,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,GAAA;AAAA,QACzB,CAAA,OAAA,EAAU,MAAM,CAAA,UAAA,EAAa,kBAAA,CAAmB,GAAG,CAAC,CAAA,CAAA;AAAA,QACpD;AAAA,OACF;AAGA,MAAA,OAAOC,aAAAA,CAAc,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA;AAAA,IAC1C,CAAA;AAAA,IACA,SAAA,EAAW,CAAC,CAAA,EAAG,EAAE,QAAO,KAAM;AAE5B,MAAA,WAAA,CAAY,iBAAA,CAAkB;AAAA,QAC5B,QAAA,EAAU,WAAA,CAAY,IAAA,CAAK,MAAM;AAAA,OAClC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,GAAG;AAAA,GACJ,CAAA;AACH;ACrDO,SAAS,iBACd,OAAA,EACA;AACA,EAAA,MAAM,cAAcH,cAAAA,EAAe;AAEnC,EAAA,OAAOE,WAAAA,CAAY;AAAA,IACjB,UAAA,EAAY,OAAO,EAAE,MAAA,EAAQ,KAAI,KAA2B;AAE1D,MAAA,MAAM,SAAA,GAAY,mBAAA,CAAoB,KAAA,CAAM,EAAE,KAAK,CAAA;AAGnD,MAAA,MAAM,GAAA,CAAI,OAAO,CAAA,OAAA,EAAU,MAAM,aAAa,kBAAA,CAAmB,SAAA,CAAU,GAAG,CAAC,CAAA,CAAE,CAAA;AAAA,IACnF,CAAA;AAAA,IACA,SAAA,EAAW,CAAC,CAAA,EAAG,EAAE,QAAO,KAAM;AAE5B,MAAA,WAAA,CAAY,iBAAA,CAAkB;AAAA,QAC5B,QAAA,EAAU,WAAA,CAAY,IAAA,CAAK,MAAM;AAAA,OAClC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,GAAG;AAAA,GACJ,CAAA;AACH","file":"index.js","sourcesContent":["import axios from 'axios';\n\n/**\n * Axios instance for Expenses API\n * Base URL configured through environment variables or defaults to relative path\n */\nexport const api = axios.create({\n baseURL: '/api',\n headers: {\n 'Content-Type': 'application/json',\n },\n});\n","export const expenseKeys = {\n all: ['expenses'] as const,\n lists: () => [...expenseKeys.all, 'list'] as const,\n list: (\n userId: string,\n filters?: { begin_on?: string; end_on?: string; threshold?: number }\n ) => [...expenseKeys.lists(), userId, filters] as const,\n};\n","import { useQuery } from '@tanstack/react-query';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport {\n ExpensesResponseSchema,\n ExpenseSearchParamsSchema,\n type Expense,\n type ExpenseSearchParams,\n} from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\ninterface UseExpensesParams {\n userId: string;\n filters?: ExpenseSearchParams;\n}\n\n/**\n * Fetch expenses for a user with optional filters\n *\n * @param params - User ID and optional filters (begin_on, end_on, threshold)\n * @param options - Additional useQuery options\n * @returns Query result with expenses array\n */\nexport function useExpenses(\n { userId, filters }: UseExpensesParams,\n options?: Omit<UseQueryOptions<Expense[]>, 'queryKey' | 'queryFn'>\n) {\n return useQuery({\n queryKey: expenseKeys.list(userId, filters),\n queryFn: async () => {\n // Validate filters if provided\n let validatedFilters = filters;\n if (filters) {\n validatedFilters = ExpenseSearchParamsSchema.parse(filters);\n }\n\n const params = new URLSearchParams();\n\n if (validatedFilters?.begin_on) {\n params.append('begin_on', validatedFilters.begin_on);\n }\n if (validatedFilters?.end_on) {\n params.append('end_on', validatedFilters.end_on);\n }\n if (validatedFilters?.threshold !== undefined) {\n params.append('threshold', validatedFilters.threshold.toString());\n }\n\n const queryString = params.toString();\n const url = `/users/${userId}/expenses${queryString ? `?${queryString}` : ''}`;\n\n const response = await api.get(url);\n const validated = ExpensesResponseSchema.parse(response.data);\n return validated.expenses;\n },\n staleTime: 1000 * 60 * 5, // 5 minutes (aggregated data, moderate update frequency)\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport {\n ExpenseCreateSchemaAdmin,\n ExpenseCreateSchemaUser,\n ExpenseSchema,\n useAppMode,\n type Expense,\n} from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\n/**\n * Expense create data interface\n */\nexport interface ExpenseCreate {\n tag: string;\n amount: string;\n}\n\n/**\n * Parameters for useCreateExpense mutation\n */\nexport interface CreateExpenseParams {\n userId: string;\n data: ExpenseCreate;\n}\n\n/**\n * Mutation hook for creating a new expense\n *\n * Creates an expense record manually for testing purposes.\n * Uses mode switching for validation:\n * - Admin mode: Allows manual expense creation for test data\n * - User mode: Blocked (expenses are computed from transactions)\n *\n * @example\n * ```tsx\n * const createExpense = useCreateExpense();\n *\n * createExpense.mutate({\n * userId: 'user123',\n * data: {\n * tag: 'groceries',\n * amount: '123.45'\n * }\n * });\n * ```\n */\nexport function useCreateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n const { mode } = useAppMode();\n\n return useMutation({\n mutationFn: async ({ userId, data }: CreateExpenseParams) => {\n // Select schema based on mode\n const schema =\n mode === 'admin'\n ? ExpenseCreateSchemaAdmin\n : ExpenseCreateSchemaUser;\n\n // Validate expense data\n const validated = schema.parse(data);\n\n // POST to create new expense\n const response = await api.post(`/users/${userId}/expenses`, validated);\n\n // Return created expense\n return ExpenseSchema.parse(response.data);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate expenses query to refetch updated list\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport {\n ExpenseUpdateSchemaAdmin,\n ExpenseUpdateSchemaUser,\n ExpenseSchema,\n useAppMode,\n type Expense,\n} from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\n/**\n * Expense update data interface\n */\nexport interface ExpenseUpdate {\n amount?: string;\n}\n\n/**\n * Parameters for useUpdateExpense mutation\n */\nexport interface UpdateExpenseParams {\n userId: string;\n tag: string;\n data: ExpenseUpdate;\n}\n\n/**\n * Mutation hook for updating an expense\n *\n * Updates expense fields for testing purposes.\n * Uses mode switching for validation:\n * - Admin mode: Allows updating expenses for test scenarios\n * - User mode: Blocked (expenses are computed from transactions)\n *\n * @example\n * ```tsx\n * const updateExpense = useUpdateExpense();\n *\n * updateExpense.mutate({\n * userId: 'user123',\n * tag: 'groceries',\n * data: {\n * amount: '200.00'\n * }\n * });\n * ```\n */\nexport function useUpdateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n const { mode } = useAppMode();\n\n return useMutation({\n mutationFn: async ({ userId, tag, data }: UpdateExpenseParams) => {\n // Select schema based on mode\n const schema =\n mode === 'admin'\n ? ExpenseUpdateSchemaAdmin\n : ExpenseUpdateSchemaUser;\n\n // Validate expense update data\n const validated = schema.parse(data);\n\n // PUT to update expense\n const response = await api.put(\n `/users/${userId}/expenses/${encodeURIComponent(tag)}`,\n validated\n );\n\n // Return updated expense\n return ExpenseSchema.parse(response.data);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate expenses query to refetch updated list\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport { ExpenseDeleteSchema } from '@pfm-platform/shared';\nimport { api } from '../client';\nimport { expenseKeys } from '../keys';\n\n/**\n * Parameters for useDeleteExpense mutation\n */\nexport interface DeleteExpenseParams {\n userId: string;\n tag: string;\n}\n\n/**\n * Mutation hook for deleting an expense\n *\n * Deletes an expense record from the system.\n * Available in both admin and user modes (for test data cleanup).\n *\n * @example\n * ```tsx\n * const deleteExpense = useDeleteExpense();\n *\n * deleteExpense.mutate({\n * userId: 'user123',\n * tag: 'groceries'\n * });\n * ```\n */\nexport function useDeleteExpense(\n options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async ({ userId, tag }: DeleteExpenseParams) => {\n // Validate expense tag\n const validated = ExpenseDeleteSchema.parse({ tag });\n\n // DELETE expense\n await api.delete(`/users/${userId}/expenses/${encodeURIComponent(validated.tag)}`);\n },\n onSuccess: (_, { userId }) => {\n // Invalidate expenses query to refetch updated list\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/keys.ts","../src/queries/useExpenses.ts","../src/mutations/useCreateExpense.ts","../src/mutations/useUpdateExpense.ts","../src/mutations/useDeleteExpense.ts"],"names":["useQueryClient","useMutation"],"mappings":"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","file":"index.js","sourcesContent":["export const expenseKeys = {\n all: ['expenses'] as const,\n lists: () => [...expenseKeys.all, 'list'] as const,\n list: (\n userId: string,\n filters?: { begin_on?: string; end_on?: string; threshold?: number }\n ) => [...expenseKeys.lists(), userId, filters] as const,\n};\n","import { useQuery } from '@tanstack/react-query';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport {\n ExpensesViewResponseSchema,\n ExpenseSearchParamsSchema,\n type Expense,\n type ExpenseSearchParams,\n} from '@pfm-platform/shared';\nimport { supabase } from '../client';\nimport { expenseKeys } from '../keys';\n\ninterface UseExpensesParams {\n userId: string;\n filters?: ExpenseSearchParams;\n}\n\n/**\n * Fetch expenses for a user from expenses_by_tag Supabase VIEW\n *\n * The view aggregates debit transactions by tag. All data is read-only.\n * Amount comes back as number from the view and is converted to string format.\n *\n * @param params - User ID and optional filters (threshold only; begin_on/end_on are no-ops)\n * @param options - Additional useQuery options\n * @returns Query result with expenses array\n */\nexport function useExpenses(\n { userId, filters }: UseExpensesParams,\n options?: Omit<UseQueryOptions<Expense[]>, 'queryKey' | 'queryFn'>\n) {\n return useQuery({\n queryKey: expenseKeys.list(userId, filters),\n queryFn: async () => {\n // Validate filters if provided\n if (filters) {\n ExpenseSearchParamsSchema.parse(filters);\n }\n\n let query = supabase\n .from('expenses_by_tag')\n .select('*')\n .eq('user_id', userId);\n\n // Apply threshold filter if provided (view has amount column)\n if (filters?.threshold !== undefined) {\n query = query.gte('amount', filters.threshold);\n }\n\n const { data, error } = await query;\n\n if (error) throw new Error(error.message);\n\n // Validate raw view data (nullable columns)\n const rows = ExpensesViewResponseSchema.parse(data);\n\n // Transform: filter out nulls, convert amount number → string with 2 decimal places\n return rows\n .filter(\n (r): r is { user_id: string; tag: string; amount: number } =>\n r.tag != null && r.amount != null\n )\n .map((r) => ({ tag: r.tag, amount: r.amount.toFixed(2) }));\n },\n staleTime: 1000 * 60 * 5,\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport type { Expense } from '@pfm-platform/shared';\nimport { expenseKeys } from '../keys';\n\nexport interface ExpenseCreate {\n tag: string;\n amount: string;\n}\n\nexport interface CreateExpenseParams {\n userId: string;\n data: ExpenseCreate;\n}\n\n/**\n * Stub mutation — expenses are read-only (computed from transactions via view).\n * Kept exported so UI components that import it still compile.\n */\nexport function useCreateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (_params: CreateExpenseParams): Promise<Expense> => {\n throw new Error('Expenses are read-only (computed from transactions)');\n },\n onSuccess: (_, { userId }) => {\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport type { Expense } from '@pfm-platform/shared';\nimport { expenseKeys } from '../keys';\n\nexport interface ExpenseUpdate {\n amount?: string;\n}\n\nexport interface UpdateExpenseParams {\n userId: string;\n tag: string;\n data: ExpenseUpdate;\n}\n\n/**\n * Stub mutation — expenses are read-only (computed from transactions via view).\n * Kept exported so UI components that import it still compile.\n */\nexport function useUpdateExpense(\n options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (_params: UpdateExpenseParams): Promise<Expense> => {\n throw new Error('Expenses are read-only (computed from transactions)');\n },\n onSuccess: (_, { userId }) => {\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n","import {\n useMutation,\n useQueryClient,\n type UseMutationOptions,\n} from '@tanstack/react-query';\nimport { expenseKeys } from '../keys';\n\nexport interface DeleteExpenseParams {\n userId: string;\n tag: string;\n}\n\n/**\n * Stub mutation — expenses are read-only (computed from transactions via view).\n * Kept exported so UI components that import it still compile.\n */\nexport function useDeleteExpense(\n options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>\n) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (_params: DeleteExpenseParams): Promise<void> => {\n throw new Error('Expenses are read-only (computed from transactions)');\n },\n onSuccess: (_, { userId }) => {\n queryClient.invalidateQueries({\n queryKey: expenseKeys.list(userId),\n });\n },\n ...options,\n });\n}\n"]}
package/package.json CHANGED
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "name": "@pfm-platform/expenses-data-access",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "dependencies": {
7
- "axios": "^1.13.2",
8
7
  "zod": "4.1.12",
9
- "@pfm-platform/shared": "0.0.1"
8
+ "@pfm-platform/shared": "0.2.1"
10
9
  },
11
10
  "devDependencies": {
12
- "@testing-library/react": "^16.3.0",
13
- "@vitejs/plugin-react": "^5.1.1",
14
- "@vitest/coverage-v8": "^4.0.9",
11
+ "@testing-library/react": "^16.3.2",
12
+ "@vitejs/plugin-react": "^5.1.4",
13
+ "@vitest/coverage-v8": "^4.0.18",
15
14
  "jsdom": "^27.2.0",
16
- "react-dom": "19.2.0",
15
+ "react-dom": "19.2.4",
17
16
  "typescript": "5.9.3",
18
- "vitest": "4.0.9"
17
+ "vitest": "4.0.18"
19
18
  },
20
19
  "module": "./dist/index.js",
21
20
  "types": "./dist/index.d.ts",