Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources
  • Guide overview
  • Create a project
i.Core concepts
  • The mental model
  • Which primitive should I use?
  • Anatomy of a primitive
  • Generators and yield*
  • Insertions
  • Typed insertion pipes
  • Exceptions as values
ii.Managing state
  • Local state
  • State machines
  • query
  • Mutations
  • queryParams
  • asyncProcess
  • Selecting
  • Reacting to mutations
  • Collections
  • Collection utilities
  • Persistence
  • GlobalPersisterHandler
  • Pagination placeholders
  • Schema validation
iii.Recommended approaches
  • Inject at the point of use
iv.Structuring the app
  • craftService
  • Service scopes
  • Shaping the public API
  • Abstract services
  • App start
  • Lazy services
  • Server functions
  • craftRegisterFor
  • Target wrapper
v.Routing & type-safe DI
  • Setup
  • CLI automation
  • ESLint rules
  • Route providers
  • Route guards
  • Route exception handling
  • Non-blocking navigation
  • Global error component
  • Route load errors
  • Scaling routes
vi.Components & templates
  • Components
  • Activating the style system
  • Typed styles
  • Defining a design system
  • Tokens and variables
  • Global foundation and fonts
  • Pseudo-elements and animations
  • Axes and the matrix
  • Context obligations
  • Testing visual states
  • Text contrast
  • Visual attestation
  • Template obligations
  • Typed CSS variables
  • Fine-grained reactivity
  • Progressive rendering
  • Directives and .pipe(...)
  • settledValue & pendingNode
  • Accessibility
  • Customization
  • Content projection
  • Styling a component
  • Template migrator
vii.Forms
  • Overview
  • Validators
  • Submitting
  • Nested forms
  • Exception handling
  • Complete examples
viii.Testing
  • Testing services
  • Testing components
  • Type-level tests
  • Browser boundaries
  • Page stability
  • Architecture rules
    • Overview
    • Declarative baseline
    • Unique identities
    • HTTP endpoint ownership
    • Computed purity
    • Dependency cycles
    • Mutation reactions
    • Route DI proofs
    • Route component files
    • Path boundaries
    • Exclusive links
    • Persisted identities
    • insertSelect keys
    • craftEffect and network
    • craftEffect and imperative sync
    • Interactive element names
    • Metric thresholds
    • Server-state loaders
    • Primitive loader requirements
  • Extensible architecture graph
  • Graph insights
  • Craft graph vs Nx
ix.Reactivity utilities
  • craftComputed
  • craftEffect
  • craftMethod
  • source$
  • on$
  • fromEventToSource$
  • sourceFromEvent
  • afterRecomputation
x.Internationalisation
  • Type-safe i18n
  • The catalogue
  • Tokens
  • The runtime
  • With Effect
xi.Deployment (experimental)
  • Overview
  • Manifest reference
  • Diagnostics
  • Providers
  • Alchemy provider
xii.Going further
  • SSR and hydration
  • Effect integration
  • Program operators
  • Pattern matching
  • Observability
  • AI agents
  • Temporal runtime
  1. Structuring the app
  2. App start

App start#

onAppStart declares work that must run — and finish — before the application renders, owned by the service that needs it rather than by a global bootstrap file.

Use it when something must be true before the first paint: a loaded config, a restored session, a feature-flag fetch. Not when the work can happen after render — that is just an effect, and blocking on it costs your users a blank screen.

Import#

typescript
import { onAppStart } from '@craft-ts/core';

Overview#

onAppStart(...) is used inside a craftService(..., function* () {}) generator to declare logic that should run when the application starts.

Important constraints:

  • the owning service must be declared with appStart: true

  • a service can declare yield* onAppStart(...) only once

  • the callback can be a plain function or a generator function

  • nested onAppStart(...) calls inside the callback are not supported

craftAppConfig(...) runs registered app-start services during application initialization.

Signature#

typescript
function onAppStart(
  run: () => Observable<unknown> | Promise<unknown> | void,
): Generator<unknown, void, unknown>;

function onAppStart<Yielded>(
  run: () => Generator<
    Yielded,
    Observable<unknown> | Promise<unknown> | void,
    unknown
  >,
): Generator<unknown, void, unknown>;

Plain Callback#

Use a plain callback when startup logic does not need to yield* crafted dependencies.

