@tpzdsp/next-toolkit 2.1.0 → 2.3.0

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/README.md CHANGED
@@ -559,10 +559,32 @@ import '@your-org/nextjs-library/src/assets/styles/globals.css';
559
559
  4. Add tests for your changes (`src/test/YourComponent.test.tsx`)
560
560
  5. Run the test suite (`pnpm test`)
561
561
  6. Test with yalc in a real Next.js app
562
- 7. Commit your changes (`git commit -m 'Add some amazing feature'`)
562
+ 7. Commit your changes following the [Angular commit message guidelines](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) the `commit-msg` hook enforces this via commitlint
563
563
  8. Push to the branch (`git push origin feature/amazing-feature`)
564
564
  9. Open a Pull Request
565
565
 
566
+ ### Commit Convention
567
+
568
+ We follow the [Angular commit message guidelines](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md). Commits are validated by [commitlint](https://commitlint.io/) on every `git commit`. Allowed types come from `@commitlint/config-conventional`. Allowed scopes are defined in `commitlint.config.ts` — edit that file to add or remove scopes.
569
+
570
+ #### Smoke-testing the commit hook
571
+
572
+ Run these directly in the terminal to verify the hook is working after any changes to `commitlint.config.ts`:
573
+
574
+ ```bash
575
+ # Should be rejected — wrong type
576
+ printf 'feature(map): bad type\n\nBody text here explaining the change.' | pnpm commitlint
577
+
578
+ # Should be rejected — unknown scope
579
+ printf 'feat(unknown): bad scope\n\nBody text here explaining the change.' | pnpm commitlint
580
+
581
+ # Should be rejected — missing body
582
+ printf 'feat(map): missing body\n' | pnpm commitlint
583
+
584
+ # Should pass
585
+ printf 'feat(map): valid message\n\nThis explains what changed and why.' | pnpm commitlint
586
+ ```
587
+
566
588
  ### Code Style
567
589
 
568
590
  - Use TypeScript for all files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpzdsp/next-toolkit",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "A reusable React component library for Next.js applications",
5
5
  "packageManager": "pnpm@11.5.3",
6
6
  "engines": {
@@ -9,17 +9,17 @@
9
9
  },
10
10
  "type": "module",
11
11
  "private": false,
12
- "homepage": "https://github.com/DSP3-apps/next-toolkit#readme",
12
+ "homepage": "https://github.com/agrimetrics/next-toolkit#readme",
13
13
  "author": "Mark Small",
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/DSP3-apps/next-toolkit.git"
19
+ "url": "git+https://github.com/agrimetrics/next-toolkit.git"
20
20
  },
21
21
  "bugs": {
22
- "url": "https://github.com/DSP3-apps/next-toolkit/issues"
22
+ "url": "https://github.com/agrimetrics/next-toolkit/issues"
23
23
  },
24
24
  "main": "./src/index.ts",
25
25
  "module": "./src/index.ts",
@@ -127,6 +127,9 @@
127
127
  },
