Create ANY_USAGE_REPORT.md
This commit is contained in:
parent
8aaaa12ca0
commit
b68b2abe18
1 changed files with 313 additions and 0 deletions
313
ANY_USAGE_REPORT.md
Normal file
313
ANY_USAGE_REPORT.md
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
# `any` Usage Audit — what each one should be
|
||||
|
||||
**Branch:** `DEAD_CODE_CLEAN`
|
||||
**Scope:** all `any` keyword usages in `apps/*` and `packages/*` source (`.ts`/`.tsx`), excluding `node_modules`, `dist`, `.wrangler`, `.output`, `.expo`, `.turbo`, `.tanstack`, dumps, `*.sql`, and generated route trees (`routeTree.gen.ts`).
|
||||
**Nature:** documentation only — no code changed.
|
||||
|
||||
## Totals (type-level `any`, excluding prose/comments/generated)
|
||||
|
||||
| Area | Count | Dominant cause |
|
||||
|---|---|---|
|
||||
| `apps/backend` | ~85 | `as any` casts on JWT/Drizzle/error shapes; `: any` where a shared type exists |
|
||||
| `packages/db_helper_sqlite` + `_postgres` | ~105 | `(x: any)` on already-typed Drizzle results; `Promise<any>` helpers |
|
||||
| `apps/admin-ui` | ~161 | `onError/catch (e: any)`; `router.push(... as any)`; entity `useState<any>` |
|
||||
| `apps/user-ui` + `apps/fallback-ui` | ~137 | `catch/onError`; Expo/TanStack `as any`; catalog/cart `item: any` |
|
||||
| `apps/web-ui` | ~138 | `Record<number, any>`/`(p: any)` product+slot callbacks; `onError` |
|
||||
| `apps/admin-web` | ~128 | `onError/catch` (36); domain rows; `navigate({ to: ... as any })` |
|
||||
| `packages/shared` + `web-components` + `ui` + `migrator` | ~28 | untyped component props; RN style/asset props; migrator rows |
|
||||
| **Total** | **~780** | |
|
||||
|
||||
Regex-based raw totals are a bit higher because the word "any" also appears in comments/prose and `tsconfig` files; those are excluded here.
|
||||
|
||||
## The replacement playbook ("what it should have been")
|
||||
|
||||
1. **`catch (e: any)` / `onError: (e: any)` → `unknown`** (and narrow with `e instanceof Error ? e.message : '…'`). This is the single largest mechanical category (~100 sites across the repo).
|
||||
2. **`onError: (error: any)` on tRPC hooks → delete the annotation.** tRPC v11 already infers `TRPCClientError<AppRouter>`; `.message` keeps working.
|
||||
3. **Callback params on typed Drizzle/array results → delete `: any`.** Because `db_index.ts` passes the full `schema` to `drizzle(...)`, `db.query.*` and `.select()` results are fully inferred; `(x: any) => …` is pure noise.
|
||||
4. **`(item: any)` / `(product: any)` in catalog/cart lists → the real entity type:** `MergedProduct` (catalog), `CartItem` (carts), `AdminSku`, `AdminSlotWithProducts`, `AdminOrderDetails`, `UserOrderSummary`, etc.
|
||||
5. **`router.push(… as any)` → typed routes** (`Href` from `expo-router`; `FileRouteTypes['to']`/generated union for TanStack Router). Static routes can simply drop the cast.
|
||||
6. **`Record<string, any>` → `Record<string, unknown>`** for heterogeneous bags.
|
||||
7. **`z.any()` → `z.unknown()`** in Zod schemas (or a precise union).
|
||||
8. **RN/Expo props → their real types:** `StyleProp<ViewStyle>` / `StyleProp<ImageStyle>`, `NativeSyntheticEvent<NativeScrollEvent>`, `TextStyle['fontWeight']`, `ImagePicker.ImagePickerAsset`.
|
||||
9. **Icon `name={x as any}` → `React.ComponentProps<typeof MaterialIcons>['name']`** (the glyph-key union).
|
||||
10. **`Promise<any>` db-helper returns → `InferSelectModel<typeof table>` / explicit `Pick<Row, …>`.**
|
||||
|
||||
## Highest-leverage shared fixes (fix once, removes many)
|
||||
|
||||
These untyped *shared* declarations force `any` at dozens of call sites — tighten them first:
|
||||
|
||||
| Fix | File | Impact |
|
||||
|---|---|---|
|
||||
| `value: any` → a `ConstValueType \| ConstValueType[]` union (union already defined at line 18) | `packages/shared/types/const.types.ts:8` | Removes `value.map((id: any) => …)` in admin-ui **and** admin-web reorder screens |
|
||||
| `ListItemProps.item: any`, `CompactProductCardProps.item: any`, `OffersSectionProps.products: any[]`, `AddedToCartProduct.product: any` → hoist a shared structural product type (or make the props generic) | `packages/shared/types/app-common.types.ts:60,78,87,96` | Caps how far web-ui/user-ui flash/offers/complaints props can be tightened |
|
||||
| `queryParams?: Record<string, any>` → `Record<string, unknown>` | `packages/shared/types/app-common.types.ts:20` | Consumer only does `String(value)` |
|
||||
| `setFile: (file: any) => void` → `ImagePickerAsset \| ImagePickerAsset[] \| null` | `packages/ui/src/components/use-pick-image.tsx:13` | Type flows into admin-ui `setFile: (assets: any)` (3 sites) and `profile-image`/`ImageUploaderNeo` |
|
||||
| Export app-local entity types: `MergedProduct`, `CartItem`, `SlotInfo`/`ProductSlotMap` (web-ui/user-ui); `ProductFormData`, `Variant`, `Attribute`, `CouponFormValues` (admin-ui/admin-web) | respective hook/component files | Removes dozens of `values: any` / `item: any` in mutations and list callbacks |
|
||||
| Backend procedures returning `any[]` (coupon getAll/getReservedCoupons, admin `getUserIncidents`, store) → concrete shared types | `apps/backend/.../admin-apis/apis/{coupon,user,store}.ts` | Removes the downstream `Coupon`/`UserIncident` `any`s in both admin frontends |
|
||||
|
||||
---
|
||||
|
||||
## apps/backend
|
||||
|
||||
### Summary
|
||||
- **85** `any` keywords across **83 lines / 31 files**.
|
||||
- **Top files:** `coupon.ts` & `const-store.ts` (7 each), `admin/user.ts`, `post-order-handler.ts`, `app.ts` (6 each), `admin/product.ts` (5), `auth.ts`, `cache-creator.ts` (4).
|
||||
- **Dominant patterns:** (1) `x as any` around JWT payloads, Drizzle `json` columns, and untyped db-helper returns; (2) `: any` params/locals where a concrete shared type exists (`OrderWithFullData`, `AdminVendorSnippet`, `TagBasicData`, `StaffRole`); (3) generic defaults `<T = any>` and catch clauses.
|
||||
- **10 of the 85 are inside commented-out "Old implementation" blocks** → delete, don't retype.
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| `app.ts:57`, `middleware/auth.middleware.ts:19` | `const decoded = payload as any` | `AppJwtPayload` = `{ userId?: number; staffId?: number; name?: string }` | jose `JWTPayload`; tokens are signed as `SignJWT({ userId })` / `SignJWT({ staffId, name })` |
|
||||
| `app.ts:131,132` | `(err as any).statusCode` | `err instanceof ApiError ? err.statusCode` | `ApiError` is the only error with `statusCode` |
|
||||
| `app.ts:134,135` | `(err as any).status` | `err instanceof HTTPException ? err.status` | Hono `HTTPException` |
|
||||
| `app.ts:141` | `c.json({ message }, status as any)` | `status as StatusCode` | Hono status union |
|
||||
| `types/hono.d.ts:5`, `trpc/trpc-index.ts:7` | `user?: any` | `AppJwtPayload` | consumed as `ctx.user?.userId` |
|
||||
| `trpc/trpc-index.ts:37` | `const err = error as any` | `unknown` + narrow | catch var; reads name/message/code/stack/meta/sql |
|
||||
| `jobs/cache-creator.ts:10,13` | `state: any` | `DurableObjectState` | uses storage/alarm |
|
||||
| `jobs/cache-creator.ts:11,13` | `env: any` | `WorkerEnv` (needs domain input) | passed to `ensureWorkerInit` |
|
||||
| `jobs/payment-status-checker.ts:3,4,5` | `payment:any; order:any; slot:any` | `typeof payments.$inferSelect` / `{id;userId}` / row types | stub interface; only `order.id/.userId` read |
|
||||
| `lib/queue-consumer.ts:4,27,40` | `batch: any` | `MessageBatch<NotifQueueMessage>` / `<{orderIds:number[]}>` / `<CancellationMessage>` | pushed message shapes are known |
|
||||
| `lib/const-store.ts:34,58` | `<T = any>` | `<T = unknown>` | generic default |
|
||||
| `lib/const-store.ts:90,102,107` | `Record<…, any>` | `Record<…, unknown>` | opaque KV values |
|
||||
| `lib/api-error.ts:3,5` | `details?: any` | `unknown` | never consumed typed |
|
||||
| `lib/notif-job.ts:11,22` | `notificationQueue:any`, `notificationWorker:any` | bullmq `Queue`/`Worker` or `Record<string, never>` | stubbed (`{}`) |
|
||||
| `lib/notif-job.ts:91` | `scheduleNotification(userId, payload: any, …)` | `{ title; body; type; orderId? }` | callers pass exactly this |
|
||||
| `lib/worker-init.ts:3,4` | `env: any`, `(globalThis as any).ENV` | `WorkerEnv` / typed global | matches existing typed-global pattern |
|
||||
| `lib/post-order-handler.ts:44,52,95,167,168` | `ordersData: any[]`, `(item: any)`, `createdOrders: any[]`, `Map<…, any[]>` | `OrderWithFullData[]`, `OrderWithFullData['orderItems'][number]`, `PlacedOrder[]` | db-helper return types |
|
||||
| `lib/post-order-handler.ts:86` | `orderData: any` | `OrderWithCancellationData & { refundStatus: string }` | known shape |
|
||||
| `lib/flash-delivery-cron.ts:12` | `env: any` | `WorkerEnv` | |
|
||||
| `lib/env-exporter.ts:62` | `(globalThis as any).ENV \|\| (globalThis as any).process?.env` | typed structural cast | matches rest of codebase |
|
||||
| `lib/notif-job.ts:91`, `trpc/.../staff-user.ts:64,112` | mixture | `AdminVendorSnippet`, `StaffRole` | concrete shared types exist |
|
||||
| `admin-apis/apis/store.ts:17,37` | `stores: any[]`, `store: any` | `Store & { owner: User }` | query includes `owner` |
|
||||
| `admin-apis/apis/banner.ts:59`, `user-apis/apis/auth.ts:274` | `catch (e: any)` | `unknown` + narrow | |
|
||||
| `admin-apis/apis/const.ts:38` | `z.any()` | `z.unknown()` | |
|
||||
| `admin-apis/apis/slots.ts:74` | `deliverySequence: z.any()` | `Record<string, number[]>` union | shared `AdminDeliverySequence` |
|
||||
| `user-apis/apis/payments.ts:129` | `payload as any` | `Record<string, unknown>` | schema is `jsonText<unknown>` |
|
||||
| `admin-apis/apis/product.ts:265,357,754` | `as any` casts / `product: any` | `CreateProductInput` / `AdminProductTagWithProducts` | helpers/return types exist |
|
||||
| `user-apis/apis/user.ts:61`, `auth.ts:110,374` | `new Date(userDetail.dateOfBirth as any)` | drop cast | field is `Date \| null` |
|
||||
| `user-apis/apis/order.ts:293` | `slotId: null as any` | `null` | param is `number \| null` |
|
||||
| `admin-apis/apis/coupon.ts:129,141,153,154,351,366,440` | `coupons: any[]`, `Promise<any>`, `(au:any)`, `(ap:any)`, `coupon: any` | `Coupon & { applicableUsers; applicableProducts }` / `ReservedCoupon` (needs domain input) | relations not modelled in shared |
|
||||
| `common-apis/common.ts:79,84,85` | `(id:any)`, `(tag:any)`, `orderedTags:any[]` | `number`, `TagBasicData`, `TagBasicData[]` | `getConstant<T>` default; `getAllTagsForCache()` |
|
||||
| `admin-apis/apis/user.ts:48,60,98,117,170,258` | `(u:any)` etc | `Pick<User,…>`, `Pick<PlacedOrder,…>`, `UserIncident` (needs domain input) | select shapes |
|
||||
| Dead-code `any`s (delete) | `product.ts:565,700`, `banner.ts:194`, `slots.ts:176,347`, `address.ts:192`, `admin/order.ts:384`, `vendor-snippets.ts:260`, `const-store.ts:91,97` | — | commented-out old impls |
|
||||
|
||||
**Easy wins:** drop redundant `as any` (`product.ts:265/357`, `order.ts:293`, `user.ts:61`, `auth.ts:110/374`, `payments.ts:129`); `: any`→`unknown` (`api-error`, `const-store` generics, `payment-status-checker`, `payments-utils`); `catch`→`unknown` (`banner.ts:59`, `auth.ts:274`); `z.any()`→`z.unknown()`; delete dead `any`s; define `AppJwtPayload` once.
|
||||
|
||||
---
|
||||
|
||||
## packages/db_helper_sqlite + db_helper_postgres
|
||||
|
||||
**Summary:** **105** tokens across **98 lines / 17 files**. Top: `sqlite/admin-apis/product.ts` (27), `sqlite/admin-apis/order.ts` (19), `run-batched.ts` (7), `admin-apis/user.ts` (7 each pkg), `admin-apis/coupon.ts` (6 each). Dominant: `(x: any)` on already-typed Drizzle results (~60) — just delete. `Row<T>` = `InferSelectModel<typeof T>`, `Insert<T>` = `InferInsertModel<typeof T>`.
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| `sqlite/admin-apis/product.ts:182,235,385,483,530` | `(ci\|f: any)` | `CreateComboItemInput` / `SkuFeatureLike` | element types of typed relations |
|
||||
| `product.ts:185,238` | `(f: any)` | `Row<skuFeatures>` | |
|
||||
| `product.ts:596` | `(tag: any)` | inferred (cast at 594) | |
|
||||
| `product.ts:598,669,709` | `(assignment: any)` | `Row<productTags> & { sku: … }` | |
|
||||
| `product.ts:756` | `(review: any)` | inferred select shape | |
|
||||
| `product.ts:817,822` | `(group:any)`, `(membership:any)` | inferred | |
|
||||
| `product.ts:367` | `updateProduct(id, input: any)` | `UpdateProductInput` (domain) | |
|
||||
| `product.ts:39,41,114,340,498,545` | `any[]`/`(ci:any)` | `CreateComboItemInput` / `AdminProductComboItem[]` | |
|
||||
| `product.ts:384,398,401` | `(sku: any)` | `CreateSkuInput & { id?: number }` | |
|
||||
| `product.ts:956` | `const updateData: any` | `Partial<Insert<productMarketStats>>` | |
|
||||
| `sqlite/admin-apis/order.ts:174,239,250,364,369,378,388,412,523,528,538,549,554,577,615,616,622,643` | `(u\|item\|f\|order\|acc: any)` | delete → inferred | typed query results |
|
||||
| `sqlite/admin-apis/slots.ts:122,125,133,199` | `(item\|s\|sku: any)` | delete → inferred | |
|
||||
| `slots.ts:194` | `(item: any)` + reads `item.isDeleted` | inferred; **add `isDeleted: true` to `columns` at :191** | hidden bug surfaced by typing |
|
||||
| `slots.ts:56` | `fetchExistingSkuIds(tx: any, …)` | Drizzle tx type (`Parameters<Parameters<typeof db.transaction>[0]>[0]`) | |
|
||||
| `stores/store-helpers.ts:312,315,326` | `(item\|s\|sku: any)` | delete → inferred | |
|
||||
| `admin-apis/store.ts:7,17` (both pkgs) | `Promise<any[]>` / `Promise<any\|null>` | `Array<Row<storeInfo> & { owner: Row<staffUsers>\|null }>` | |
|
||||
| `admin-apis/staff-user.ts:22,78` (both) | `Promise<any[]>` | inferred relation / `StaffRole[]` | |
|
||||
| `admin-apis/user.ts:5,28,95,122,184,224,245` (both) | `Promise<any…>` | `Row<users>`, `Pick<Row<users>,…>`, `Row<userIncidents>`, etc. | select shapes |
|
||||
| `admin-apis/coupon.ts:10,53,175,211,213,300` (both) | `any[]`/`Promise<any…>`/`input: any` | `Row<coupons> & {…}`, `Row<reservedCoupons>`, `ReservedCouponInput` | InferInsertModel |
|
||||
| `user-apis/auth.ts:102,119` (both) | `const userUpdate: any` | `Partial<Insert<users>>` / `Partial<Insert<userDetails>>` | |
|
||||
| `user-apis/auth.ts:184` (both) | `catch (error: any)` | `unknown` + narrow | `23505` is pg-only |
|
||||
| `lib/run-batched.ts:3,19` | `(cb: (tx: infer Tx) => any, ...args: any[]) => any` | `unknown` | unused callback return/args |
|
||||
| `lib/automated-jobs.ts:25` (both) | `value: any` | `unknown` | |
|
||||
|
||||
**Easy wins:** delete `: any` from ~60 map/filter callbacks; `run-batched` `any`→`unknown`; `automated-jobs` `value: unknown`; object literals → `Partial<Insert<…>>`; `catch`→`unknown`; **free bug fix** at `slots.ts:191/194`; ~35 `Promise<any>` return annotations via `Row`/`Insert`.
|
||||
|
||||
---
|
||||
|
||||
## apps/admin-ui
|
||||
|
||||
**Summary:** **161** code occurrences / 49 files. Top: `delivery-sequences/index.tsx` (10), `prices-overview` (11), `products/add+edit` (14), `_layout.tsx` (13). Dominant: `catch/onError (error: any)` (35) and `router.* as any` (36) — both mechanical.
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| `_layout.tsx:53…137`, `dashboard/index.tsx:32,99,242`, banners/products/slots/stores/prices/manage-orders (≈18 files) | `router.push("…" as any)` | `as Href` (`expo-router`); drop cast for static routes | typed routes |
|
||||
| `dashboard/index.tsx:39,250,275`, `manage-orders/index.tsx:77` | `name={item.icon as any}` | `keyof typeof MaterialIcons.glyphMap` | glyph union |
|
||||
| `TagMenu.tsx:62`, `stores/edit:28`, `send-notifications:48`, `coupons/*`, `prices:286`, `SlotForm:92,107`, `delivery-sequences`, `order-details`, `orders` (≈24) | `onError: (error: any)` | delete annotation | tRPC infers |
|
||||
| VendorSnippet/ProductGroupForm/products/coupons/stores/vendor-snippets/product-tags/send-notifications (≈11) | `catch (error: any)` | `unknown` + narrow | |
|
||||
| `coupons/index.tsx:11,111` | `item: any` | `Coupon & {relations}` / reserved (domain) | backend returns `any[]` |
|
||||
| `stores/index.tsx:15` | `item: any` | `Store` | |
|
||||
| `products/index.tsx:148` | `{ item: any }` | `Omit<AdminProductWithRelations,'skus'> & { skus: SerializedAdminSku[] }` | |
|
||||
| `slots/index.tsx:11`, `rebalance-orders:10` | `item: any` | `AdminSlotWithProducts` | |
|
||||
| `slots/slot-details.tsx:24`, `product-groupings:130`, `products/detail:146` | `useState<any…>` | `AdminSlotProductSummary[]`, `AdminSku[]`, `AdminProductReviewWithSignedUrls` | |
|
||||
| `slots/index:238`, `rebalance:123` | `useState<any[]>` | `string[]` | product names |
|
||||
| `products/add:15,64,29,36,80`, `edit` (same) | `values: any`, `(variant:any)`, `(a:any)`, `(ci:any)` | `ProductFormData`, `Variant`, `Attribute`, `CreateComboItemInput` (export from ProductForm) | |
|
||||
| `BannerForm:66`, `StoreForm:72`, `products/detail:32` | `setFile: async (assets: any)` | `ImagePicker.ImagePickerAsset \| ImagePickerAsset[]` | |
|
||||
| `TagForm.tsx:40` | `forwardRef<any,…>` | `unknown` | no imperative handle |
|
||||
| `TagForm.tsx:57` | `(sku: any)` | `SkuSummary` | |
|
||||
| `toaster.tsx:39` | `data: any` | `{ rideId?; carId? } \| null` (domain) | |
|
||||
| `CouponForm.tsx:60,66,156,191,192,205,237,238` | `function(value:any)`, `(values as any)`, `(errors.couponCodes as any)` | `CouponFormValues`, narrow error shapes | |
|
||||
| `ProductGroupForm:13`, `SnippetOrdersView:21`, `SlotForm:37,48,191` | `products: any[]`, `sequence: any[]`, `(snippet/p: any)` | `AdminSku[]`, `number[]`, `AdminVendorSnippetWithAccess`, `AdminSlotProductSummary` | |
|
||||
| `UserIncidentsView:141` | `(incident: any)` | `UserIncident` (domain) | |
|
||||
| `customize-app/index:9,58,59,60` | `Record<string,any>`, `value:any`, `setFieldValue(any)`, `router:any` | `Record<string,unknown>`, `unknown`, `Router` | |
|
||||
| `popular-items:139`, `all-items-order:111`, `product-tags/order:102` | `value.map((id: any) => parseInt(id))` | `number` (root: `Constant.value`) | |
|
||||
| `prices-overview:25,28,29,30,46,153,204,224,270` | `sku:any`, `Record<string,any>`, `const update:any` | local `PriceSku`, existing `PendingChange`, mutation input | |
|
||||
| `delivery-sequences:53,354,373,389,438,588,622` | `orders:any[]`, `(deliverySequence as any)` | `AdminSlotOrder[]`, drop cast (`AdminDeliverySequence`) | |
|
||||
| `order-details:148` | `const mutationData:any` | `initiateRefund` input | |
|
||||
| `coupons/create:27` | `values: any` | `CouponFormValues` | |
|
||||
|
||||
**Easy wins:** drop `: any` on 24 `onError`; `catch`→`unknown` (11); `as any`→`as Href` (36); icon glyph types (4); drop `deliverySequence as any` (6); remove `products/edit:180 as any`; `triggerStyle` → `StyleProp<ViewStyle>`; export `ProductFormData/Variant/Attribute`.
|
||||
|
||||
---
|
||||
|
||||
## apps/user-ui + apps/fallback-ui
|
||||
|
||||
**Summary:** **137** real type-level / 46 files (4 non-type "any" words ignored). Top: `home/index.tsx` (20), `cart-page` (11), `ProductDetail` (11). Dominant: `catch/onError` (29); router casts (23); catalog/cart `item: any` (30+).
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| login/register/edit-profile/ComplaintForm/OrderMenu/ProductDetail/registration-form + fallback create-coupon/demo | `catch (error: any)` | `unknown` + narrow | |
|
||||
| many `onError: (error: any)` (14+) | `onError: (error: any)` | `unknown` / delete (tRPC infers) | |
|
||||
| `me/index`, `SlotSpecificView`, `cart-page` (≈12) | `router.replace/push(target as any)` | `Href` | |
|
||||
| fallback AuthWrapper/home/login/super-admin/user-home (≈13) | `navigate({ to: '…' as any })` | drop cast | routes registered |
|
||||
| `me/index:137`, `my-orders:111,278` | icon `as any` | `ComponentProps<typeof Ionicons/MaterialIcons>['name']` | |
|
||||
| `my-orders:68,69` | `getStatusColor(): any` | typed `StatusColor` | |
|
||||
| `home/index.tsx` (many) | `slot: any`, `(p: any)`, `dashboardTags:any[]`, `Record<number,any[]>`, `useRef<any>`, `onLayout(e:any)`, `useState<any[]>` | `UserSlotWithProducts`, `UserSlotProduct`, `AllProductsApiType['tags']`, `MergedProduct[]`, `ComponentRef<typeof ScrollView>`, `LayoutChangeEvent` | catalog types exist |
|
||||
| `ProductDetail:48,75,76,79,87,147,710` | `useState<any[]>`, `(slot/p/a/b/item:any)`, `setFile(assets:any)` | review/cart/slot types + `ImagePickerAsset` | shared `UserProductReviewWithSignedUrls` missing `adminResponse`/`signedAdminImageUrls` (domain) |
|
||||
| `cart-page:60` | `coupon: any` | `UserCouponWithRelations` | |
|
||||
| `registration-form:29` | `useState<any>` | `ImagePicker.ImagePickerAsset` | |
|
||||
| `useUploadToObjectStore:26` | `contextString as any` | drop cast | shared `ContextString` assignable |
|
||||
| `notif-context:45,46` | `useRef<any>` | listener return types | |
|
||||
| `toaster:5` | `data: any` | `Record<string, unknown>` | |
|
||||
| `AddToCartDialog` (6) | `(item/slot/p: any)`, `Record<number,any>` | `CartItem`, `UserSlotWithProducts`, `UserSlotProduct` | |
|
||||
| `AuthContext:14` | `Record<string,any>` | tighten shared `AuthRedirectOptions.queryParams` | |
|
||||
| `getCurrentUserId:8` | `jwtDecode(token)` any | `jwtDecode<{id?;userId?}>` | |
|
||||
| fallback `order-details:33,51,223`, `user-details:44,45,141`, `login:23`, `demo:11,76`, `inauguration:74` | `as any` / `any[]` / `any` | `AdminOrderDetails`, inferred, `ReturnType<typeof setInterval>`, local `DemoResponse` | |
|
||||
| `ProductCard:22,29,84,93,94` | `item:any`, `ComponentType<any>`, `(cartItem:any)`, `Record<number,any>`, `(slot:any)` | `MergedProduct`, `ComponentType<PropsWithChildren>`, `CartItem`, `UserSlotWithProducts` | |
|
||||
| `SlotSpecificView:232,252,457` | `item:any`, `(cartItem:any)`, `(p:any)` | `MergedProduct`, `CartItem` | |
|
||||
| `PaymentAndOrderComponent:17,24,25` | `cartItems:any[]`, `constsData:any`, `selectedCoupons:any[]` | `CartItem[]`, `EssentialConstsApiType`, `EligibleCoupon[]` | |
|
||||
| `hooks/useProductSlotIdentifier:18,25`, `prominent-api-hooks:127` | `(slot/a/b/p: any)` | drop → inferred | |
|
||||
|
||||
**Easy wins:** bulk `unknown` (29); delete fallback `as any` casts (≈18 incl. `contextString`); catalog/cart annotations (≈30) → `MergedProduct`/`CartItem`; icon types (4); trivial typed `any`s (jwt, setInterval, notif refs, toaster, styles).
|
||||
|
||||
---
|
||||
|
||||
## apps/web-ui
|
||||
|
||||
**Summary:** **138** annotations / 35 files (2 prose excluded). Top: `slot-view` (20), `home.index` (18), `flash` (10), `home.product.$id` (9). Dominant: untyped list callbacks over cached API data (`MergedProduct`, `UserSlotWithProducts`), then `(error: any)`.
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| `FloatingCartBar:43,44`, `PaymentAndOrderComponent:34,35`, `cart:18,19`, `checkout:38,39`, `home.cart:18,19` | `Record<number,any>` / `(p:any)` | `Record<number, MergedProduct>` / `(p: MergedProduct)` | from `useAllProducts()` |
|
||||
| `AddToCartDialog:45,78`, `ProductCard:38`, `home.product.$id:63`, `flash:205` | `(item:any)` | `CartItem` | `useGetCart()` |
|
||||
| `slot-view:399` | `(cartItem:any).productId` | `(cartItem: CartItem).skuId` | **latent bug** |
|
||||
| `AddToCartDialog:53,57,59,143`, `usePopulateCentralStores:15,30,31,43`, `home.product.$id:51,52,55,94,316,397`, `slot-view:58…153`, `home.index:623` | `(slot:any)`, `slot.products.forEach((p:any)`, slot maps, `(a/b:any)` | `UserSlotWithProducts`, `UserSlotProduct`, `Record<number, UserSlotWithProducts>` | `useSlots()` |
|
||||
| `usePopulateCentralStores:15` (+43–48) | extra fields `deliveryDate/displayDate/displayTime` | **domain**: not on `UserSlotWithProducts` but required by `SlotInfo` | stale/dead-field mismatch |
|
||||
| `usePopulateCentralStores:25` | `Record<number,any>` | `ProductSlotMap[number]` (export) | |
|
||||
| `flash:38,67,77,83,166`, `home.index:154,155,225,326`, `home.search:87,166`, `offers:32,67,68`, `slot-view:139…238`, `stores.$storeId:44,125,163`, `usePopulateCentralStores:28`, `prominent-api-hooks:167` | `(product/p/a/b:any)` | `MergedProduct` | |
|
||||
| `stores.$storeId:28,38` | `new Map<number,any>`, `(product:any)` | `Map<number, MergedProduct>`, `(product: UserStoreProduct)` | |
|
||||
| `home.index:133,135,142,143` | `Record<number,any[]>`, `Map<number,any>`, `const ordered/rest: any[]` | `MergedProduct[]` etc | |
|
||||
| `ProductCard:11` | `item:any` | `MergedProduct` | |
|
||||
| `flash:36,54,124,156`, `home.index:378,530`, `slot-view:228,294,339`, `stores:78,104`, `stores:161` | `(store:any)`, `stores:any[]`, `(product:any)` | `UserStoreSummary`, `UserStoreSampleProduct` | |
|
||||
| `checkout-hooks:16,44,67`, `CheckoutAddressSelector:11,72`, `checkout:31`, `flash.checkout:74`, `home.checkout:75` | `useState<any>`, `(addr/address:any)` | `UserAddress` | |
|
||||
| `AddressForm:48,59,90,92`, `MeOrderMenu:68,95`, `PaymentAndOrderComponent:67`, `me.addresses:121,149`, `me.edit-profile:46,56,103`, `me.orders.$id:42,54` | `onError/catch (e:any)`, `(error:any)` | delete / `unknown`; `AddressForm` narrow `Yup.ValidationError` | |
|
||||
| `me.orders.$id:99` | `order as any` | drop (`UserOrderDetail`) | |
|
||||
| `me.orders.$id:117,301`, `me.orders:189,75,325` | `(item/product:any)`, `useState<any[]>` | `UserOrderItemSummary`, `UserOrderSummary` | |
|
||||
| `BottomNavigation:72`, `Sidebar:81`, `me:167` | `to: … as any` | generated route union | |
|
||||
| `Topbar:27` | `'/home/search' as any` | drop cast | |
|
||||
| `home.index:122,291,472,474,513` | `(t/tag/b/_:any)`, `banners:any[]` | tag type, `UserBanner`, `string` | |
|
||||
| `home.product.$id:379` | `(deal:any)` | `SpecialDealCore` | |
|
||||
| `lib/auth-context:15,24,110` | `userDetails:any`, `(details:any)` | local `User` | |
|
||||
| `flash-cart-store:4,5` | `any \| null` | `MergedProduct \| null` (domain) | |
|
||||
| `useUploadToObjectStorage:26` | `contextString as any` | drop | |
|
||||
| `ProductCard:47,48`, `PaymentAndOrderComponent:13`, `slot-view:101,104`, `me.complaints:168` | `Record<number,any>`, `(slot:any)`, `cartItems:any[]`, `as any` time, `(complaint:any)` | `SlotInfo`, `CartItem[]`, widen helper, `UserComplaint` | |
|
||||
|
||||
**Easy wins:** drop redundant annotations on tRPC `onError` (10) and `cartData.items.find` (5); replace 5-file `productsById` boilerplate (10); `catch`→`unknown` (4); export `CartItem` + `SlotInfo`/`ProductSlotMap`; drop `navigate` casts; **fix `slot-view:399` bug**.
|
||||
|
||||
---
|
||||
|
||||
## apps/admin-web
|
||||
|
||||
**Summary:** **128** annotations (3 prose excluded) / 50 files. Top: `orders.sequence` (11), `prices` (10), `products.edit` (9). Dominant: `onError/catch` (36 = 28%), untyped rows, route casts.
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| 24 sites (`stores.edit:35`, `orders.sequence:471,623,665`, `orders.list:71,171`, `coupons.*`, `slots:51`, `complaints:81`, `SlotForm:91,106`, `notifications:35`, `TagMenu:56`, `UserIncidentsView:31`, `staff-auth:62`, `orders.$id:68,84,95,105`, `CancelOrderDialog:34`, `prices:281`, `stores.new:27`) | `onError: (error:any)` | delete | tRPC infers |
|
||||
| 13 sites (`product-tags.new:50`, `product-tags.edit:65`, `coupons.new:57`, `products.$id:81`, `notifications:72`, `vendor-snippets:54,296`, `OrderOptionsMenu:69`, `products.new:106`, ProductGroupForm:68, VendorSnippetForm:87, products.edit:189, stores:114) | `catch (error:any)` | `unknown` + narrow | |
|
||||
| `orders.sequence:363,382,398,456,603,635` | `(deliverySequence as any)` | drop → `AdminDeliverySequence` | |
|
||||
| `slots.$id.edit:48`, `slots:28`, `rebalance:28`, `slots.new:43`, `SlotForm:47` | `(p:any)` | `AdminSlotProductSummary` | |
|
||||
| `SlotForm:36` | `(snippet:any)` | `AdminVendorSnippetWithAccess` (shape mismatch, domain) | |
|
||||
| `SlotForm:189`, `TagForm:114`, `prices:41` | `(prod/sku/f:any)` | `AdminSku`, `SkuSummary`, `AdminSkuFeature` | |
|
||||
| `product-tags.edit:92` | `(p:any)` | `AdminProductTagAssignment` | |
|
||||
| `ProductGroupForm:12`, `product-groupings:25,108` | `products:any[]`, `useState<any[]>` | `AdminSku[]` | |
|
||||
| `UserIncidentsView:142` | `(incident:any)` | `UserIncident` (domain) | |
|
||||
| `products:149` | `(product:any)` | `AdminProductWithRelations` | |
|
||||
| `rebalance:13`, `slots:14` | `item:any` | `AdminSlotWithProducts` | |
|
||||
| `rebalance:16,128`, `slots:15,242` | `Dispatch<…any[]>`, `useState<any[]>` | `string[]` | |
|
||||
| `slots.detail:31` | `useState<any[]>` | domain (`AdminSlotProductSummary` lacks `shortDescription`) | |
|
||||
| `orders.sequence:69` | `orders:any[]` | `AdminSlotOrder[]` | |
|
||||
| `prices:20,23,24,25,148,199,219,265` | `sku:any`, `Record<string,any>`, `const update:any` | `SkuRow`, existing `PendingChange`, mutation input | |
|
||||
| `vendor-snippets:233` | `useState<any>` | `{ orders; snippetCode }` (align `totalAmount` number/string) | |
|
||||
| `SnippetOrdersView:20` | `sequence:any[]` | `unknown`/`number[]` | shared is `unknown` |
|
||||
| `coupons.new:30`, `products.edit:87`, `products.new:19` | `values: any` | export `CouponFormValues` / `ProductFormData` | |
|
||||
| `CouponForm:78,84,171,206,207,220,252,253` | `function(value:any)`, `(values as any)`, `(errors as any)` | `CouponFormValues`, narrow | |
|
||||
| `coupons.tsx:14,17,114,220,363` | `item/coupon:any` | `Coupon & {relations}` / reserved (domain) | |
|
||||
| `MultiSelect:25`, `SearchableSelect:23`, `coupons:302,430` | `triggerComponent?: any` | `(props:{onClick:()=>void}) => ReactNode` | |
|
||||
| `SnippetMenu:20`, `TagMenu:11` | `triggerStyle?: any` | `string \| CSSProperties` | |
|
||||
| `TagForm:96` | `forwardRef<any,…>` | `unknown` (ref unused) | |
|
||||
| `customize-app:12,61,62,63` | `Record<string,any>`, `value:any`, `setFieldValue(any)`, `router:any` | `Record<string,unknown>`, `unknown`, local router shape | |
|
||||
| `customize-app.ordering:113`, `popular:126`, `product-tags.order:106` | `value.map((id:any)=>parseInt(id))` | root: `Constant.value` | |
|
||||
| `stores.edit:33`, `dashboard.index:82`, `customize-app:173`, `orders.tsx:24`, `dashboard.tsx:108` | `navigate({to: … as any})` | generated route union / drop | |
|
||||
| `products.edit:66,101,108,139,162,166,183`, `products.new:33,40,68,84,88` | `(variant/ci/a/attr:any)`, `as any` | `Variant`, `AdminProductComboItem`, `SkuFeatureLike`, `CreateComboItemInput` | |
|
||||
| `orders.$id:159` | `const mutationData:any` | `initiateRefund` input | |
|
||||
| `toaster:16`, `event-bus:6,11` | `_data:any`, `...args: any[]` | `unknown` | |
|
||||
|
||||
**Easy wins:** drop 23 `onError`; 13 `catch`→`unknown`; type slot rows (9); `prices` `SkuRow`+`PendingChange` (10); export `ProductFormData/Variant/Attribute/CouponFormValues`; fix shared `Constant.value`; typed routes (5).
|
||||
|
||||
---
|
||||
|
||||
## packages/shared + web-components + ui + migrator
|
||||
|
||||
**Summary:** **28** tokens / 17 files. Top: `app-common.types.ts` (5), `migrator/sqliteToPostgres` (4), `web-components/data-table.tsx` (4). Dominant: untyped component props + RN style/asset props. (No `catch (e: any)` in scope.)
|
||||
|
||||
| Location | Current code | Should be | Why |
|
||||
|---|---|---|---|
|
||||
| `packages/shared/types/app-common.types.ts:20` | `queryParams?: Record<string, any>` | `Record<string, unknown>` | only `String(value)` |
|
||||
| `app-common.types.ts:60` | `item: any` | generic `ListItemProps<T>` (domain) | shared list renderer |
|
||||
| `app-common.types.ts:78` | `item: any` | hoist shared product type (domain) | consumers use id/images/name/flashPrice/incrementStep |
|
||||
| `app-common.types.ts:87` | `products: any[]` | shared product type (domain) | offers section |
|
||||
| `app-common.types.ts:96` | `product: any` | shared product type (domain) | AddToCartDialog |
|
||||
| `packages/shared/types/const.types.ts:8` | `value: any` | `ConstValueType \| ConstValueType[]` (defined line 18) | consumers branch/narrow |
|
||||
| `migrator/sqliteToPostgres/index.ts:47` | `.all() as any[]` | named `SqliteColumnRow` | PRAGMA shape fixed |
|
||||
| `migrator/.../index.ts:113` | `parseValue(value:any): any` | `unknown` | returns JSON.parse/raw |
|
||||
| `migrator/.../index.ts:181` | `.all() as any[]` | `Record<string, unknown>[]` | dynamic `SELECT *` |
|
||||
| `packages/ui/src/services/axios.ts:56` | `(err as any).original = error` | `Object.assign` / typed `Error & { original: unknown }` | |
|
||||
| `packages/ui/src/lib/refresh-context.tsx:7` | `queryClient: any` | `QueryClient` | dep exists |
|
||||
| `packages/ui/src/lib/tailwind.ts:6` | `create(tailwindConfig as any)` | `as TwConfig` (`twrnc`) | |
|
||||
| `packages/ui/src/components/image-viewer.tsx:16` | `style?: any` | `StyleProp<ImageStyle>` | |
|
||||
| `packages/ui/src/components/info-dialog.tsx:15`, `dropdown.tsx:17` | `style?: any` | `StyleProp<ViewStyle>` | |
|
||||
| `packages/ui/src/components/search-bar.tsx:21` | `forwardRef<any,…>` | `React.ElementRef<typeof PaperTextInput>` | |
|
||||
| `packages/ui/src/components/text.tsx:25` | `let fontWeight: any` | `TextStyle['fontWeight']` | |
|
||||
| `packages/ui/src/components/profile-image.tsx:11,25` | `file?: any`, `setFile:(file:any)` | `ImagePickerAsset & { name }` | |
|
||||
| `packages/ui/src/components/use-pick-image.tsx:13` | `setFile: (file: any) => void` | `ImagePickerAsset \| ImagePickerAsset[] \| null` | root of many `assets: any` |
|
||||
| `packages/ui/src/components/ImageUploaderNeo.tsx:22` | `(files: any)` | `ImagePickerAsset \| ImagePickerAsset[] \| null` | |
|
||||
| `packages/ui/src/components/ImageCarousel.tsx:27` | `(event: any)` | `NativeSyntheticEvent<NativeScrollEvent>` | |
|
||||
| `packages/web-components/.../my-text-input.tsx:45` | `{...(props as any)}` | `TextareaHTMLAttributes` union | spread onto `<textarea>` |
|
||||
| `packages/web-components/.../data-table.tsx:7,12,13` | `(value:any,row:any)`, `data:any[]`, `(row:any,index)` | generic `Column<T>` / `DataTableProps<T>`, `T extends Record<string, unknown>` | |
|
||||
|
||||
**Easy wins:** `queryParams`/`Constant.value`; migrator `unknown` + typed PRAGMA row; `QueryClient`/`TwConfig`; RN prop types (10); `Object.assign`; make `DataTable` generic. **4 domain items:** product-shaped props in `app-common.types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Suggested remediation order
|
||||
|
||||
1. **Mechanical, huge payoff (~200 sites, no domain knowledge):** `catch`/`onError` → `unknown`; delete tRPC `onError` annotations; `Record<string, any>` → `unknown`; `z.any()` → `z.unknown()`; delete `: any` from typed Drizzle callbacks.
|
||||
2. **Shared root causes (removes dozens downstream):** `Constant.value`, `app-common.types.ts`, `use-pick-image.tsx`, export `CartItem`/`SlotInfo`/`MergedProduct` and `ProductFormData`/`CouponFormValues`.
|
||||
3. **Backend result typing:** replace `Promise<any…>` db-helper returns and `any[]` procedures with `InferSelectModel`/shared types — unblocks both admin frontends.
|
||||
4. **Typed routes:** Expo `Href` / TanStack generated union (removes ~50 `as any`).
|
||||
5. **Domain-input types to define:** `UserIncident`, reserved-coupon/client-coupon-with-relations, `RefundInput`, shared product type, `NotificationToast` payload.
|
||||
6. **Bugs masked by `any` (fix while typing):** `web-ui/src/routes/slot-view.tsx:399` (`cartItem.productId` → `skuId`), `db_helper_sqlite/src/admin-apis/slots.ts:191/194` (`isDeleted` not selected), `usePopulateCentralStores.ts:15` (`deliveryDate`/`displayDate`/`displayTime` fields not on the slot type).
|
||||
Loading…
Add table
Reference in a new issue