@react-vault/create-app 0.1.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.
Files changed (117) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +16 -0
  3. package/bin/create-app.js +8 -0
  4. package/claude-toolkit/README.md +131 -0
  5. package/claude-toolkit/agents/bfsi-accessibility-auditor.md +132 -0
  6. package/claude-toolkit/agents/bfsi-architect.md +156 -0
  7. package/claude-toolkit/agents/bfsi-code-reviewer.md +137 -0
  8. package/claude-toolkit/agents/bfsi-compliance-auditor.md +161 -0
  9. package/claude-toolkit/agents/bfsi-pii-scanner.md +142 -0
  10. package/claude-toolkit/agents/bfsi-pr-reviewer.md +114 -0
  11. package/claude-toolkit/agents/bfsi-security-reviewer.md +136 -0
  12. package/claude-toolkit/commands/bfsi-audit.md +46 -0
  13. package/claude-toolkit/commands/bfsi-doctor.md +97 -0
  14. package/claude-toolkit/commands/bfsi-review.md +46 -0
  15. package/claude-toolkit/commands/bfsi-scaffold.md +47 -0
  16. package/claude-toolkit/hooks/hooks.json +181 -0
  17. package/claude-toolkit/hooks/scripts/a11y-check.sh +63 -0
  18. package/claude-toolkit/hooks/scripts/audit-prompt.sh +36 -0
  19. package/claude-toolkit/hooks/scripts/block-destructive.sh +41 -0
  20. package/claude-toolkit/hooks/scripts/block-force-push.sh +30 -0
  21. package/claude-toolkit/hooks/scripts/format.sh +42 -0
  22. package/claude-toolkit/hooks/scripts/inject-context.sh +44 -0
  23. package/claude-toolkit/hooks/scripts/lint.sh +45 -0
  24. package/claude-toolkit/hooks/scripts/protect-files.sh +53 -0
  25. package/claude-toolkit/hooks/scripts/save-compliance-context.sh +35 -0
  26. package/claude-toolkit/hooks/scripts/scan-pii.sh +87 -0
  27. package/claude-toolkit/hooks/scripts/scan-secrets.sh +67 -0
  28. package/claude-toolkit/hooks/scripts/verify-clean.sh +50 -0
  29. package/claude-toolkit/package.json +22 -0
  30. package/claude-toolkit/plugin.json +31 -0
  31. package/claude-toolkit/skills/bfsi-api-endpoint/SKILL.md +105 -0
  32. package/claude-toolkit/skills/bfsi-commit/SKILL.md +102 -0
  33. package/claude-toolkit/skills/bfsi-compliance-check/SKILL.md +107 -0
  34. package/claude-toolkit/skills/bfsi-encrypt-helper/SKILL.md +127 -0
  35. package/claude-toolkit/skills/bfsi-error-message/SKILL.md +162 -0
  36. package/claude-toolkit/skills/bfsi-feature/SKILL.md +120 -0
  37. package/claude-toolkit/skills/bfsi-feature/references/architecture.md +69 -0
  38. package/claude-toolkit/skills/bfsi-feature/references/audit-events.md +70 -0
  39. package/claude-toolkit/skills/bfsi-feature/scripts/scaffold.mjs +136 -0
  40. package/claude-toolkit/skills/bfsi-form/SKILL.md +73 -0
  41. package/claude-toolkit/skills/bfsi-form/references/validation-regex.md +50 -0
  42. package/claude-toolkit/skills/bfsi-onboarding/SKILL.md +110 -0
  43. package/claude-toolkit/skills/bfsi-pii-field/SKILL.md +90 -0
  44. package/claude-toolkit/skills/bfsi-test-pattern/SKILL.md +179 -0
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +339 -0
  48. package/dist/index.js.map +1 -0
  49. package/package.json +69 -0
  50. package/templates/_shared/.claude/settings.json +31 -0
  51. package/templates/_shared/.env.local.sample +25 -0
  52. package/templates/_shared/.github/workflows/ci.yml +49 -0
  53. package/templates/_shared/CLAUDE.md +89 -0
  54. package/templates/_shared/README.md +50 -0
  55. package/templates/_shared/index.html +16 -0
  56. package/templates/_shared/package.json +73 -0
  57. package/templates/_shared/postcss.config.cjs +6 -0
  58. package/templates/_shared/src/app/App.tsx +13 -0
  59. package/templates/_shared/src/app/globals.css +64 -0
  60. package/templates/_shared/src/env.ts +33 -0
  61. package/templates/_shared/src/i18n/i18n.ts +18 -0
  62. package/templates/_shared/src/i18n/translations/en.json +54 -0
  63. package/templates/_shared/src/i18n/translations/hi.json +30 -0
  64. package/templates/_shared/src/main.tsx +16 -0
  65. package/templates/_shared/src/routes/ProtectedRoute.tsx +28 -0
  66. package/templates/_shared/src/routes/index.tsx +67 -0
  67. package/templates/_shared/src/shared/ErrorBoundary.tsx +60 -0
  68. package/templates/_shared/tailwind.config.ts +68 -0
  69. package/templates/_shared/tests/setup.ts +7 -0
  70. package/templates/_shared/tsconfig.json +33 -0
  71. package/templates/_shared/tsconfig.node.json +13 -0
  72. package/templates/_shared/vite.config.ts +47 -0
  73. package/templates/rtk-query/.claude/skills/axios-auth/SKILL.md +103 -0
  74. package/templates/rtk-query/.claude/skills/axios-auth/references/error-shape.md +84 -0
  75. package/templates/rtk-query/.claude/skills/axios-auth/references/full-code-walkthrough.md +146 -0
  76. package/templates/rtk-query/.claude/skills/axios-auth/references/notification-wiring.md +141 -0
  77. package/templates/rtk-query/.claude/skills/constants-organization/SKILL.md +112 -0
  78. package/templates/rtk-query/.claude/skills/constants-organization/references/example-files.md +134 -0
  79. package/templates/rtk-query/.claude/skills/constants-organization/references/tag-types-catalog.md +53 -0
  80. package/templates/rtk-query/.claude/skills/redux-store-integration/SKILL.md +159 -0
  81. package/templates/rtk-query/.claude/skills/redux-store-integration/references/localStorage-persistence.md +70 -0
  82. package/templates/rtk-query/.claude/skills/redux-store-integration/references/middleware-patterns.md +82 -0
  83. package/templates/rtk-query/.claude/skills/rtk-query-api/SKILL.md +148 -0
  84. package/templates/rtk-query/.claude/skills/rtk-query-api/references/cache-strategies.md +96 -0
  85. package/templates/rtk-query/.claude/skills/rtk-query-api/references/endpoint-cookbook.md +145 -0
  86. package/templates/rtk-query/.claude/skills/rtk-query-api/references/optimistic-update.md +53 -0
  87. package/templates/rtk-query/README.md +84 -0
  88. package/templates/rtk-query/package.partial.json +7 -0
  89. package/templates/rtk-query/src/app/App.tsx +23 -0
  90. package/templates/rtk-query/src/axiosconfig/axiosInstance.ts +26 -0
  91. package/templates/rtk-query/src/axiosconfig/baseQuery.ts +72 -0
  92. package/templates/rtk-query/src/axiosconfig/interceptor.ts +42 -0
  93. package/templates/rtk-query/src/redux/invalidateCacheMiddleware.ts +20 -0
  94. package/templates/rtk-query/src/redux/reduxHooks.ts +10 -0
  95. package/templates/rtk-query/src/redux/rootReducer.ts +18 -0
  96. package/templates/rtk-query/src/redux/store.ts +36 -0
  97. package/templates/tanstack-query/.claude/skills/axios-auth/SKILL.md +109 -0
  98. package/templates/tanstack-query/.claude/skills/axios-auth/references/error-shape.md +89 -0
  99. package/templates/tanstack-query/.claude/skills/axios-auth/references/full-code-walkthrough.md +121 -0
  100. package/templates/tanstack-query/.claude/skills/axios-auth/references/notification-pattern.md +109 -0
  101. package/templates/tanstack-query/.claude/skills/constants-organization/SKILL.md +144 -0
  102. package/templates/tanstack-query/.claude/skills/constants-organization/references/example-files.md +111 -0
  103. package/templates/tanstack-query/.claude/skills/constants-organization/references/query-key-factories.md +129 -0
  104. package/templates/tanstack-query/.claude/skills/query-client-setup/SKILL.md +165 -0
  105. package/templates/tanstack-query/.claude/skills/query-client-setup/references/devtools.md +67 -0
  106. package/templates/tanstack-query/.claude/skills/query-client-setup/references/global-handlers.md +94 -0
  107. package/templates/tanstack-query/.claude/skills/tanstack-services/SKILL.md +142 -0
  108. package/templates/tanstack-query/.claude/skills/tanstack-services/references/audited-mutation.md +144 -0
  109. package/templates/tanstack-query/.claude/skills/tanstack-services/references/optimistic-update.md +102 -0
  110. package/templates/tanstack-query/.claude/skills/tanstack-services/references/service-cookbook.md +151 -0
  111. package/templates/tanstack-query/README.md +63 -0
  112. package/templates/tanstack-query/package.partial.json +8 -0
  113. package/templates/tanstack-query/src/api/axiosInstance.ts +20 -0
  114. package/templates/tanstack-query/src/api/http.ts +62 -0
  115. package/templates/tanstack-query/src/api/queryClient.ts +28 -0
  116. package/templates/tanstack-query/src/app/App.tsx +20 -0
  117. package/templates/tanstack-query/src/services/example.ts +32 -0