ts
import { craftService, onAppStart } from '@craft-ts/core';

export const { StartupFlag } = craftService(
  {
    name: 'StartupFlag',
    providedIn: 'global',
    appStart: true,
  },
  function* () {
    yield* onAppStart(() => {
      console.log('app started');
      return Promise.resolve();
    });
  },
);

Generator Callback#

Use a generator callback when startup logic needs to yield* crafted dependencies.

typescript
import { Console, craftService, onAppStart } from '@craft-ts/core';

export const { AppStartLog } = craftService(
  {
    name: 'AppStartLog',
    providedIn: 'toProvide',
    appStart: true,
  },
  function* () {
    yield* onAppStart(function* () {
      yield* Console.log('This is a log from the appStart callback');
      return new Promise((resolve) => setTimeout(resolve, 1000));
    });
  },
);

The callback generator supports the same dependency-yield semantics as a normal crafted generator for:

  • yield* X(...)

  • yield* exposure tokens returned by derivation callbacks

  • browser boundaries such as yield* Console.log(...)

Dependencies used only inside this callback are merged into the parent service dependency graph.

Registering it with craftAppConfig#

Declaring onAppStart is only half of it — nothing runs until the service is registered. Two steps, and both are mechanical.

Augment the app-start registry so the service is known by name:

typescript
declare module '@craft-ts/core' {
  interface CraftAppStartRegistry {
    AppStartLog: typeof AppStartLog;
  }
}

Then list it in craftAppConfig:

typescript
export const appConfig = craftAppConfig({
  appStart: {
    AppStartLog,
  },
  providers: [
    /* … */
  ],
});

craftAppConfig runs every registered app-start service during the application's application initialization, and the app renders once they have settled.

Here it is end to end:

ts
import {
  Console,
  craftAppConfig,
  craftService,
  onAppStart,
} from '@craft-ts/core';

const { AppStartLog } = craftService(
  {
    name: 'AppStartLog',
    providedIn: 'global',
    appStart: true,
  },
  function* () {
    yield* onAppStart(function* () {
      yield* Console.log('startup log');
      return Promise.resolve();
    });
  },
);

declare module '@craft-ts/core' {
  interface CraftAppStartRegistry {
    AppStartLog: typeof AppStartLog;
  }
}

export const appConfig = craftAppConfig({
  appStart: { AppStartLog },
});

The registry augmentation is generated

The declare module block is written for you by the craft-ts ESLint plugin — you rarely type it by hand.

A declared hook that is never registered simply never runs

It is not an error: appStart: true and yield* onAppStart(...) describe the service, the appStart map in craftAppConfig is what activates it. If startup logic silently doesn't happen, check the map first.

Dependency Tracking#

Generator callbacks are type-visible.

If the callback only uses Console, the owning service dependency graph includes ConsoleService as a normal dependency node, with browserBoundary: true.

This means startup-only dependencies are still visible to:

  • GetServiceDependencies<typeof X>

  • route/app DI checks built on top of service metadata

  • test helpers that inspect crafted dependency graphs

Runtime Behavior#

onAppStart(...) does not run when the service instance is created.

It registers a startup hook that is executed when the application initializer runs that service, typically through craftAppConfig(...).

If the callback returns:

  • void: startup continues immediately

  • Promise: startup waits for the promise to resolve

  • Observable: startup waits until the observable completes

Generator callbacks preserve the same waiting behavior. The generator itself resolves first, then its returned Promise / Observable / void is used as the startup result.

Common Errors#

Missing appStart: true#

typescript
yield * onAppStart(() => undefined);

This throws at runtime if the owning service was not declared with appStart: true.

Nested onAppStart(...)#

typescript
yield *
  onAppStart(function* () {
    yield* onAppStart(() => undefined); // unsupported
    return undefined;
  });

Nested declarations are rejected at runtime.

See Also#

  • craftService

  • Browser Boundaries

←Abstract servicesLazy services→

On this page

  • Import
  • Overview
  • Signature
  • Plain Callback
  • Generator Callback
  • Registering it with craftAppConfig
  • Dependency Tracking
  • Runtime Behavior
  • Common Errors
  • Missing appStart: true
  • Nested onAppStart(...)
  • See Also

@craft-ts · MIT licensed

GitHubnpm

Search