128
128
  "devDependencies": {
129
129
  "@better-fetch/fetch": "^1.1.21",
130
+ "@commitlint/cli": "^21.1.0",
131
+ "@commitlint/config-conventional": "^21.1.0",
132
+ "@commitlint/types": "^21.1.0",
130
133
  "@floating-ui/react": "^0.27.17",
131
134
  "@storybook/addon-docs": "10.3.5",
132
135
  "@storybook/addon-links": "10.3.5",
package/src/http/query.ts CHANGED
@@ -9,9 +9,12 @@ import {
9
9
  type BetterFetchOption,
10
10
  } from '@better-fetch/fetch';
11
11
  import {
12
+ useMutation,
12
13
  useQuery,
13
14
  useSuspenseQuery,
14
15
  type QueryKey,
16
+ type UseMutationOptions,
17
+ type UseMutationResult,
15
18
  type UseQueryOptions,
16
19
  type UseQueryResult,
17
20
  type UseSuspenseQueryOptions,
@@ -84,8 +87,8 @@ type SchemaRoutesOf<Option extends CreateFetchOption> = Option extends {
84
87
  ? Routes
85
88
  : undefined;
86
89
 
87
- // We provide the `queryFn` and `queryKeys` automatically, so no need to let the user provide them.
88
- type OmitReactQueryKeys = 'queryKey' | 'queryFn';
90
+ // We provide these keys automatically, so no need to let the user provide them.
91
+ type OmitReactQueryKeys = 'queryKey' | 'queryFn' | 'mutationFn' | 'mutationKey';
89
92
 
90
93
  /**
91
94
  * A higher-order type that creates React Query hooks (`useBetterQuerySafe` and `useBetterSuspenseQuery`)
@@ -183,6 +186,75 @@ type ReactQueryBetterFetch = <
183
186
  OmitReactQueryKeys
184
187
  >,
185
188
  ): UseSuspenseQueryResult<Output>;
189
+
190
+ // Mutation query (error thrown)
191
+
192
+ // Overload that uses schema output (`RoutesWithOutput`)
193
+ useBetterMutation<
194
+ Error = ApiError,
195
+ Route extends RoutesWithOutput<Routes> = RoutesWithOutput<Routes>,
196
+ >(
197
+ route: Route,
198
+ // If the route requires an input, then expect options that provide that input, otherwise just expect options without inputs.
199
+ fetchOptions?: OptionsNoInputOutput,
200
+ queryOptions?: Omit<
201
+ UseMutationOptions<SchemaRouteOutput<Routes, Route>, Error, unknown, unknown>,
202
+ OmitReactQueryKeys
203
+ >,
204
+ ): UseMutationResult<
205
+ SchemaRouteOutput<Routes, Route>,
206
+ Error,
207
+ Route extends RoutesWithInput<Routes> ? OptionsWithInput<Routes, Route> : void
208
+ >;
209
+
210
+ // Overload that allows manual output type instead of schema (`RoutesWithoutOutput`)
211
+ useBetterMutation<
212
+ Output,
213
+ Error = ApiError,
214
+ Route extends RoutesWithoutOutput<Routes> = RoutesWithoutOutput<Routes>,
215
+ >(
216
+ route: Route,
217
+ fetchOptions?: OptionsNoInputOutput,
218
+ queryOptions?: Omit<UseMutationOptions<Output, Error, unknown, unknown>, OmitReactQueryKeys>,
219
+ ): UseMutationResult<
220
+ Output,
221
+ Error,
222
+ Route extends RoutesWithInput<Routes> ? OptionsWithInput<Routes, Route> : void
223
+ >;
224
+
225
+ // Safe mutation (error NOT thrown)
226
+
227
+ // Overload that uses schema output (`RoutesWithOutput`)
228
+ useBetterMutationSafe<
229
+ Error = ApiError,
230
+ Route extends RoutesWithOutput<Routes> = RoutesWithOutput<Routes>,
231
+ >(
232
+ route: Route,
233
+ fetchOptions?: OptionsNoInputOutput,
234
+ queryOptions?: Omit<
235
+ UseMutationOptions<SchemaRouteOutput<Routes, Route>, Error, unknown, unknown>,
236
+ OmitReactQueryKeys
237
+ >,
238
+ ): UseMutationResult<
239
+ SchemaRouteOutput<Routes, Route>,
240
+ Error,
241
+ Route extends RoutesWithInput<Routes> ? OptionsWithInput<Routes, Route> : void
242
+ >;
243
+
244
+ // Overload that allows manual output type instead of schema (`RoutesWithoutOutput`)
245
+ useBetterMutationSafe<
246
+ Output,
247
+ Error = ApiError,
248
+ Route extends RoutesWithoutOutput<Routes> = RoutesWithoutOutput<Routes>,
249
+ >(
250
+ route: Route,
251
+ fetchOptions?: OptionsNoInputOutput,
252
+ queryOptions?: Omit<UseMutationOptions<Output, Error, unknown, unknown>, OmitReactQueryKeys>,
253
+ ): UseMutationResult<
254
+ Output,
255
+ Error,
256
+ Route extends RoutesWithInput<Routes> ? OptionsWithInput<Routes, Route> : void
257
+ >;
186
258
  };
187
259
 
188
260
  /**
@@ -283,5 +355,50 @@ export const reactQueryBetterFetch: ReactQueryBetterFetch = <
283
355
  });
284
356
  };
285
357
 
286
- return { useBetterQuerySafe, useBetterSuspenseQuery };
358
+ const useBetterMutation = <Route extends keyof Routes>(
359
+ route: Route,
360
+ fetchOptions?: InferOptions<FetchSchema, ''>,
361
+ queryOptions?: Omit<
362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
+ UseMutationOptions<any, any, any, unknown>,
364
+ OmitReactQueryKeys | 'throwOnError'
365
+ >,
366
+ ) => {
367
+ return useMutation({
368
+ ...queryOptions,
369
+ mutationKey: [
370
+ route,
371
+ fetchOptions?.body,
372
+ fetchOptions?.params,
373
+ fetchOptions?.query,
374
+ fetchOptions?.headers,
375
+ ],
376
+ mutationFn: (inputs) => anyInstance(route, { ...fetchOptions, ...inputs }),
377
+ throwOnError: true,
378
+ });
379
+ };
380
+
381
+ const useBetterMutationSafe = <Route extends keyof Routes>(
382
+ route: Route,
383
+ fetchOptions?: InferOptions<FetchSchema, ''>,
384
+ queryOptions?: Omit<
385
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
+ UseMutationOptions<any, any, any, unknown>,
387
+ OmitReactQueryKeys | 'throwOnError'
388
+ >,
389
+ ) => {
390
+ return useMutation({
391
+ ...queryOptions,
392
+ mutationKey: [
393
+ route,
394
+ fetchOptions?.body,
395
+ fetchOptions?.params,
396
+ fetchOptions?.query,
397
+ fetchOptions?.headers,
398
+ ],
399
+ mutationFn: (inputs) => anyInstance(route, { ...fetchOptions, ...inputs }),
400
+ });
401
+ };
402
+
403
+ return { useBetterQuerySafe, useBetterMutationSafe, useBetterMutation, useBetterSuspenseQuery };
287
404
  };