@@ -0,0 +1,134 @@
1
+ # Constants files — templates
2
+
3
+ ## `src/utils/constants/apiConstants.ts`
4
+
5
+ ```ts
6
+ export const API_VERSION = '/v1';
7
+ export const API_URL = import.meta.env.VITE_API_BASE_URL;
8
+
9
+ // HTTP methods as constants — used by axiosBaseQuery method comparisons
10
+ export const GET = 'GET' as const;
11
+ export const POST = 'POST' as const;
12
+ export const PUT = 'PUT' as const;
13
+ export const PATCH = 'PATCH' as const;
14
+ export const DELETE = 'DELETE' as const;
15
+ ```
16
+
17
+ ## `src/utils/constants/appConstants.ts`
18
+
19
+ ```ts
20
+ // Storage keys
21
+ export const LOGGED_IN_USER = 'loggedInUser';
22
+ export const SELECTED_LOCALE = 'selectedLocale';
23
+ export const THEME_PREFERENCE = 'themePreference';
24
+
25
+ // Notification types
26
+ export const SUCCESS = 'success' as const;
27
+ export const ERROR = 'error' as const;
28
+ export const INFO = 'info' as const;
29
+ export const WARNING = 'warning' as const;
30
+
31
+ // Status enums
32
+ export const KYC_STATUS = {
33
+ PENDING: 'pending',
34
+ APPROVED: 'approved',
35
+ REJECTED: 'rejected',
36
+ REVIEW: 'review',
37
+ } as const;
38
+ export type KycStatus = (typeof KYC_STATUS)[keyof typeof KYC_STATUS];
39
+ ```
40
+
41
+ ## `src/utils/constants/urlConstants.ts`
42
+
43
+ ```ts
44
+ // Group endpoints by feature. Use functions for dynamic paths.
45
+
46
+ export const AUTH_URLS = {
47
+ LOGIN: '/auth/login',
48
+ LOGOUT: '/auth/logout',
49
+ REFRESH: '/auth/refresh',
50
+ PROFILE: '/auth/profile',
51
+ } as const;
52
+
53
+ export const KYC_URLS = {
54
+ LIST: '/kyc',
55
+ DETAIL: (id: string) => `/kyc/${id}`,
56
+ SUBMIT: '/kyc/submit',
57
+ APPROVE: (id: string) => `/kyc/${id}/approve`,
58
+ REJECT: (id: string) => `/kyc/${id}/reject`,
59
+ } as const;
60
+
61
+ export const TRANSACTION_URLS = {
62
+ LIST: '/transactions',
63
+ DETAIL: (id: string) => `/transactions/${id}`,
64
+ CREATE: '/transactions',
65
+ } as const;
66
+
67
+ // Used by request interceptor side-paths (if any)
68
+ export const VALIDATE_INVITATION = '/auth/validate-invitation';
69
+ ```
70
+
71
+ ## `src/utils/constants/routeConstants.ts`
72
+
73
+ ```ts
74
+ export const ROUTES = {
75
+ home: '/',
76
+ login: '/login',
77
+ dashboard: '/dashboard',
78
+ kyc: {
79
+ list: '/kyc',
80
+ detail: '/kyc/:id',
81
+ submit: '/kyc/submit',
82
+ },
83
+ transactions: {
84
+ list: '/transactions',
85
+ detail: '/transactions/:id',
86
+ create: '/transactions/new',
87
+ },
88
+ notFound: '*',
89
+ } as const;
90
+
91
+ // For useNavigate() with dynamic params, write small helpers:
92
+ export const kycDetailPath = (id: string): string => `/kyc/${id}`;
93
+ export const transactionDetailPath = (id: string): string => `/transactions/${id}`;
94
+ ```
95
+
96
+ ## `src/utils/constants/tagTypes.ts`
97
+
98
+ ```ts
99
+ // Every RTK Query API's tagTypes array unions from here.
100
+ // One literal per feature/entity. Keep this list as the source of truth.
101
+
102
+ export const TAG_TYPES = [
103
+ 'User',
104
+ 'Kyc',
105
+ 'Transaction',
106
+ 'Loan',
107
+ 'Document',
108
+ 'AccessRights',
109
+ ] as const;
110
+
111
+ export type TagType = (typeof TAG_TYPES)[number];
112
+ ```
113
+
114
+ ## `src/utils/constants/regexConstants.ts`
115
+
116
+ ```ts
117
+ // Re-export the BFSI patterns from core so there's one source of truth.
118
+ // Add app-specific regexes below.
119
+
120
+ export { PII_PATTERNS } from '@<scope>/core/pii';
121
+
122
+ // Pull out common ones with friendlier names:
123
+ import { PII_PATTERNS } from '@<scope>/core/pii';
124
+ export const PAN_REGEX = PII_PATTERNS.pan;
125
+ export const AADHAAR_REGEX = PII_PATTERNS.aadhaar;
126
+ export const MOBILE_REGEX = PII_PATTERNS.mobileIndia;
127
+ export const IFSC_REGEX = PII_PATTERNS.ifsc;
128
+ export const PINCODE_REGEX = PII_PATTERNS.pincodeIndia;
129
+ export const EMAIL_REGEX = PII_PATTERNS.email;
130
+
131
+ // App-specific patterns:
132
+ export const REFERENCE_CODE_REGEX = /^ERR-[A-Z0-9]{4}$/;
133
+ export const TRANSACTION_ID_REGEX = /^TXN[0-9]{12}$/;
134
+ ```
@@ -0,0 +1,53 @@
1
+ # Tag types catalog
2
+
3
+ RTK Query uses tag types for cache invalidation. Each feature picks ONE tag type per entity it manages.
4
+
5
+ ## Naming
6
+
7
+ - Singular, PascalCase: `User`, `Kyc`, `Transaction`, `Loan`
8
+ - One tag per logical entity, not per endpoint
9
+ - A single tag covers list + detail of the same entity
10
+
11
+ ## Provides vs invalidates
12
+
13
+ | Endpoint | Use | Why |
14
+ | -------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
15
+ | `getKycList` | `providesTags: ['Kyc']` | Reads → marks the cache as having Kyc data |
16
+ | `getKycDetail` | `providesTags: (_, _, id) => [{ type: 'Kyc', id }]` | Read for one — tagged with the id so detail invalidates separately |
17
+ | `submitKyc` | `invalidatesTags: ['Kyc']` | Mutation → invalidates ALL Kyc-tagged caches (list refetches) |
18
+ | `approveKyc` | `invalidatesTags: (_, _, id) => [{ type: 'Kyc', id }, 'Kyc']` | Per-record + list |
19
+
20
+ ## Cross-feature invalidation
21
+
22
+ When a mutation in feature A should invalidate caches in feature B, two patterns:
23
+
24
+ ### Pattern 1 — Tag both features
25
+
26
+ ```ts
27
+ // transactionsApi.ts
28
+ endpoints: (builder) => ({
29
+ createTransaction: builder.mutation({
30
+ query: ...,
31
+ invalidatesTags: ['Transaction', 'AccountBalance'],
32
+ }),
33
+ }),
34
+ ```
35
+
36
+ If both `Transaction` and `AccountBalance` are tag types provided by their respective APIs, both refetch.
37
+
38
+ ### Pattern 2 — Middleware-driven
39
+
40
+ Use the `invalidateCacheMiddleware` (in `src/redux/`). When `transactionsApi.endpoints.create.matchFulfilled(action)` fires, dispatch invalidation on another API:
41
+
42
+ ```ts
43
+ store.dispatch(accountsApi.util.invalidateTags(['AccountBalance']));
44
+ ```
45
+
46
+ Use Pattern 2 when there's a chain (one mutation affects N caches) and listing them all in `invalidatesTags` becomes noisy.
47
+
48
+ ## Anti-patterns
49
+
50
+ - ❌ A tag per endpoint (`KycList`, `KycDetail`, `KycSubmit`) — proliferates without benefit
51
+ - ❌ Forgetting `as const` on the array — types degrade to `string[]`
52
+ - ❌ Same tag name across multiple APIs without sharing — invalidations don't cross
53
+ - ❌ String-typed tag references instead of using the exported `TagType` union
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: redux-store-integration
3
+ description: Wire a new RTK Query API or Redux slice into the store. Covers registering reducers in rootReducer.ts, appending middleware in store.ts, using typed hooks (useAppDispatch / useAppSelector), and cross-API cache invalidation. Use when the user just scaffolded a new feature's api.ts or slice.ts and needs to plug it into the store, or asks about cache invalidation middleware, typed hooks, or Redux wiring.
4
+ ---
5
+
6
+ # Redux Store Integration
7
+
8
+ After scaffolding a new feature with an `api.ts` (RTK Query) or `slice.ts` (regular Redux), you have to register it in TWO places. Both are tiny but easy to forget.
9
+
10
+ ## File map
11
+
12
+ ```
13
+ src/redux/
14
+ ├── store.ts configureStore + middleware concat
15
+ ├── rootReducer.ts combineReducers (slices + API reducers)
16
+ ├── reduxHooks.ts typed useAppDispatch / useAppSelector
17
+ └── invalidateCacheMiddleware.ts cross-API cache invalidation rules
18
+ ```
19
+
20
+ ## Workflow — adding an RTK Query API
21
+
22
+ ### Step 1 — Register the reducer
23
+
24
+ Open `src/redux/rootReducer.ts`:
25
+
26
+ ```ts
27
+ import { combineReducers } from '@reduxjs/toolkit';
28
+ import kycApi from '@/features/Kyc/api';
29
+
30
+ const rootReducer = combineReducers({
31
+ [kycApi.reducerPath]: kycApi.reducer,
32
+ });
33
+
34
+ export default rootReducer;
35
+ ```
36
+
37
+ Use `[api.reducerPath]` as the key — never a hardcoded string. The `reducerPath` is set inside `createApi({ reducerPath: 'kycApi', ... })`.
38
+
39
+ ### Step 2 — Append the middleware
40
+
41
+ Open `src/redux/store.ts`:
42
+
43
+ ```ts
44
+ import kycApi from '@/features/Kyc/api';
45
+
46
+ const store = configureStore({
47
+ reducer: rootReducer,
48
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat([kycApi.middleware]),
49
+ devTools: true,
50
+ });
51
+ ```
52
+
53
+ Without this, RTK Query won't intercept the API's actions. Symptoms: queries fire but cache never updates, subscriptions don't trigger refetch.
54
+
55
+ ### Step 3 — Use the API in components
56
+
57
+ ```tsx
58
+ import { useGetKycListQuery } from '@/features/Kyc/api';
59
+ import { useAppDispatch, useAppSelector } from '@/redux/reduxHooks';
60
+
61
+ function KycList() {
62
+ const { data, isLoading, error } = useGetKycListQuery();
63
+ // ...
64
+ }
65
+ ```
66
+
67
+ NEVER use plain `useDispatch` / `useSelector` directly — use the typed hooks. They give you `RootState` autocomplete + `AppDispatch` action types.
68
+
69
+ ## Workflow — adding a regular Redux slice
70
+
71
+ ### Step 1 — Create the slice
72
+
73
+ In `src/features/<Feature>/slice.ts`:
74
+
75
+ ```ts
76
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
77
+
78
+ interface FeatureState {
79
+ selectedId: string | null;
80
+ filters: Record<string, unknown>;
81
+ }
82
+
83
+ const initialState: FeatureState = { selectedId: null, filters: {} };
84
+
85
+ const featureSlice = createSlice({
86
+ name: 'feature',
87
+ initialState,
88
+ reducers: {
89
+ setSelected: (state, action: PayloadAction<string | null>) => {
90
+ state.selectedId = action.payload;
91
+ },
92
+ setFilters: (state, action: PayloadAction<Record<string, unknown>>) => {
93
+ state.filters = action.payload;
94
+ },
95
+ reset: () => initialState,
96
+ },
97
+ });
98
+
99
+ export const { setSelected, setFilters, reset } = featureSlice.actions;
100
+ export const featureReducer = featureSlice.reducer;
101
+ ```
102
+
103
+ ### Step 2 — Register
104
+
105
+ In `src/redux/rootReducer.ts`:
106
+
107
+ ```ts
108
+ import { featureReducer } from '@/features/Feature/slice';
109
+
110
+ const rootReducer = combineReducers({
111
+ feature: featureReducer,
112
+ });
113
+ ```
114
+
115
+ No middleware step for plain slices (they go through `getDefaultMiddleware` automatically).
116
+
117
+ ## Cross-API cache invalidation
118
+
119
+ When mutation in one API should invalidate another API's cache, use `src/redux/invalidateCacheMiddleware.ts`:
120
+
121
+ ```ts
122
+ import type { Middleware } from '@reduxjs/toolkit';
123
+ import kycApi from '@/features/Kyc/api';
124
+ import userApi from '@/features/User/api';
125
+
126
+ const invalidateCacheMiddleware: Middleware = (storeApi) => (next) => (action) => {
127
+ if (kycApi.endpoints.submitKyc.matchFulfilled(action)) {
128
+ storeApi.dispatch(userApi.util.invalidateTags(['User']));
129
+ }
130
+ return next(action);
131
+ };
132
+
133
+ export default invalidateCacheMiddleware;
134
+ ```
135
+
136
+ Make sure `invalidateCacheMiddleware` is included in `store.ts`'s middleware concat array.
137
+
138
+ See [`references/middleware-patterns.md`](references/middleware-patterns.md) for common invalidation patterns.
139
+
140
+ ## Conventions enforced
141
+
142
+ - ❌ NEVER import `useDispatch` / `useSelector` from `react-redux` — use `useAppDispatch` / `useAppSelector` from `reduxHooks`.
143
+ - ❌ NEVER hardcode an API's reducerPath as a string in `rootReducer.ts` — use `[api.reducerPath]`.
144
+ - ❌ NEVER skip the middleware registration — leads to silent cache-bust failures.
145
+ - ✅ Register middleware in the order: `invalidateCacheMiddleware` first, then feature APIs.
146
+ - ✅ Slices live next to their feature (`src/features/<Feature>/slice.ts`).
147
+ - ✅ Each API has a unique `reducerPath`; never share.
148
+
149
+ ## Checklist when wiring a new feature
150
+
151
+ - [ ] Reducer registered in `rootReducer.ts`
152
+ - [ ] Middleware appended in `store.ts` (if it's an API)
153
+ - [ ] Containers use `useAppDispatch` / `useAppSelector` (typed)
154
+ - [ ] Cross-API invalidations added to `invalidateCacheMiddleware.ts` (if any)
155
+
156
+ ## References
157
+
158
+ - [`references/middleware-patterns.md`](references/middleware-patterns.md) — common cross-API invalidation patterns
159
+ - [`references/localStorage-persistence.md`](references/localStorage-persistence.md) — pattern for persisting a slice to localStorage
@@ -0,0 +1,70 @@
1
+ # Persisting a slice to localStorage
2
+
3
+ For state that must survive a page reload (locale, theme, in-progress form drafts) but is NOT sensitive (no PII, no tokens).
4
+
5
+ ## Rule
6
+
7
+ > Tokens NEVER go in localStorage. Use `setAuthToken()` from `@<scope>/core/http` instead, which keeps them in memory.
8
+
9
+ For non-sensitive state, this pattern is fine.
10
+
11
+ ## Pattern — `store.subscribe`
12
+
13
+ ```ts
14
+ // src/redux/store.ts
15
+ import { LOCALE_KEY, THEME_KEY } from '@/utils/constants/appConstants';
16
+
17
+ store.subscribe(() => {
18
+ const state = store.getState();
19
+ // Only persist allow-listed slices, not the entire store:
20
+ localStorage.setItem(LOCALE_KEY, JSON.stringify(state.locale));
21
+ localStorage.setItem(THEME_KEY, JSON.stringify(state.theme));
22
+ });
23
+ ```
24
+
25
+ ## Pattern — `preloadedState`
26
+
27
+ Rehydrate on store init:
28
+
29
+ ```ts
30
+ const preloadedState = {
31
+ locale: tryParse(localStorage.getItem(LOCALE_KEY)) ?? initialLocaleState,
32
+ theme: tryParse(localStorage.getItem(THEME_KEY)) ?? initialThemeState,
33
+ };
34
+
35
+ const store = configureStore({
36
+ reducer: rootReducer,
37
+ preloadedState,
38
+ middleware: ...,
39
+ });
40
+
41
+ function tryParse<T>(raw: string | null): T | null {
42
+ if (!raw) return null;
43
+ try { return JSON.parse(raw) as T; } catch { return null; }
44
+ }
45
+ ```
46
+
47
+ ## Don't persist these slices
48
+
49
+ - Anything containing auth tokens
50
+ - Anything containing PII (PAN, Aadhaar, account#)
51
+ - RTK Query API cache (`[api.reducerPath]`) — let RTK Query handle its own cache lifecycle
52
+ - Anything with timestamps that go stale (notifications, idle markers)
53
+
54
+ ## Preferred alternative for non-trivial cases
55
+
56
+ Use `@<scope>/core/storage` (`secureStorage.put/get`) with sensitivity tier `'session'`. It does the same job but:
57
+
58
+ - One memory + sessionStorage codepath (no leak risk)
59
+ - Optional TTL eviction
60
+ - Easier to swap to encrypted IndexedDB later
61
+
62
+ ```ts
63
+ import { put, get } from '@<scope>/core/storage';
64
+
65
+ // On store.subscribe:
66
+ put('locale', state.locale, { sensitivity: 'session' });
67
+
68
+ // On init:
69
+ const persisted = get<LocaleState>('locale', { sensitivity: 'session' });
70
+ ```
@@ -0,0 +1,82 @@
1
+ # Cross-API cache invalidation patterns
2
+
3
+ When `invalidatesTags` on a single endpoint isn't enough — typically because the mutation in API A should invalidate caches in API B that's not coupled to A's tagTypes.
4
+
5
+ ## Pattern: One mutation invalidates many APIs
6
+
7
+ ```ts
8
+ import type { Middleware } from '@reduxjs/toolkit';
9
+ import authApi from '@/features/Auth/api';
10
+ import userApi from '@/features/User/api';
11
+ import permissionsApi from '@/features/Permissions/api';
12
+
13
+ const invalidateCacheMiddleware: Middleware = (storeApi) => (next) => (action) => {
14
+ // Login → re-fetch user profile + permissions
15
+ if (authApi.endpoints.login.matchFulfilled(action)) {
16
+ storeApi.dispatch(userApi.util.invalidateTags(['User']));
17
+ storeApi.dispatch(permissionsApi.util.invalidateTags(['Permission']));
18
+ }
19
+
20
+ return next(action);
21
+ };
22
+ ```
23
+
24
+ ## Pattern: Conditional invalidation
25
+
26
+ ```ts
27
+ if (kycApi.endpoints.submitKyc.matchFulfilled(action)) {
28
+ const newStatus = action.payload?.status;
29
+ if (newStatus === 'approved') {
30
+ storeApi.dispatch(accountsApi.util.invalidateTags(['Account']));
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Pattern: Reset on logout
36
+
37
+ ```ts
38
+ if (authApi.endpoints.logout.matchFulfilled(action)) {
39
+ storeApi.dispatch(kycApi.util.resetApiState());
40
+ storeApi.dispatch(transactionsApi.util.resetApiState());
41
+ storeApi.dispatch(userApi.util.resetApiState());
42
+ }
43
+ ```
44
+
45
+ `resetApiState` wipes all cached data for that API — safer than tag invalidation on logout because it also clears in-flight queries.
46
+
47
+ ## Pattern: matchAny for grouped events
48
+
49
+ ```ts
50
+ import { isAnyOf } from '@reduxjs/toolkit';
51
+
52
+ const matchAnyMutation = isAnyOf(
53
+ kycApi.endpoints.submitKyc.matchFulfilled,
54
+ kycApi.endpoints.approveKyc.matchFulfilled,
55
+ kycApi.endpoints.rejectKyc.matchFulfilled,
56
+ );
57
+
58
+ if (matchAnyMutation(action)) {
59
+ storeApi.dispatch(auditApi.util.invalidateTags(['AuditEvent']));
60
+ }
61
+ ```
62
+
63
+ ## Anti-pattern: invalidate inside endpoints' `onQueryStarted`
64
+
65
+ Tempting but bad: it couples the source API to the target. Prefer middleware so the source API stays unaware. Easier to grep for invalidations (one file) and easier to test.
66
+
67
+ ## Order matters
68
+
69
+ In `store.ts`, register `invalidateCacheMiddleware` BEFORE the feature APIs' middleware:
70
+
71
+ ```ts
72
+ middleware: (getDefaultMiddleware) =>
73
+ getDefaultMiddleware().concat([
74
+ invalidateCacheMiddleware, // first
75
+ authApi.middleware,
76
+ userApi.middleware,
77
+ kycApi.middleware,
78
+ // ...
79
+ ]),
80
+ ```
81
+
82
+ This way the invalidation middleware sees the fulfilled action BEFORE the API's own subscription dispatches a refetch, avoiding a brief stale window.
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: rtk-query-api
3
+ description: Create an RTK Query API file for a feature. Covers createApi with axiosBaseQuery, query/mutation endpoints, tagTypes for cache invalidation, opt-in success/error notifications, typed request/response interfaces with Zod parse, and exported React hooks. Use when adding a new feature's api.ts, adding endpoints to an existing API, or wiring tag types.
4
+ ---
5
+
6
+ # RTK Query API
7
+
8
+ Every feature has an `api.ts` at `src/features/<Feature>/api.ts`. It uses `createApi` with the project's `axiosBaseQuery` and exposes auto-generated React hooks.
9
+
10
+ ## File map (typical feature)
11
+
12
+ ```
13
+ src/features/<Feature>/
14
+ ├── api.ts createApi + endpoints
15
+ ├── schema.ts Zod schemas (request + response)
16
+ ├── types.ts inferred types from Zod
17
+ ├── constants.ts cache tag references + audit event names
18
+ └── containers/...
19
+ ```
20
+
21
+ ## Workflow — new feature API
22
+
23
+ ### Step 1 — Define schemas
24
+
25
+ `src/features/Kyc/schema.ts`:
26
+
27
+ ```ts
28
+ import { z } from 'zod';
29
+ import { PAN_REGEX, AADHAAR_REGEX } from '@/utils/constants/regexConstants';
30
+
31
+ export const kycRecordSchema = z.object({
32
+ id: z.string(),
33
+ pan: z.string().regex(PAN_REGEX),
34
+ aadhaar: z.string().regex(AADHAAR_REGEX),
35
+ status: z.enum(['pending', 'approved', 'rejected']),
36
+ createdAt: z.coerce.date(),
37
+ });
38
+
39
+ export const kycListResponseSchema = z.object({
40
+ items: z.array(kycRecordSchema),
41
+ total: z.number().int().nonnegative(),
42
+ });
43
+
44
+ export const kycSubmitRequestSchema = z.object({
45
+ pan: z.string().regex(PAN_REGEX),
46
+ aadhaar: z.string().regex(AADHAAR_REGEX),
47
+ });
48
+ ```
49
+
50
+ Types are inferred — never hand-write them:
51
+
52
+ ```ts
53
+ // types.ts
54
+ import { z } from 'zod';
55
+ import type { kycRecordSchema, kycListResponseSchema, kycSubmitRequestSchema } from './schema';
56
+
57
+ export type KycRecord = z.infer<typeof kycRecordSchema>;
58
+ export type KycListResponse = z.infer<typeof kycListResponseSchema>;
59
+ export type KycSubmitRequest = z.infer<typeof kycSubmitRequestSchema>;
60
+ ```
61
+
62
+ ### Step 2 — Create the API
63
+
64
+ `src/features/Kyc/api.ts`:
65
+
66
+ ```ts
67
+ import { createApi } from '@reduxjs/toolkit/query/react';
68
+ import axiosBaseQuery from '@/axiosconfig/baseQuery';
69
+ import { KYC_URLS } from '@/utils/constants/urlConstants';
70
+ import { GET, POST } from '@/utils/constants/apiConstants';
71
+ import { kycRecordSchema, kycListResponseSchema } from './schema';
72
+ import type { KycListResponse, KycSubmitRequest, KycRecord } from './types';
73
+
74
+ const kycApi = createApi({
75
+ reducerPath: 'kycApi',
76
+ baseQuery: axiosBaseQuery(),
77
+ tagTypes: ['Kyc'],
78
+ endpoints: (builder) => ({
79
+ getKycList: builder.query<KycListResponse, void>({
80
+ query: () => ({ url: KYC_URLS.LIST, method: GET }),
81
+ transformResponse: (raw: unknown) => kycListResponseSchema.parse(raw),
82
+ providesTags: ['Kyc'],
83
+ }),
84
+ getKycDetail: builder.query<KycRecord, string>({
85
+ query: (id) => ({ url: KYC_URLS.DETAIL(id), method: GET }),
86
+ transformResponse: (raw: unknown) => kycRecordSchema.parse(raw),
87
+ providesTags: (_, __, id) => [{ type: 'Kyc', id }],
88
+ }),
89
+ submitKyc: builder.mutation<KycRecord, KycSubmitRequest>({
90
+ query: (body) => ({
91
+ url: KYC_URLS.SUBMIT,
92
+ method: POST,
93
+ data: body,
94
+ showSuccessNotification: true,
95
+ showFailureNotification: true,
96
+ }),
97
+ transformResponse: (raw: unknown) => kycRecordSchema.parse(raw),
98
+ invalidatesTags: ['Kyc'],
99
+ }),
100
+ }),
101
+ });
102
+
103
+ export const { useGetKycListQuery, useGetKycDetailQuery, useSubmitKycMutation } = kycApi;
104
+ export default kycApi;
105
+ ```
106
+
107
+ ### Step 3 — Register in the store
108
+
109
+ See the `redux-store-integration` skill: register `kycApi.reducer` in `rootReducer.ts` and `kycApi.middleware` in `store.ts`.
110
+
111
+ ### Step 4 — Use the hooks
112
+
113
+ ```tsx
114
+ import { useGetKycListQuery, useSubmitKycMutation } from '@/features/Kyc/api';
115
+
116
+ function KycList() {
117
+ const { data, isLoading, error } = useGetKycListQuery();
118
+ const [submit] = useSubmitKycMutation();
119
+ // ...
120
+ }
121
+ ```
122
+
123
+ ## Conventions enforced
124
+
125
+ - ❌ NEVER use `fetchBaseQuery` — always `axiosBaseQuery()` (so notifications/auth/interceptors apply).
126
+ - ❌ NEVER skip `transformResponse: schema.parse` — every response must validate at runtime.
127
+ - ❌ NEVER hand-write request/response types — infer from Zod.
128
+ - ❌ NEVER inline a URL string — use `urlConstants.ts`.
129
+ - ❌ NEVER inline a method string — use `apiConstants.ts` (`GET`, `POST`, …).
130
+ - ✅ One `reducerPath` per feature API (kebab → camelCase: `kycApi`, `loanApi`).
131
+ - ✅ Mutations always set `showFailureNotification: true`. Queries usually don't.
132
+ - ✅ `tagTypes` from `tagTypes.ts` catalog (see constants-organization skill).
133
+ - ✅ Audit-sensitive mutations also dispatch audit events (see the toolkit's `bfsi-audit-action` skill).
134
+
135
+ ## Notification flags
136
+
137
+ ```ts
138
+ showSuccessNotification: true; // success path dispatches toast
139
+ showFailureNotification: true; // error path dispatches toast
140
+ ```
141
+
142
+ Both default to `false` — opt-in per endpoint. Read by `axiosBaseQuery` and routed through your Notification slice (see the `axios-auth` skill's notification-wiring reference).
143
+
144
+ ## References
145
+
146
+ - [`references/endpoint-cookbook.md`](references/endpoint-cookbook.md) — list, detail, create, update, delete, paginated, polling, file-upload examples
147
+ - [`references/optimistic-update.md`](references/optimistic-update.md) — when and how to do optimistic UI updates with `onQueryStarted`
148
+ - [`references/cache-strategies.md`](references/cache-strategies.md) — `providesTags` / `invalidatesTags` patterns by feature shape