UNPKG

storybook

Version:

Storybook: Develop, document, and test UI components in isolation

1,375 lines (1,352 loc) 443 kB
import { loadAllPresets } from 'storybook/internal/common'; export { getPreviewBodyTemplate, getPreviewHeadTemplate } from 'storybook/internal/common'; import * as storybook_internal_types from 'storybook/internal/types'; import { CLIOptions, LoadOptions, BuilderOptions, StorybookConfigRaw, ModuleResolveConfig, NormalizedProjectAnnotations, ProjectAnnotations, ComposedStoryFn, IndexInputStats, NormalizedStoriesSpecifier, Path as Path$1, Indexer, DocsOptions, StoryIndexEntry, DocsIndexEntry, IndexEntry, StoryIndex, Options, StatusStoreByTypeId as StatusStoreByTypeId$1 } from 'storybook/internal/types'; export { ModuleResolveConfig } from 'storybook/internal/types'; import { EventType } from 'storybook/internal/telemetry'; import { CreateNewStoryRequestPayload } from 'storybook/internal/core-events'; import { StoryId, StrictArgTypes as StrictArgTypes$1, SBType as SBType$1 } from 'storybook/internal/csf'; import { Channel } from 'storybook/internal/channels'; import { ImportEdge } from 'storybook/internal/oxc-parser'; export { ImportEdge } from 'storybook/internal/oxc-parser'; import { watch } from 'node:fs'; import { readFile, stat } from 'node:fs/promises'; type BuildStaticStandaloneOptions = CLIOptions & LoadOptions & BuilderOptions & { outputDir: string; }; declare function buildStaticStandalone(options: BuildStaticStandaloneOptions): Promise<void>; /** * Resolves the initialPath for the browser open URL. * CLI-provided initialPath always wins. If not set and not running in an agent context, * checks the project cache for an `onboarding-pending` entry written by `storybook init`. * If found, returns '/onboarding' and removes the cache entry so it only triggers once. * The cache entry is only written by init when onboarding is known to be supported, * so no further addon check is needed here. */ declare function resolveOnboardingInitialPath(cliInitialPath: string | undefined): Promise<string | undefined>; declare function buildDevStandalone(options: CLIOptions & LoadOptions & BuilderOptions & { storybookVersion?: string; previewConfigPath?: string; }): Promise<{ port: number; address: string; networkAddress: string; }>; type BuildIndexOptions = CLIOptions & LoadOptions & BuilderOptions; declare const buildIndex: (options: BuildIndexOptions) => Promise<storybook_internal_types.StoryIndex>; declare const buildIndexStandalone: (options: BuildIndexOptions & { outputFile: string; }) => Promise<void>; declare abstract class StorybookError extends Error { private _name; /** Category of the error. Used to classify the type of error, e.g., 'PREVIEW_API'. */ readonly category: string; /** Code representing the error. Used to uniquely identify the error, e.g., 1. */ readonly code: number; /** * Data associated with the error. Used to provide additional information in the error message or * to be passed to telemetry. */ readonly data: {}; /** * Specifies the documentation for the error. * * - If `true`, links to a documentation page on the Storybook website (make sure it exists before * enabling) – This is not implemented yet. * - If a string, uses the provided URL for documentation (external or FAQ links). * - If `false` (default), no documentation link is added. */ readonly documentation: boolean | string | string[]; /** Flag used to easily determine if the error originates from Storybook. */ readonly fromStorybook: true; /** * Flag used to determine if the error is handled by us and should therefore not be shown to the * user. */ isHandledError: boolean; get fullErrorCode(): `SB_${string}_${string}`; /** Overrides the default `Error.name` property in the format: SB_<CATEGORY>_<CODE>. */ get name(): string; set name(name: string); /** * A collection of sub errors which relate to a parent error. * * Sub-errors are used to represent multiple related errors that occurred together. When a * StorybookError with sub-errors is sent to telemetry, both the parent error and each sub-error * are sent as separate telemetry events. This allows for better error tracking and debugging. * * @example * * ```ts * const error1 = new SomeError(); * const error2 = new AnotherError(); * const parentError = new ParentError({ * // ... other props * subErrors: [error1, error2], * }); * ``` */ subErrors: StorybookError[]; constructor(props: { category: string; code: number; message: string; cause?: unknown; documentation?: boolean | string | string[]; isHandledError?: boolean; name: string; /** * Optional array of sub-errors that are related to this error. When this error is sent to * telemetry, each sub-error will be sent as a separate event. */ subErrors?: StorybookError[]; }); /** Generates the error message along with additional documentation link (if applicable). */ static getFullMessage({ documentation, code, category, message, }: ConstructorParameters<typeof StorybookError>[0]): string; } type TelemetryOptions = { cliOptions: CLIOptions; presetOptions?: Parameters<typeof loadAllPresets>[0]; printError?: (err: any) => void; skipPrompt?: boolean; eventType?: EventType; fallbackTelemetryState?: boolean; }; type ErrorLevel = 'none' | 'error' | 'full'; declare function getErrorLevel({ cliOptions, presetOptions, skipPrompt, eventType, }: TelemetryOptions): Promise<ErrorLevel>; declare function sendTelemetryError(_error: unknown, eventType: EventType, options: TelemetryOptions, blocking?: boolean, parent?: StorybookError): Promise<void>; declare function withTelemetry<T>(eventType: EventType, options: TelemetryOptions, run: () => Promise<T>): Promise<T | undefined>; declare function build(options?: any, frameworkOptions?: any): Promise<void | { port: number; address: string; networkAddress: string; }>; declare const mapStaticDir: (staticDir: NonNullable<StorybookConfigRaw["staticDirs"]>[number], configDir: string) => { staticDir: string; staticPath: string; targetDir: string; targetEndpoint: string; }; /** * A function that json from a file */ interface ReadJsonSync { (packageJsonPath: string): any | undefined; } /** * Function that can match a path */ interface MatchPath { (requestedModule: string, readJson?: ReadJsonSync, fileExists?: (name: string) => boolean, extensions?: ReadonlyArray<string>): string | undefined; } /** * Actions represent the type of change to a location value. */ declare enum Action { /** * A POP indicates a change to an arbitrary index in the history stack, such * as a back or forward navigation. It does not describe the direction of the * navigation, only that the current index changed. * * Note: This is the default action for newly created history objects. */ Pop = "POP", /** * A PUSH indicates a new entry being added to the history stack, such as when * a link is clicked and a new page loads. When this happens, all subsequent * entries in the stack are lost. */ Push = "PUSH", /** * A REPLACE indicates the entry at the current index in the history stack * being replaced by a new one. */ Replace = "REPLACE" } /** * The pathname, search, and hash values of a URL. */ interface Path { /** * A URL pathname, beginning with a /. */ pathname: string; /** * A URL search string, beginning with a ?. */ search: string; /** * A URL fragment identifier, beginning with a #. */ hash: string; } /** * An entry in a history stack. A location contains information about the * URL path, as well as possibly some arbitrary state and a key. */ interface Location extends Path { /** * A value of arbitrary data associated with this location. */ state: any; /** * A unique string associated with this location. May be used to safely store * and retrieve data in some other storage API, like `localStorage`. * * Note: This value is always "default" on the initial location. */ key: string; } /** * Map of routeId -> data returned from a loader/action/error */ interface RouteData { [routeId: string]: any; } declare enum ResultType { data = "data", deferred = "deferred", redirect = "redirect", error = "error" } /** * Successful result from a loader or action */ interface SuccessResult { type: ResultType.data; data: any; statusCode?: number; headers?: Headers; } /** * Successful defer() result from a loader or action */ interface DeferredResult { type: ResultType.deferred; deferredData: DeferredData; statusCode?: number; headers?: Headers; } /** * Redirect result from a loader or action */ interface RedirectResult { type: ResultType.redirect; status: number; location: string; revalidate: boolean; reloadDocument?: boolean; } /** * Unsuccessful result from a loader or action */ interface ErrorResult { type: ResultType.error; error: any; headers?: Headers; } /** * Result from a loader or action - potentially successful or unsuccessful */ type DataResult = SuccessResult | DeferredResult | RedirectResult | ErrorResult; type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete"; type UpperCaseFormMethod = Uppercase<LowerCaseFormMethod>; /** * Active navigation/fetcher form methods are exposed in lowercase on the * RouterState */ type FormMethod = LowerCaseFormMethod; /** * In v7, active navigation/fetcher form methods are exposed in uppercase on the * RouterState. This is to align with the normalization done via fetch(). */ type V7_FormMethod = UpperCaseFormMethod; type FormEncType = "application/x-www-form-urlencoded" | "multipart/form-data" | "application/json" | "text/plain"; type JsonObject = { [Key in string]: JsonValue; } & { [Key in string]?: JsonValue | undefined; }; type JsonArray = JsonValue[] | readonly JsonValue[]; type JsonPrimitive = string | number | boolean | null; type JsonValue = JsonPrimitive | JsonObject | JsonArray; /** * @private * Internal interface to pass around for action submissions, not intended for * external consumption */ type Submission = { formMethod: FormMethod | V7_FormMethod; formAction: string; formEncType: FormEncType; formData: FormData; json: undefined; text: undefined; } | { formMethod: FormMethod | V7_FormMethod; formAction: string; formEncType: FormEncType; formData: undefined; json: JsonValue; text: undefined; } | { formMethod: FormMethod | V7_FormMethod; formAction: string; formEncType: FormEncType; formData: undefined; json: undefined; text: string; }; /** * @private * Arguments passed to route loader/action functions. Same for now but we keep * this as a private implementation detail in case they diverge in the future. */ interface DataFunctionArgs { request: Request; params: Params; context?: any; } /** * Arguments passed to loader functions */ interface LoaderFunctionArgs extends DataFunctionArgs { } /** * Arguments passed to action functions */ interface ActionFunctionArgs extends DataFunctionArgs { } /** * Loaders and actions can return anything except `undefined` (`null` is a * valid return value if there is no data to return). Responses are preferred * and will ease any future migration to Remix */ type DataFunctionValue = Response | NonNullable<unknown> | null; /** * Route loader function signature */ interface LoaderFunction { (args: LoaderFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue; } /** * Route action function signature */ interface ActionFunction { (args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue; } /** * Route shouldRevalidate function signature. This runs after any submission * (navigation or fetcher), so we flatten the navigation/fetcher submission * onto the arguments. It shouldn't matter whether it came from a navigation * or a fetcher, what really matters is the URLs and the formData since loaders * have to re-run based on the data models that were potentially mutated. */ interface ShouldRevalidateFunction { (args: { currentUrl: URL; currentParams: AgnosticDataRouteMatch["params"]; nextUrl: URL; nextParams: AgnosticDataRouteMatch["params"]; formMethod?: Submission["formMethod"]; formAction?: Submission["formAction"]; formEncType?: Submission["formEncType"]; text?: Submission["text"]; formData?: Submission["formData"]; json?: Submission["json"]; actionResult?: DataResult; defaultShouldRevalidate: boolean; }): boolean; } /** * Keys we cannot change from within a lazy() function. We spread all other keys * onto the route. Either they're meaningful to the router, or they'll get * ignored. */ type ImmutableRouteKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children"; type RequireOne<T, Key = keyof T> = Exclude<{ [K in keyof T]: K extends Key ? Omit<T, K> & Required<Pick<T, K>> : never; }[keyof T], undefined>; /** * lazy() function to load a route definition, which can add non-matching * related properties to a route */ interface LazyRouteFunction<R extends AgnosticRouteObject> { (): Promise<RequireOne<Omit<R, ImmutableRouteKey>>>; } /** * Base RouteObject with common props shared by all types of routes */ type AgnosticBaseRouteObject = { caseSensitive?: boolean; path?: string; id?: string; loader?: LoaderFunction; action?: ActionFunction; hasErrorBoundary?: boolean; shouldRevalidate?: ShouldRevalidateFunction; handle?: any; lazy?: LazyRouteFunction<AgnosticBaseRouteObject>; }; /** * Index routes must not have children */ type AgnosticIndexRouteObject = AgnosticBaseRouteObject & { children?: undefined; index: true; }; /** * Non-index routes may have children, but cannot have index */ type AgnosticNonIndexRouteObject = AgnosticBaseRouteObject & { children?: AgnosticRouteObject[]; index?: false; }; /** * A route object represents a logical route, with (optionally) its child * routes organized in a tree-like structure. */ type AgnosticRouteObject = AgnosticIndexRouteObject | AgnosticNonIndexRouteObject; type AgnosticDataIndexRouteObject = AgnosticIndexRouteObject & { id: string; }; type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObject & { children?: AgnosticDataRouteObject[]; id: string; }; /** * A data route object, which is just a RouteObject with a required unique ID */ type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject; /** * The parameters that were parsed from the URL path. */ type Params<Key extends string = string> = { readonly [key in Key]: string | undefined; }; /** * A RouteMatch contains info about how a route matched a URL. */ interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject> { /** * The names and values of dynamic parameters in the URL. */ params: Params<ParamKey>; /** * The portion of the URL pathname that was matched. */ pathname: string; /** * The portion of the URL pathname that was matched before child routes. */ pathnameBase: string; /** * The route object that was used to match. */ route: RouteObjectType; } interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> { } declare class DeferredData { private pendingKeysSet; private controller; private abortPromise; private unlistenAbortSignal; private subscribers; data: Record<string, unknown>; init?: ResponseInit; deferredKeys: string[]; constructor(data: Record<string, unknown>, responseInit?: ResponseInit); private trackPromise; private onSettle; private emit; subscribe(fn: (aborted: boolean, settledKey?: string) => void): () => boolean; cancel(): void; resolveData(signal: AbortSignal): Promise<boolean>; get done(): boolean; get unwrappedData(): {}; get pendingKeys(): string[]; } /** * State maintained internally by the router. During a navigation, all states * reflect the the "old" location unless otherwise noted. */ interface RouterState { /** * The action of the most recent navigation */ historyAction: Action; /** * The current location reflected by the router */ location: Location; /** * The current set of route matches */ matches: AgnosticDataRouteMatch[]; /** * Tracks whether we've completed our initial data load */ initialized: boolean; /** * Current scroll position we should start at for a new view * - number -> scroll position to restore to * - false -> do not restore scroll at all (used during submissions) * - null -> don't have a saved position, scroll to hash or top of page */ restoreScrollPosition: number | false | null; /** * Indicate whether this navigation should skip resetting the scroll position * if we are unable to restore the scroll position */ preventScrollReset: boolean; /** * Tracks the state of the current navigation */ navigation: Navigation; /** * Tracks any in-progress revalidations */ revalidation: RevalidationState; /** * Data from the loaders for the current matches */ loaderData: RouteData; /** * Data from the action for the current matches */ actionData: RouteData | null; /** * Errors caught from loaders for the current matches */ errors: RouteData | null; /** * Map of current fetchers */ fetchers: Map<string, Fetcher>; /** * Map of current blockers */ blockers: Map<string, Blocker>; } /** * Data that can be passed into hydrate a Router from SSR */ type HydrationState = Partial<Pick<RouterState, "loaderData" | "actionData" | "errors">>; /** * Potential states for state.navigation */ type NavigationStates = { Idle: { state: "idle"; location: undefined; formMethod: undefined; formAction: undefined; formEncType: undefined; formData: undefined; json: undefined; text: undefined; }; Loading: { state: "loading"; location: Location; formMethod: Submission["formMethod"] | undefined; formAction: Submission["formAction"] | undefined; formEncType: Submission["formEncType"] | undefined; formData: Submission["formData"] | undefined; json: Submission["json"] | undefined; text: Submission["text"] | undefined; }; Submitting: { state: "submitting"; location: Location; formMethod: Submission["formMethod"]; formAction: Submission["formAction"]; formEncType: Submission["formEncType"]; formData: Submission["formData"]; json: Submission["json"]; text: Submission["text"]; }; }; type Navigation = NavigationStates[keyof NavigationStates]; type RevalidationState = "idle" | "loading"; /** * Potential states for fetchers */ type FetcherStates<TData = any> = { Idle: { state: "idle"; formMethod: undefined; formAction: undefined; formEncType: undefined; text: undefined; formData: undefined; json: undefined; data: TData | undefined; " _hasFetcherDoneAnything "?: boolean; }; Loading: { state: "loading"; formMethod: Submission["formMethod"] | undefined; formAction: Submission["formAction"] | undefined; formEncType: Submission["formEncType"] | undefined; text: Submission["text"] | undefined; formData: Submission["formData"] | undefined; json: Submission["json"] | undefined; data: TData | undefined; " _hasFetcherDoneAnything "?: boolean; }; Submitting: { state: "submitting"; formMethod: Submission["formMethod"]; formAction: Submission["formAction"]; formEncType: Submission["formEncType"]; text: Submission["text"]; formData: Submission["formData"]; json: Submission["json"]; data: TData | undefined; " _hasFetcherDoneAnything "?: boolean; }; }; type Fetcher<TData = any> = FetcherStates<TData>[keyof FetcherStates<TData>]; interface BlockerBlocked { state: "blocked"; reset(): void; proceed(): void; location: Location; } interface BlockerUnblocked { state: "unblocked"; reset: undefined; proceed: undefined; location: undefined; } interface BlockerProceeding { state: "proceeding"; reset: undefined; proceed: undefined; location: Location; } type Blocker = BlockerUnblocked | BlockerBlocked | BlockerProceeding; /** * NOTE: If you refactor this to split up the modules into separate files, * you'll need to update the rollup config for react-router-dom-v5-compat. */ declare global { var __staticRouterHydrationData: HydrationState | undefined; } type EnvironmentType = (typeof UniversalStore.Environment)[keyof typeof UniversalStore.Environment]; type StatusType = (typeof UniversalStore.Status)[keyof typeof UniversalStore.Status]; type StateUpdater<TState> = (prevState: TState) => TState; type Actor = { id: string; type: (typeof UniversalStore.ActorType)[keyof typeof UniversalStore.ActorType]; environment: EnvironmentType; }; type EventInfo = { actor: Actor; forwardingActor?: Actor; }; type Listener<TEvent> = (event: TEvent, eventInfo: EventInfo) => void; type BaseEvent = { type: string; payload?: any; }; interface SetStateEvent<TState> extends BaseEvent { type: typeof UniversalStore.InternalEventType.SET_STATE; payload: { state: TState; previousState: TState; }; } interface ExistingStateRequestEvent extends BaseEvent { type: typeof UniversalStore.InternalEventType.EXISTING_STATE_REQUEST; payload: never; } interface ExistingStateResponseEvent<TState> extends BaseEvent { type: typeof UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE; payload: TState; } interface LeaderCreatedEvent extends BaseEvent { type: typeof UniversalStore.InternalEventType.LEADER_CREATED; payload: never; } interface FollowerCreatedEvent extends BaseEvent { type: typeof UniversalStore.InternalEventType.FOLLOWER_CREATED; payload: never; } type InternalEvent<TState> = SetStateEvent<TState> | ExistingStateRequestEvent | ExistingStateResponseEvent<TState> | FollowerCreatedEvent | LeaderCreatedEvent; type Event<TState, TEvent extends BaseEvent> = TEvent | InternalEvent<TState>; type ChannelLike = Pick<Channel, 'on' | 'off' | 'emit'>; type StoreOptions<TState> = { id: string; leader?: boolean; initialState?: TState; debug?: boolean; }; type EnvironmentOverrides = { channel: ChannelLike; environment: EnvironmentType; }; /** * A universal store implementation that synchronizes state across different environments using a * channel-based communication. * * The store follows a leader-follower pattern where: * * - Leader: The main store instance that owns and manages the state * - Follower: Store instances that mirror the leader's state * * Features: * * - State synchronization across environments * - Event-based communication * - Type-safe state and custom events * - Subscription system for state changes and custom events * * @remarks * - The store must be created using the static `create()` method, not the constructor * - Follower stores will automatically sync with their leader's state. If they have initial state, it * will be replaced immediately when it has synced with the leader. * * @example * * ```typescript * interface MyState { * count: number; * } * interface MyCustomEvent { * type: 'INCREMENT'; * payload: number; * } * * // Create a leader store * const leaderStore = UniversalStore.create<MyState, MyCustomEvent>({ * id: 'my-store', * leader: true, * initialState: { count: 0 }, * }); * * // Create a follower store * const followerStore = UniversalStore.create<MyState, MyCustomEvent>({ * id: 'my-store', * leader: false, * }); * ``` * * @template State - The type of state managed by the store * @template CustomEvent - Custom events that can be sent through the store. Must have a `type` * string and optional `payload` * @throws {Error} If constructed directly instead of using `create()` * @throws {Error} If created without setting a channel first * @throws {Error} If a follower is created with initial state * @throws {Error} If a follower cannot find its leader within 1 second */ declare class UniversalStore<State, CustomEvent extends { type: string; payload?: any; } = { type: string; payload?: any; }> { /** * Defines the possible actor types in the store system * * @readonly */ static readonly ActorType: { readonly LEADER: "LEADER"; readonly FOLLOWER: "FOLLOWER"; }; /** * Defines the possible environments the store can run in * * @readonly */ static readonly Environment: { readonly SERVER: "SERVER"; readonly MANAGER: "MANAGER"; readonly PREVIEW: "PREVIEW"; readonly UNKNOWN: "UNKNOWN"; readonly MOCK: "MOCK"; }; /** * Internal event types used for store synchronization * * @readonly */ static readonly InternalEventType: { readonly EXISTING_STATE_REQUEST: "__EXISTING_STATE_REQUEST"; readonly EXISTING_STATE_RESPONSE: "__EXISTING_STATE_RESPONSE"; readonly SET_STATE: "__SET_STATE"; readonly LEADER_CREATED: "__LEADER_CREATED"; readonly FOLLOWER_CREATED: "__FOLLOWER_CREATED"; }; static readonly Status: { readonly UNPREPARED: "UNPREPARED"; readonly SYNCING: "SYNCING"; readonly READY: "READY"; readonly ERROR: "ERROR"; }; protected static isInternalConstructing: boolean; /** * The preparation construct is used to keep track of all store's preparation state the promise is * resolved when the store is prepared with the static __prepare() method which will also change * the state from PENDING to RESOLVED */ private static preparation; private static setupPreparationPromise; /** Enable debug logs for this store */ debugging: boolean; /** The actor object representing the store instance with a unique ID and a type */ get actor(): Actor; /** * The current state of the store, that signals both if the store is prepared by Storybook and * also - in the case of a follower - if the state has been synced with the leader's state. */ get status(): StatusType; /** * A promise that resolves when the store is fully ready. A leader will be ready when the store * has been prepared by Storybook, which is almost instantly. * * A follower will be ready when the state has been synced with the leader's state, within a few * hundred milliseconds. */ untilReady(): Promise<[void | { channel: ChannelLike; environment: EnvironmentType; }, void | undefined]>; /** * The syncing construct is used to keep track of if the instance's state has been synced with the * other instances. A leader will immediately have the promise resolved. A follower will initially * be in a PENDING state, and resolve when the leader has sent the existing state, or reject if no * leader has responded before the timeout. */ private syncing?; private channelEventName; private state; private channel?; private environment?; private listeners; private id; private actorId; private actorType; protected constructor(options: StoreOptions<State>, environmentOverrides?: EnvironmentOverrides); /** Creates a new instance of UniversalStore */ static create<State = any, CustomEvent extends { type: string; payload?: any; } = { type: string; payload?: any; }>(options: StoreOptions<State>): UniversalStore<State, CustomEvent>; /** Gets the current state */ getState: () => State; /** * Updates the store's state * * Either a new state or a state updater function can be passed to the method. */ setState(updater: State | StateUpdater<State>): void; /** * Subscribes to store events * * @returns A function to unsubscribe */ subscribe: { (listener: Listener<Event<State, CustomEvent>>): () => void; <EventType extends Event<State, CustomEvent>['type']>(eventType: EventType, listener: Listener<Extract<Event<State, CustomEvent>, { type: EventType; }>>): () => void; }; /** * Subscribes to state changes * * @returns Unsubscribe function */ onStateChange(listener: (state: State, previousState: State, eventInfo: EventInfo) => void): () => void; /** Sends a custom event to the other stores */ send: (event: CustomEvent) => void; private emitToChannel; private prepareThis; private emitToListeners; private handleChannelEvents; private debug; } /** * A mock universal store that can be used when testing code that relies on a universal store. It * functions exactly like a normal universal store, with a few exceptions: * * - It is fully isolated, meaning that it doesn't interact with any channel, and it is always a * leader. * * If the second testUtils argument is provided, all the public methods are spied on, so they can be * asserted. * * When a mock store is re-used across tests (eg. in stories), you manually need to reset the state * after each test. * * @example * * ```ts * import * as testUtils from 'storybook/test'; // in stories * import { vi as testUtils } from 'vitest'; // ... or in Vitest tests * * const initialState = { ... }; * const store = new MockUniversalStore({ initialState }, testUtils); * * export default { * title: 'My story', * beforeEach: () => { * return () => { * store.setState(initialState); * }; * } * } * ``` */ declare class MockUniversalStore<State, CustomEvent extends { type: string; payload?: any; } = { type: string; payload?: any; }> extends UniversalStore<State, CustomEvent> { private testUtils; constructor(options: StoreOptions<State>, testUtils?: any); /** Create a mock universal store. This is just an alias for the constructor */ static create<State = any, CustomEvent extends { type: string; payload?: any; } = { type: string; payload?: any; }>(options: StoreOptions<State>, testUtils?: any): MockUniversalStore<State, CustomEvent>; unsubscribeAll(): void; } type StatusValue = 'status-value:pending' | 'status-value:success' | 'status-value:new' | 'status-value:modified' | 'status-value:affected' | 'status-value:reviewing' | 'status-value:warning' | 'status-value:error' | 'status-value:unknown'; type StatusTypeId = string; type StatusByTypeId = Record<StatusTypeId, Status>; type StatusesByStoryIdAndTypeId = Record<StoryId, StatusByTypeId>; interface Status { value: StatusValue; typeId: StatusTypeId; storyId: StoryId; title: string; description: string; data?: any; sidebarContextMenu?: boolean; } declare const StatusStoreEventType: { readonly SELECT: "select"; }; type StatusStoreEvent = { type: typeof StatusStoreEventType.SELECT; payload: Status[]; }; type StatusStore = { getAll: () => StatusesByStoryIdAndTypeId; set: (statuses: Status[]) => void; onAllStatusChange: (listener: (statuses: StatusesByStoryIdAndTypeId, previousStatuses: StatusesByStoryIdAndTypeId) => void) => () => void; onSelect: (listener: (selectedStatuses: Status[]) => void) => () => void; unset: (storyIds?: StoryId[]) => void; }; type StatusStoreByTypeId = StatusStore & { typeId: StatusTypeId; }; type TestProviderState = 'test-provider-state:pending' | 'test-provider-state:running' | 'test-provider-state:succeeded' | 'test-provider-state:crashed'; type TestProviderId = string; type TestProviderStateByProviderId = Record<TestProviderId, TestProviderState>; type TestProviderStoreEventType = 'run-all' | 'clear-all' | 'settings-changed'; type TestProviderStoreEvent = BaseEvent & { type: TestProviderStoreEventType; }; type BaseTestProviderStore = { /** * Notifies all listeners that settings have changed for test providers. The Storybook UI will * highlight the test providers to tell the user that settings has changed. */ settingsChanged: () => void; /** * Subscribe to clicks on the "Run All" button, that is supposed to trigger all test providers to * run. Your test provider should do the "main thing" when this happens, similar to when the user * triggers your test provider specifically. * * @example * * ```typescript * // Subscribe to run-all events * const unsubscribe = myTestProviderStore.onRunAll(() => { * await runAllMyTests(); * }); * ``` */ onRunAll: (listener: () => void) => () => void; /** * Subscribe to clicks on the "Clear All" button, that is supposed to clear all state from test * providers. Storybook already clears all statuses, but if your test provider has more * non-status-based state, you can use this to clear that here. * * @remarks * The purpose of this is _not_ to clear your test provider's settings, only the test results. * @example * * ```typescript * // Subscribe to clear-all events * const unsubscribe = myTestProviderStore.onClearAll(() => { * clearMyTestResults(); * }); * * // Later, when no longer needed * unsubscribe(); * ``` */ onClearAll: (listener: () => void) => () => void; }; /** * Represents a store for a specific test provider, identified by its unique ID. This store provides * methods to manage the state of an individual test provider, including getting and setting its * state, running operations with automatic state management, and accessing its unique identifier. * * Each test provider has its own instance of this store, allowing for independent state management * across different test providers in the application. * * @example * * ```typescript * // Get a store for a specific test provider * const grammarStore = getTestProviderStoreById('addon-grammar'); * * // Check the current state * if (grammarStore.getState() === 'test-provider-state:pending') { * console.log('Grammar tests are ready to run'); * } * * // Run tests with automatic state management * grammarStore.runWithState(async () => { * await runGrammarTests(); * }); * ``` * * @see {@link TestProviderState} for possible state values * @see {@link BaseTestProviderStore} for methods inherited from the base store */ type TestProviderStoreById = BaseTestProviderStore & { /** * Gets the current state of this specific test provider * * The state represents the current execution status of the test provider, which can be one of the * following: * * - 'test-provider-state:pending': Tests have not been run yet * - 'test-provider-state:running': Tests are currently running * - 'test-provider-state:succeeded': Tests completed successfully * - 'test-provider-state:crashed': Running tests failed or encountered an error * * Storybook UI will use this state to determine what to show in the UI. * * @remarks * The 'test-provider-state:crashed' is meant to signify that the test run as a whole failed to * execute for some reason. It should _not_ be set just because a number of tests failed, use * statuses and the status store for that. See {@link TestStatusStore} for managing individual test * statuses. * @example * * ```typescript * // Get the current state of a specific test provider * const state = testProviderStore.getState(); * * // Conditionally render UI based on the state * const TestStatus = () => { * const state = testProviderStore.getState(); * * if (state === 'test-provider-state:running') { * return <Spinner />; * } else if (state === 'test-provider-state:succeeded') { * return <SuccessIcon />; * } else if (state === 'test-provider-state:crashed') { * return <ErrorIcon />; * } * * return <PendingIcon />; * }; * ``` */ getState: () => TestProviderState; /** * Sets the state of this specific test provider * * This method allows you to manually update the execution state of the test provider. It's * typically used when you need to reflect the current status of test execution in the UI or when * you want to programmatically control the test provider's state. * * Common use cases include: * * - Setting to 'running' when tests start * - Setting to 'succeeded' when tests complete successfully * - Setting to 'crashed' when tests fail or encounter errors * - Setting to 'pending' to reset the state * * The state represents the current execution status of the test provider, which can be one of the * following: * * - 'test-provider-state:pending': Tests have not been run yet * - 'test-provider-state:running': Tests are currently running * - 'test-provider-state:succeeded': Tests completed successfully * - 'test-provider-state:crashed': Running tests failed or encountered an error * * Storybook UI will use this state to determine what to show in the UI. * * @remarks * The 'test-provider-state:crashed' is meant to signify that the test run as a whole failed to * execute for some reason. It should _not_ be set just because a number of tests failed, use * statuses and the status store for that. See {@link TestStatusStore} for managing individual test * statuses. * * For most use cases, consider using {@link runWithState} instead, which provides automatic state * management and error handling during test execution. * @example * * ```typescript * // Update the state when tests start running * const startTests = async () => { * testProviderStore.setState('test-provider-state:running'); * ... run tests ... * }; * ``` */ setState: (state: TestProviderState) => void; /** * Runs a callback and automatically updates the test provider's state with running, succeeded or * crashed, depending on the end result. * * - Immediately changes the state to 'running' * - If the callback returns/resolves, change the state to 'succeeded'. * - If the callback throws an error/rejects, change the state to 'crashed'. * * This approach helps prevent state inconsistencies that might occur if exceptions are thrown * during test execution. * * @example * * ```typescript * // Run tests with automatic state management * const runTests = () => { * testProviderStore.runWithState(async () => { * // The state is automatically set to 'running' before this callback * * // Run tests here... * const results = await executeTests(); * }); * }; * ``` */ runWithState: (callback: () => void | Promise<void>) => Promise<void>; /** The unique identifier for this test provider */ testProviderId: TestProviderId; }; /** Arguments handed to an {@link ImportParser} when the registry dispatches a file to it. */ interface ParseFileArgs { filePath: string; source: string; } /** * Services passed to every parser. SFC parsers (vue, svelte) extract a `<script>` block * and then delegate the actual import-edge extraction to the built-in oxc wrapper via * {@link ImportParserContext.parseScriptWithOxc}. */ interface ImportParserContext { /** Core's oxc-parser wrapper. SFC plugins call this after extracting <script> content. */ parseScriptWithOxc(source: string, virtualFilePath: string): Promise<ImportEdge[]>; } /** * A parser plugin that claims one or more file extensions and knows how to extract import * edges from that file type. May be registered with a {@link ParserRegistry} directly or * surfaced through the `experimental_importParsers` preset key. * * Extensions are compared with `path.extname(filePath).toLowerCase()` lookup — compound * extensions such as `.svelte.ts` are NOT supported here (only the last segment matches). */ interface ImportParser { /** * Lowercase, leading dot. Compound extensions like `.svelte.ts` are NOT supported here — * `path.extname` returns the last segment only, so `.svelte.ts` matches `.ts`. */ extensions: readonly string[]; parse(args: ParseFileArgs, ctx: ImportParserContext): Promise<ImportEdge[]>; } /** JSON-serializable reverse index shape stored in open-service state. */ type StoriesByFileRecord = Record<string, Record<string, number>>; type ErrorLike = { message: string; name?: string; stack?: string; cause?: ErrorLike; }; type ModuleGraphStatus = { value: 'booting'; } | { value: 'ready'; } | { value: 'error'; error: ErrorLike; } | { value: 'unavailable'; reason: string; error?: ErrorLike; }; type ModuleGraphServiceState = { /** Project root used to normalize absolute file paths in query inputs. */ workingDir: string; status: ModuleGraphStatus; graphRevision: number; storiesByFile: StoriesByFileRecord; /** * Per-story revision stamps keyed by story-index-style relative path. Each entry holds the * {@link graphRevision} at which that story's subgraph last changed. Seeded to `0` for every * story at snapshot time so scoped `graphRevision` reads observe existing keys. */ storyChangeRevisions: Record<string, number>; latestChangedStoryFiles: string[]; }; /** The Standard Typed interface. This is a base type extended by other specs. */ interface StandardTypedV1<Input = unknown, Output = Input> { /** The Standard properties. */ readonly "~standard": StandardTypedV1.Props<Input, Output>; } declare namespace StandardTypedV1 { /** The Standard Typed properties interface. */ interface Props<Input = unknown, Output = Input> { /** The version number of the standard. */ readonly version: 1; /** The vendor name of the schema library. */ readonly vendor: string; /** Inferred types associated with the schema. */ readonly types?: Types<Input, Output> | undefined; } /** The Standard Typed types interface. */ interface Types<Input = unknown, Output = Input> { /** The input type of the schema. */ readonly input: Input; /** The output type of the schema. */ readonly output: Output; } /** Infers the input type of a Standard Typed. */ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"]; /** Infers the output type of a Standard Typed. */ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"]; } /** The Standard Schema interface. */ interface StandardSchemaV1<Input = unknown, Output = Input> { /** The Standard Schema properties. */ readonly "~standard": StandardSchemaV1.Props<Input, Output>; } declare namespace StandardSchemaV1 { /** The Standard Schema properties interface. */ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> { /** Validates unknown input values. */ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>; } /** The result interface of the validate function. */ type Result<Output> = SuccessResult<Output> | FailureResult; /** The result interface if validation succeeds. */ interface SuccessResult<Output> { /** The typed output value. */ readonly value: Output; /** A falsy value for `issues` indicates success. */ readonly issues?: undefined; } interface Options { /** Explicit support for additional vendor-specific parameters, if needed. */ readonly libraryOptions?: Record<string, unknown> | undefined; } /** The result interface if validation fails. */ interface FailureResult { /** The issues of failed validation. */ readonly issues: ReadonlyArray<Issue>; } /** The issue interface of the failure output. */ interface Issue { /** The error message of the issue. */ readonly message: string; /** The path of the issue, if any. */ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined; } /** The path segment interface of the issue. */ interface PathSegment { /** The key representing a path segment. */ readonly key: PropertyKey; } /** The Standard types interface. */ interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> { } /** Infers the input type of a Standard. */ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>; /** Infers the output type of a Standard. */ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>; } /** Generic Standard Schema constraint used across open-service definitions. */ type AnySchema = StandardSchemaV1<unknown, unknown>; /** Stable alias for service identifiers across definition, runtime, and registration APIs. */ type ServiceId = string; /** * Constrains a service's state to a plain object — the only shape the architecture supports. * * This is not an arbitrary restriction; two layers require it: * * 1. State is wrapped in a `deepSignal` proxy for fine-grained per-field reactivity, and `deepSignal` * throws ("this object can't be observed") on primitives, `null`, and `undefined` — there are no * fields to track on a scalar. * 2. Cross-peer sync (`applyStatePatch` in `service-sync.ts`) merges state by walking object keys; * it has no notion of replacing a whole scalar, so the wire protocol only carries keyed objects. * * Arrays are technically observable by `deepSignal` but are still rejected here: `applyStatePatch` * replaces arrays wholesale rather than merging by key, so a *top-level* array state would silently * fail to sync between peers. Wrap collections in a field instead (`{ items: [...] }`). * * Authoring helpers pair this with an `extends object` bound (which rejects primitives, `null`, and * `undefined` while still accepting both `interface` and `type` declarations). The naked `TState` in * the intersection keeps it transparent to inference; only an array collapses to the branded error. */ type ServiceState<TState> = TState & (TState extends readonly unknown[] ? { __openServiceStateError: 'Service state must be a plain object, not an array.'; } : unknown); /** Public schema shape exposed when describing a schema-backed service contract. */ type SchemaDescriptor = AnySchema; /** Raw caller-facing value type accepted by a schema-backed operation. */ type InferSchemaInput<TSchema extends AnySchema> = StandardSchemaV1.InferInput<TSchema>; /** Parsed value type produced by a schema after validation. */ type InferSchemaOutput<TSchema extends AnySchema> = StandardSchemaV1.InferOutput<TSchema>; /** * Named schema maps are the core inference surface for inline open-service authoring. * * `defineService()` infers one input-schema map and one output-schema map per operation family * (queries and commands). Keeping those maps separate gives TypeScript a place to correlate the * `input` and `output` properties of each inline object before it contextually types sibling * callbacks like `handler`, `load`, `staticPath`, and `staticInputs`. */ type OperationInputSchemas = Record<string, AnySchema>; /** * Output-schema maps must stay key-aligned with their input-schema map. * * The authoring helper uses this alias instead of a plain `Record<string, AnySchema>` so each * operation key retains its own input/output schema pair during inference. */ type MatchingOutputSchemas<TInputSchemas extends OperationInputSchemas> = { [TKey in keyof TInputSchemas]: AnySchema; }; /** * Internal utility used to keep handler maps assignable without collapsing everything to `unknown`. */ type BivariantCallback<TArgs extends unknown[], TResult> = { bivarianceHack(...args: TArgs): TResult; }['bivarianceHack']; /** Runtime shape shared by all command collections after they are built. */ type Command = Record<string, (input: unknown) => Promise<unknown>>; /** * Runtime command map derived directly from the inferred command schema maps. * * Queries only need command-call typing, not the full command definition objects, so this helper * keeps query contexts readable while still preserving exact input/output types per command. */ type CommandFunctions<TCommandInputSchemas extends OperationInputSchemas, TCommandOutputSchemas extends MatchingOu