Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/LedgerHQ/ledger-live --skill rtk-query-api명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Place and organize Ledger Wallet code in the DDD monorepo. Use when creating, moving, or reviewing code under apps, features, domain, shared, or support; deciding which layer owns a concern; structuring packages and flow steps; or checking package names, dependency boundaries, platform variants, and legacy imports.
A new-architecture package (shared/, domain/, features/) exposes its API through barrels that contain nothing but `export *`, and keeps its private code in an internals location. Read this when creating a package under shared/, domain/ or features/, when editing any `index.*` file, or when a `lint:structure` check fails.
A new-architecture package (shared/, domain/, features/) exposes its API through barrels that contain nothing but `export *`, and keeps its private code in an internals location. Read this when creating a package under shared/, domain/ or features/, when editing any `index.*` file, or when a `lint:structure` check fails.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | rtk-query-api |
| description | RTK Query createApi best practices |
createApi calls against the same backend// ✅ GOOD - state-manager/api.ts
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { EntityTags } from "./types";
export const myApi = createApi({
reducerPath: "myApi",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
tagTypes: [EntityTags.Entity, EntityTags.Entities],
endpoints: (build) => ({
getEntity: build.query<Entity, string>({
query: (id) => `entities/${id}`,
providesTags: [EntityTags.Entity],
}),
}),
});
export const { useGetEntityQuery } = myApi;
Define tags as enums in state-manager/types.ts:
export enum EntityTags {
Entity = "Entity",
Entities = "Entities",
}
In domain/api/, this is the default — not something you reach for once a second use case appears.
Always split reaching the backend from what you ask it for:
| Half | Owner | Contains |
|---|---|---|
| Reaching a backend | @shared/api-services — one dir per backend | Base URL, base query, retry, reducerPath, extraArgument contract |
| What you ask it for | @domain/api-<name> | Endpoints, wire schemas, transforms, cache tags, hooks |
Doing it upfront costs nothing and means the second use case is a one-line addition rather than a
migration. Two createApi calls against one backend would give you two store slices, two caches and
two middlewares for one service.
The shared half declares an empty api. The use-case half adds to it with
injectEndpoints
for endpoints and enhanceEndpoints({ addTagTypes }) for tags. Both mutate and return the same api
object, so one reducer, one middleware and one cache serve every use case.
There are no exceptions. If a backend's base query currently needs use-case knowledge — mock handlers
keyed by endpoint URL, endpoint-name lookups, response types from its own wire schemas — that is a
problem to fix in the base query, not a reason to keep a second createApi.
// ✅ GOOD - the service api: base query + config. No endpoints, no tags.
export const myServiceApi = createApi({
reducerPath: "myServiceApi",
baseQuery: myServiceBaseQuery,
tagTypes: [],
endpoints: () => ({}),
});
// ✅ GOOD - a use case adds its own tags, then its endpoints
export const FIRST_USE_CASE_TAGS = ["Entity"] as const;
export const firstUseCaseApi = myServiceApi
.enhanceEndpoints({ addTagTypes: FIRST_USE_CASE_TAGS })
.injectEndpoints({
endpoints: build => ({
getEntity: build.query<Entity, string>({
query: id => `entities/${id}`,
providesTags: [...FIRST_USE_CASE_TAGS],
}),
}),
});
export const { useGetEntityQuery } = firstUseCaseApi;
injectEndpoints does not accept
tagTypes, which makes it tempting to declare every tag upfront in the shared file — don't.
enhanceEndpoints({ addTagTypes }) widens the tag union in place, so a tag stays next to the
endpoints that provide it and adding a use case never means editing a shared file.injectEndpoints cannot retype the original.@shared/api-services in order to call endpoints on it.State. Type such
helpers on the service api.overrideExisting defaults to false — injecting an endpoint name that already exists is
silently ignored unless you opt in.build.query for GET requestsbuild.mutation for POST/PUT/DELETEbuild.query<ResponseType, ArgType>void for no arguments: build.query<Data[], void>types.tsprovidesTags on queries for cache invalidationinvalidatesTags on mutations to trigger refetchkeepUnusedDataFor for custom cache durationendpoints: (build) => ({
getItems: build.query<Item[], void>({
query: () => "items",
providesTags: [ItemTags.Items],
keepUnusedDataFor: 60, // seconds
}),
addItem: build.mutation<Item, Partial<Item>>({
query: (body) => ({ url: "items", method: "POST", body }),
invalidatesTags: [ItemTags.Items],
}),
}),
transformResponse to reshape API datatransformErrorResponse for custom error handlinggetItems: build.query<Item[], void>({
query: () => "items",
transformResponse: (response: ApiResponse) => response.data.items,
}),
baseQuery or queryFn{ data } on success, { error } on failure// ✅ GOOD - errors are caught and returned
queryFn: async (arg) => {
try {
const data = await fetchData(arg);
return { data };
} catch (error) {
return { error: { status: "CUSTOM_ERROR", data: error } };
}
},
Register APIs in reducers/rtkQueryApi.ts, keyed by reducerPath. For a shared backend, register the
service api — its endpoints arrive via the use-case packages the view-models import. The registry
then reads as a list of the backends the app talks to:
const APIs = {
[myApi.reducerPath]: myApi,
[myServiceApi.reducerPath]: myServiceApi,
};
Two entries whose reducerPath resolves to the same string is a compile error
(TS1117: An object literal cannot have multiple properties with the same name), even for computed
properties — which is what catches an accidental double-registration of one backend.