@pfm-platform/expenses-data-access 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +118 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +147 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactQuery = require('@tanstack/react-query');
|
|
4
|
+
var shared = require('@pfm-platform/shared');
|
|
5
|
+
var axios = require('axios');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
10
|
+
|
|
11
|
+
// src/queries/useExpenses.ts
|
|
12
|
+
var api = axios__default.default.create({
|
|
13
|
+
baseURL: "/api",
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/json"
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// src/keys.ts
|
|
20
|
+
var expenseKeys = {
|
|
21
|
+
all: ["expenses"],
|
|
22
|
+
lists: () => [...expenseKeys.all, "list"],
|
|
23
|
+
list: (userId, filters) => [...expenseKeys.lists(), userId, filters]
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/queries/useExpenses.ts
|
|
27
|
+
function useExpenses({ userId, filters }, options) {
|
|
28
|
+
return reactQuery.useQuery({
|
|
29
|
+
queryKey: expenseKeys.list(userId, filters),
|
|
30
|
+
queryFn: async () => {
|
|
31
|
+
let validatedFilters = filters;
|
|
32
|
+
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);
|
|
41
|
+
}
|
|
42
|
+
if (validatedFilters?.threshold !== void 0) {
|
|
43
|
+
params.append("threshold", validatedFilters.threshold.toString());
|
|
44
|
+
}
|
|
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;
|
|
50
|
+
},
|
|
51
|
+
staleTime: 1e3 * 60 * 5,
|
|
52
|
+
// 5 minutes (aggregated data, moderate update frequency)
|
|
53
|
+
...options
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function useCreateExpense(options) {
|
|
57
|
+
const queryClient = reactQuery.useQueryClient();
|
|
58
|
+
const { mode } = shared.useAppMode();
|
|
59
|
+
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);
|
|
65
|
+
},
|
|
66
|
+
onSuccess: (_, { userId }) => {
|
|
67
|
+
queryClient.invalidateQueries({
|
|
68
|
+
queryKey: expenseKeys.list(userId)
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
...options
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function useUpdateExpense(options) {
|
|
75
|
+
const queryClient = reactQuery.useQueryClient();
|
|
76
|
+
const { mode } = shared.useAppMode();
|
|
77
|
+
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);
|
|
86
|
+
},
|
|
87
|
+
onSuccess: (_, { userId }) => {
|
|
88
|
+
queryClient.invalidateQueries({
|
|
89
|
+
queryKey: expenseKeys.list(userId)
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
...options
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function useDeleteExpense(options) {
|
|
96
|
+
const queryClient = reactQuery.useQueryClient();
|
|
97
|
+
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)}`);
|
|
101
|
+
},
|
|
102
|
+
onSuccess: (_, { userId }) => {
|
|
103
|
+
queryClient.invalidateQueries({
|
|
104
|
+
queryKey: expenseKeys.list(userId)
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
...options
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
exports.api = api;
|
|
112
|
+
exports.expenseKeys = expenseKeys;
|
|
113
|
+
exports.useCreateExpense = useCreateExpense;
|
|
114
|
+
exports.useDeleteExpense = useDeleteExpense;
|
|
115
|
+
exports.useExpenses = useExpenses;
|
|
116
|
+
exports.useUpdateExpense = useUpdateExpense;
|
|
117
|
+
//# sourceMappingURL=index.cjs.map
|
|
118
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
|
3
|
+
import { ExpenseSearchParams, Expense } from '@pfm-platform/shared';
|
|
4
|
+
import * as axios from 'axios';
|
|
5
|
+
|
|
6
|
+
interface UseExpensesParams {
|
|
7
|
+
userId: string;
|
|
8
|
+
filters?: ExpenseSearchParams;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch expenses for a user with optional filters
|
|
12
|
+
*
|
|
13
|
+
* @param params - User ID and optional filters (begin_on, end_on, threshold)
|
|
14
|
+
* @param options - Additional useQuery options
|
|
15
|
+
* @returns Query result with expenses array
|
|
16
|
+
*/
|
|
17
|
+
declare function useExpenses({ userId, filters }: UseExpensesParams, options?: Omit<UseQueryOptions<Expense[]>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<{
|
|
18
|
+
tag: string;
|
|
19
|
+
amount: string;
|
|
20
|
+
}[], Error>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Expense create data interface
|
|
24
|
+
*/
|
|
25
|
+
interface ExpenseCreate {
|
|
26
|
+
tag: string;
|
|
27
|
+
amount: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parameters for useCreateExpense mutation
|
|
31
|
+
*/
|
|
32
|
+
interface CreateExpenseParams {
|
|
33
|
+
userId: string;
|
|
34
|
+
data: ExpenseCreate;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
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
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function useCreateExpense(options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
58
|
+
tag: string;
|
|
59
|
+
amount: string;
|
|
60
|
+
}, Error, CreateExpenseParams, unknown>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Expense update data interface
|
|
64
|
+
*/
|
|
65
|
+
interface ExpenseUpdate {
|
|
66
|
+
amount?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parameters for useUpdateExpense mutation
|
|
70
|
+
*/
|
|
71
|
+
interface UpdateExpenseParams {
|
|
72
|
+
userId: string;
|
|
73
|
+
tag: string;
|
|
74
|
+
data: ExpenseUpdate;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
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
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare function useUpdateExpense(options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
98
|
+
tag: string;
|
|
99
|
+
amount: string;
|
|
100
|
+
}, Error, UpdateExpenseParams, unknown>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parameters for useDeleteExpense mutation
|
|
104
|
+
*/
|
|
105
|
+
interface DeleteExpenseParams {
|
|
106
|
+
userId: string;
|
|
107
|
+
tag: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
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
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
declare function useDeleteExpense(options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<void, Error, DeleteExpenseParams, unknown>;
|
|
126
|
+
|
|
127
|
+
declare const expenseKeys: {
|
|
128
|
+
all: readonly ["expenses"];
|
|
129
|
+
lists: () => readonly ["expenses", "list"];
|
|
130
|
+
list: (userId: string, filters?: {
|
|
131
|
+
begin_on?: string;
|
|
132
|
+
end_on?: string;
|
|
133
|
+
threshold?: number;
|
|
134
|
+
}) => readonly ["expenses", "list", string, {
|
|
135
|
+
begin_on?: string;
|
|
136
|
+
end_on?: string;
|
|
137
|
+
threshold?: number;
|
|
138
|
+
} | undefined];
|
|
139
|
+
};
|
|
140
|
+
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
|
3
|
+
import { ExpenseSearchParams, Expense } from '@pfm-platform/shared';
|
|
4
|
+
import * as axios from 'axios';
|
|
5
|
+
|
|
6
|
+
interface UseExpensesParams {
|
|
7
|
+
userId: string;
|
|
8
|
+
filters?: ExpenseSearchParams;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch expenses for a user with optional filters
|
|
12
|
+
*
|
|
13
|
+
* @param params - User ID and optional filters (begin_on, end_on, threshold)
|
|
14
|
+
* @param options - Additional useQuery options
|
|
15
|
+
* @returns Query result with expenses array
|
|
16
|
+
*/
|
|
17
|
+
declare function useExpenses({ userId, filters }: UseExpensesParams, options?: Omit<UseQueryOptions<Expense[]>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<{
|
|
18
|
+
tag: string;
|
|
19
|
+
amount: string;
|
|
20
|
+
}[], Error>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Expense create data interface
|
|
24
|
+
*/
|
|
25
|
+
interface ExpenseCreate {
|
|
26
|
+
tag: string;
|
|
27
|
+
amount: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parameters for useCreateExpense mutation
|
|
31
|
+
*/
|
|
32
|
+
interface CreateExpenseParams {
|
|
33
|
+
userId: string;
|
|
34
|
+
data: ExpenseCreate;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
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
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function useCreateExpense(options?: Omit<UseMutationOptions<Expense, Error, CreateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
58
|
+
tag: string;
|
|
59
|
+
amount: string;
|
|
60
|
+
}, Error, CreateExpenseParams, unknown>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Expense update data interface
|
|
64
|
+
*/
|
|
65
|
+
interface ExpenseUpdate {
|
|
66
|
+
amount?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parameters for useUpdateExpense mutation
|
|
70
|
+
*/
|
|
71
|
+
interface UpdateExpenseParams {
|
|
72
|
+
userId: string;
|
|
73
|
+
tag: string;
|
|
74
|
+
data: ExpenseUpdate;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
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
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare function useUpdateExpense(options?: Omit<UseMutationOptions<Expense, Error, UpdateExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<{
|
|
98
|
+
tag: string;
|
|
99
|
+
amount: string;
|
|
100
|
+
}, Error, UpdateExpenseParams, unknown>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parameters for useDeleteExpense mutation
|
|
104
|
+
*/
|
|
105
|
+
interface DeleteExpenseParams {
|
|
106
|
+
userId: string;
|
|
107
|
+
tag: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
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
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
declare function useDeleteExpense(options?: Omit<UseMutationOptions<void, Error, DeleteExpenseParams>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<void, Error, DeleteExpenseParams, unknown>;
|
|
126
|
+
|
|
127
|
+
declare const expenseKeys: {
|
|
128
|
+
all: readonly ["expenses"];
|
|
129
|
+
lists: () => readonly ["expenses", "list"];
|
|
130
|
+
list: (userId: string, filters?: {
|
|
131
|
+
begin_on?: string;
|
|
132
|
+
end_on?: string;
|
|
133
|
+
threshold?: number;
|
|
134
|
+
}) => readonly ["expenses", "list", string, {
|
|
135
|
+
begin_on?: string;
|
|
136
|
+
end_on?: string;
|
|
137
|
+
threshold?: number;
|
|
138
|
+
} | undefined];
|
|
139
|
+
};
|
|
140
|
+
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
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';
|
|
4
|
+
|
|
5
|
+
// src/queries/useExpenses.ts
|
|
6
|
+
var api = axios.create({
|
|
7
|
+
baseURL: "/api",
|
|
8
|
+
headers: {
|
|
9
|
+
"Content-Type": "application/json"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// src/keys.ts
|
|
14
|
+
var expenseKeys = {
|
|
15
|
+
all: ["expenses"],
|
|
16
|
+
lists: () => [...expenseKeys.all, "list"],
|
|
17
|
+
list: (userId, filters) => [...expenseKeys.lists(), userId, filters]
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/queries/useExpenses.ts
|
|
21
|
+
function useExpenses({ userId, filters }, options) {
|
|
22
|
+
return useQuery({
|
|
23
|
+
queryKey: expenseKeys.list(userId, filters),
|
|
24
|
+
queryFn: async () => {
|
|
25
|
+
let validatedFilters = filters;
|
|
26
|
+
if (filters) {
|
|
27
|
+
validatedFilters = ExpenseSearchParamsSchema.parse(filters);
|
|
28
|
+
}
|
|
29
|
+
const params = new URLSearchParams();
|
|
30
|
+
if (validatedFilters?.begin_on) {
|
|
31
|
+
params.append("begin_on", validatedFilters.begin_on);
|
|
32
|
+
}
|
|
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;
|
|
44
|
+
},
|
|
45
|
+
staleTime: 1e3 * 60 * 5,
|
|
46
|
+
// 5 minutes (aggregated data, moderate update frequency)
|
|
47
|
+
...options
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function useCreateExpense(options) {
|
|
51
|
+
const queryClient = useQueryClient();
|
|
52
|
+
const { mode } = useAppMode();
|
|
53
|
+
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);
|
|
59
|
+
},
|
|
60
|
+
onSuccess: (_, { userId }) => {
|
|
61
|
+
queryClient.invalidateQueries({
|
|
62
|
+
queryKey: expenseKeys.list(userId)
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
...options
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function useUpdateExpense(options) {
|
|
69
|
+
const queryClient = useQueryClient();
|
|
70
|
+
const { mode } = useAppMode();
|
|
71
|
+
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);
|
|
80
|
+
},
|
|
81
|
+
onSuccess: (_, { userId }) => {
|
|
82
|
+
queryClient.invalidateQueries({
|
|
83
|
+
queryKey: expenseKeys.list(userId)
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
...options
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function useDeleteExpense(options) {
|
|
90
|
+
const queryClient = useQueryClient();
|
|
91
|
+
return useMutation({
|
|
92
|
+
mutationFn: async ({ userId, tag }) => {
|
|
93
|
+
const validated = ExpenseDeleteSchema.parse({ tag });
|
|
94
|
+
await api.delete(`/users/${userId}/expenses/${encodeURIComponent(validated.tag)}`);
|
|
95
|
+
},
|
|
96
|
+
onSuccess: (_, { userId }) => {
|
|
97
|
+
queryClient.invalidateQueries({
|
|
98
|
+
queryKey: expenseKeys.list(userId)
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
...options
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { api, expenseKeys, useCreateExpense, useDeleteExpense, useExpenses, useUpdateExpense };
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":"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","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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pfm-platform/expenses-data-access",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"axios": "^1.13.2",
|
|
8
|
+
"zod": "4.1.12",
|
|
9
|
+
"@pfm-platform/shared": "0.0.1"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@testing-library/react": "^16.3.0",
|
|
13
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
14
|
+
"@vitest/coverage-v8": "^4.0.9",
|
|
15
|
+
"jsdom": "^27.2.0",
|
|
16
|
+
"react-dom": "19.2.0",
|
|
17
|
+
"typescript": "5.9.3",
|
|
18
|
+
"vitest": "4.0.9"
|
|
19
|
+
},
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"description": "Personal Finance Management - EXPENSES data-access layer",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"pfm",
|
|
36
|
+
"finance",
|
|
37
|
+
"expenses",
|
|
38
|
+
"data-access",
|
|
39
|
+
"react",
|
|
40
|
+
"typescript"
|
|
41
|
+
],
|
|
42
|
+
"author": "Lenny Miller",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/lennylmiller/pfm-research",
|
|
47
|
+
"directory": "packages/expenses/data-access"
|
|
48
|
+
},
|
|
49
|
+
"bugs": "https://github.com/lennylmiller/pfm-research/issues",
|
|
50
|
+
"homepage": "https://github.com/lennylmiller/pfm-research#readme",
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@tanstack/react-query": "5.90.9",
|
|
53
|
+
"react": "19.2.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"test:coverage": "vitest run --coverage",
|
|
59
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean"
|
|
60
|
+
}
|
|
61
|
+
}
|