@pfm-platform/expenses-data-access 0.2.0 → 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 +16 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -77
- package/dist/index.d.ts +12 -77
- package/dist/index.js +18 -44
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
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 (
|
|
61
|
-
|
|
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 (
|
|
79
|
-
|
|
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 (
|
|
99
|
-
|
|
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;
|
package/dist/index.cjs.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":["axios","useQuery","ExpenseSearchParamsSchema","ExpensesResponseSchema","useQueryClient","useAppMode","useMutation","ExpenseCreateSchemaAdmin","ExpenseCreateSchemaUser","ExpenseSchema","ExpenseUpdateSchemaAdmin","ExpenseUpdateSchemaUser","ExpenseDeleteSchema"],"mappings":";;;;;;;;;;;AAMO,IAAM,GAAA,GAAMA,uBAAM,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,OAAOC,mBAAA,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,GAAmBC,gCAAA,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,GAAYC,6BAAA,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,cAAcC,yBAAA,EAAe;AACnC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,iBAAA,EAAW;AAE5B,EAAA,OAAOC,sBAAA,CAAY;AAAA,IACjB,UAAA,EAAY,OAAO,EAAE,MAAA,EAAQ,MAAK,KAA2B;AAE3D,MAAA,MAAM,MAAA,GACJ,IAAA,KAAS,OAAA,GACLC,+BAAA,GACAC,8BAAA;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,OAAOC,oBAAA,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,cAAcL,yBAAAA,EAAe;AACnC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,iBAAAA,EAAW;AAE5B,EAAA,OAAOC,sBAAAA,CAAY;AAAA,IACjB,YAAY,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,MAAK,KAA2B;AAEhE,MAAA,MAAM,MAAA,GACJ,IAAA,KAAS,OAAA,GACLI,+BAAA,GACAC,8BAAA;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,OAAOF,oBAAAA,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,cAAcL,yBAAAA,EAAe;AAEnC,EAAA,OAAOE,sBAAAA,CAAY;AAAA,IACjB,UAAA,EAAY,OAAO,EAAE,MAAA,EAAQ,KAAI,KAA2B;AAE1D,MAAA,MAAM,SAAA,GAAYM,0BAAA,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.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
|
|
10
|
+
* Fetch expenses for a user from expenses_by_tag Supabase VIEW
|
|
12
11
|
*
|
|
13
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
10
|
+
* Fetch expenses for a user from expenses_by_tag Supabase VIEW
|
|
12
11
|
*
|
|
13
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
|
|
19
|
+
ExpenseSearchParamsSchema.parse(filters);
|
|
28
20
|
}
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 (
|
|
55
|
-
|
|
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 (
|
|
73
|
-
|
|
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 (
|
|
93
|
-
|
|
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 {
|
|
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.2.
|
|
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.1
|
|
8
|
+
"@pfm-platform/shared": "0.2.1"
|
|
10
9
|
},
|
|
11
10
|
"devDependencies": {
|
|
12
|
-
"@testing-library/react": "^16.3.
|
|
13
|
-
"@vitejs/plugin-react": "^5.1.
|
|
14
|
-
"@vitest/coverage-v8": "^4.0.
|
|
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.
|
|
15
|
+
"react-dom": "19.2.4",
|
|
17
16
|
"typescript": "5.9.3",
|
|
18
|
-
"vitest": "4.0.
|
|
17
|
+
"vitest": "4.0.18"
|
|
19
18
|
},
|
|
20
19
|
"module": "./dist/index.js",
|
|
21
20
|
"types": "./dist/index.d.ts",
|