core/import-boundaries
Use when checking which store modules components, services, and sagas may import, including dispatch access and forbidden saga/reducer internals.
SKILL.mdskills/core/import-boundaries/SKILL.md
GitHubImport Boundaries
Source: Setup — the package export surface;
package.jsonexports map; public@themislib/themis/svelte-store; hidden root-entry implementation context;@themislib/themis/docs/ARCHITECTURE.mdmaintainer validation.
Setup — the package export surface
The documented public surface is the approved subpackage interface plus explicit utility leaf subpaths. Do not teach consumers to import from the package root, from removed broad utility barrels, or from source-shaped deep paths.
The utility exports are explicit leaf entries, not wildcard domains. The package still does not export the root themis, @themislib/themis/utils, @themislib/themis/utils/runtime/*, selector implementation internals (utils/svelte-selectors/*, utils/streaming-selectors/*, or utils/selector-core/*), old themis/components/* paths, themis/src/*, or directory index barrels.
The public subpackages relevant to Store-family import boundaries include:
@themislib/themis/svelte-store— canonical Svelte-readableStoreclass. Per-store operations go through the configured Store instance (store.init,store.dispatch,store.state,store.createSelector,store.runSaga,store.dispose). Utility helpers are not exported here; use the explicit utility leaf subpaths below.@themislib/themis/streaming-store— directStreamingStoreleaf for Kefir/observable selectors. Do not import streaming selector internals directly.@themislib/themis/saga— saga authoring helpers:waitFor, selector-channel helpers, and debounce/retry/streaming helpers.@themislib/themis/types— public TypeScript-only types such asStoreState,PreloadedStoreState, action, middleware, selector, reducer, and saga map types.@themislib/themis/components-svelte/use-init-store,/use-run-saga— optional Svelte lifecycle helper leaves. Do not import from oldcomponents/*paths or from acomponents-sveltedirectory barrel.@themislib/themis/utils/collections/collection-utils— approved direct collection utility leaf.@themislib/themis/utils/store/create-action,/create-reducer,/boolean-preference,/domain-scoped— the only package-level store utility leaf imports.@themislib/themis/utils/sagas/debounce-saga,/retry-with-timeout,/wrap-async-generator,/selector-channel-effects— approved direct saga utility leaves. Safe localStorage helpers are example/app-local utilities, not package exports.
Migration note: replace flat package-root examples, removed utilities-subpackage imports, and source-shaped deep imports with one of the public subpackages or approved utility leaf subpaths above.
Core Patterns
Components and component-level modules — allowed
- ✅ Actions from
*-slice.ts - ✅ Selectors from
*-selectors.ts - ✅ Types from
*-types.ts - ✅ The app's existing initialized
Storeinstance for dispatch and one-time reads in event handlers (store.dispatch,store.state)
Components — forbidden
- ❌ Saga files (
sagas/*.ts) in ordinary components/handlers — dispatch the owning action instead. Only the explicit bootstrap/lifetime owner may import the saga it starts and stops. - ❌ Operation/utility modules that dispatch internally
- ❌ Reducer internals or store init/setup modules
- ❌ Collection utils directly (access collections through selectors)
- ❌
typed-redux-sagaorredux-saga— saga effect libraries have no meaning in a component; importing them at minimum bloats the bundle and often drags the saga middleware into client code
Services and non-component TS
Services may import actions, selectors, and the app's existing initialized Store instance captured outside callbacks. Ordinary service operations may not import or call sagas directly — call store.dispatch(triggeringAction()) instead. A service that is the explicit lifetime owner has only the startup exception below.
Bootstrap and lifetime-owner exception
The designated bootstrap, layout/component, or service lifetime owner may import the configured Store and the saga function it passes to store.runSaga(saga). Initialize the Store before starting the saga, retain the matching stop handle, and stop it at that same boundary. This is lifecycle wiring, not permission to invoke/step the generator, use saga effects in components, or move business logic into handlers. Do not add a second hidden owner for an already-owned saga.
For an owner that also owns Store initialization/disposal (adapt the boundary to the selected family):
import { store } from "$lib/store";
import { jobsSaga } from "$lib/store/slices/jobs/sagas/jobs-saga";
export function startJobsOwner() {
const disposeStore = store.init();
const stopJobs = store.runSaga(jobsSaga);
return () => {
stopJobs();
disposeStore();
};
}
If a longer-lived parent already owns the initialized Store, retain/call only the saga stop handle; do not initialize or dispose the parent's Store. Ordinary UI events still import actions and use store.dispatch(action()). Follow the chosen family's lifecycle leaf and Store saga lifecycle.
Sagas
Sagas may import anything within the store directory (actions, selectors, other sagas, reducer internals, collection utils, app-local safe localStorage helpers, typed-redux-saga effects). This is the only layer where yield* of typed-redux-saga effects is allowed.
Examples
1. Public package subpaths and utility leaf imports
Use the export list above, not a package-root or implementation import:
createAction comes from @themislib/themis/utils/store/create-action,
retryWithTimeout from @themislib/themis/saga, and StoreState from
@themislib/themis/types via import type. Import the concrete Store class
from the selected family's public subpackage.
2. Component imports actions, selectors, types, and the initialized Store instance
import { store } from "$lib/store";
import { selectItems, selectIsLoading } from "./slices/items/items-selectors";
import { removeItem } from "./slices/items/items-slice";
import type { Item } from "./slices/items/items-types";
export const items = selectItems();
export const loading = selectIsLoading();
export function handleDelete(item: Item) {
const currentItems = selectItems.select(store.state);
if (currentItems.length > 1) store.dispatch(removeItem(item.id));
}
3. Service/non-component TS uses one-shot reads and dispatch, not saga source
import { store } from "$lib/store";
import { selectQueuedJob } from "$lib/store/slices/jobs/jobs-selectors";
import { startJob } from "$lib/store/slices/jobs/jobs-slice";
export function startQueuedJob(jobId: string) {
const queuedJob = selectQueuedJob.select(store.state, jobId);
if (queuedJob) store.dispatch(startJob(jobId));
}
4. Saga layer may import saga effects, selectors, actions, and saga utilities
import { call, delay, put, takeLatest } from "typed-redux-saga";
import { loadJobs, refreshJobs } from "../jobs-slice";
import { selectQueuedJob } from "../jobs-selectors";
declare const api: { runJob(id: string): Promise<void>; refreshJobs(): Promise<void> };
export function* jobsSaga() {
yield* takeLatest(loadJobs, function* loadQueuedJob(action: { payload: [string] }) {
const job = yield* selectQueuedJob.effect(action.payload[0]);
if (job) yield* call(api.runJob, job.id);
yield* put(refreshJobs());
});
yield* takeLatest(refreshJobs, function* refreshJobsAfterIdle() {
yield* delay(250);
yield* call(api.refreshJobs);
});
}
Common Mistakes
❌ Importing saga files from components
Outside the explicit bootstrap/lifetime-owner exception, importing saga source
crosses the component boundary; invoking or stepping its generator bypasses
Store.runSaga lifecycle. Import the triggering action from its owning slice and
call store.dispatch(fetchItems()) instead. The startup exception does not permit
business-saga calls or effect imports in event handlers.
Source: Components — forbidden · Priority: HIGH
❌ Hiding dispatch inside service helpers
Components and services should dispatch through the configured app Store instance or receive a dispatch function explicitly. A helper that captures a hidden module-level dispatch creates an unreviewable second boundary even when all imports type-check.
Keep the Store or dispatch parameter explicit at the call site rather than hiding
ownership in an operation helper. This does not permit business timers or other
domain effects outside sagas; core/core-policy/SKILL.md owns that rule.
Source: Components and component-level modules — allowed, Services and non-component TS · Priority: MEDIUM
❌ Importing from typed-redux-saga in a component file
Effects (call, put, select, takeEvery, ...) only run inside saga generators.
Calling put(triggerEffect()) in a handler just creates an unexecuted descriptor;
call store.dispatch(triggerEffect()) instead. Effect imports also bloat the
client bundle and drag saga mocks (core/testing) into non-saga tests.
Source: Components — forbidden (No saga effects in components; saga-function imports only at the explicit lifetime owner) · Priority: HIGH
See also
core/core-policy/SKILL.md— the policy these boundaries enforce- Selected Store family skill — Store dispatch + selector usage inside components