Skip to content

Browser Boundaries

Browser boundaries keep direct browser access out of your craftService implementations while still making those dependencies explicit in the service graph.

Every boundary on this page is backed by a global crafted service marked with browserBoundary: true.

WARNING

Some APIs are not documented here yet.

Import

The main DSL exports are:

typescript
import {
  BrowserCrypto,
  BrowserDocument,
  BrowserHistory,
  BrowserLocation,
  BrowserNavigator,
  BrowserPerformance,
  BrowserWindow,
  Console,
  Cookies,
  LocalStorage,
  SessionStorage,
} from '@craft-ng/core';

When you need to derive methods for later reuse, each boundary also exposes the usual generated helpers:

typescript
import {
  ConsoleServiceToYield,
  injectConsoleService,
  CONSOLE_SERVICE_META_DATA,
} from '@craft-ng/core';

The same pattern exists for the other boundaries:

  • LocalStorageServiceToYield
  • SessionStorageServiceToYield
  • CookiesServiceToYield
  • BrowserLocationServiceToYield
  • BrowserHistoryServiceToYield
  • BrowserNavigatorServiceToYield
  • BrowserPerformanceServiceToYield
  • BrowserCryptoServiceToYield
  • BrowserDocumentServiceToYield
  • BrowserWindowServiceToYield

Motivation

Direct browser access inside a service hides dependencies inside business logic and makes tracking harder.

Browser boundaries solve that in two complementary ways:

  • use yield* X.method(...) when the browser interaction should happen directly inside the generator
  • use XServiceToYield(...) when you want to derive bound browser helpers and reuse them inside returned callbacks

Mental Model

There are two valid ways to use a browser boundary.

Direct DSL

Use the DSL when the browser interaction belongs to the generator itself.

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

const { injectBootLogger } = craftService(
  { name: 'BootLogger', scope: 'global' },
  function* () {
    yield* Console.log('boot');
    yield* Console.info('config loaded');

    return {
      ready: true,
    };
  },
);

Derived Service Helper

Use XServiceToYield(...) when the browser method needs to stay callable later from a returned method.

typescript
import { ConsoleServiceToYield, craftService } from '@craft-ng/core';

const { injectAuditTrail } = craftService(
  { name: 'AuditTrail', scope: 'global' },
  function* () {
    const consoleService = yield* ConsoleServiceToYield(
      undefined,
      ({ log, error }) => ({
        log,
        error,
      }),
    );

    return {
      trackUserAction: (action: string) =>
        consoleService.log('user action', action),
      trackFailure: (error: unknown) =>
        consoleService.error('unexpected failure', error),
    };
  },
);

That second form is what preserves derivability while still tracking the browser dependency explicitly.

Core Examples

Console

typescript
yield * Console.log('my service run');
yield * Console.error('unexpected failure', error);

Local Storage

typescript
yield * LocalStorage.setItem('token', token);

const persistedToken = yield * LocalStorage.getItem('token');
const entryCount = yield * LocalStorage.length();

Session Storage

typescript
yield * SessionStorage.setItem('active-tab', 'settings');

const tab = yield * SessionStorage.getItem('active-tab');

Cookies

typescript
yield *
  Cookies.set('session', sessionId, {
    path: '/',
    sameSite: 'strict',
  });

const session = yield * Cookies.get('session');
const hasSession = yield * Cookies.has('session');

Location

typescript
const href = yield * BrowserLocation.href();
const pathname = yield * BrowserLocation.pathname();

yield * BrowserLocation.reload();

History

typescript
yield * BrowserHistory.replaceState({ step: 2 }, '', '/checkout?step=2');

const state = yield * BrowserHistory.state();

Document

typescript
yield * BrowserDocument.setTitle('Checkout');

const title = yield * BrowserDocument.title();

Window

typescript
const width = yield * BrowserWindow.innerWidth();

yield * BrowserWindow.scrollTo(0, 0);
yield * BrowserWindow.alert('Cache cleared! The page will reload.');

const confirmed =
  yield * BrowserWindow.confirm('Cache cleared! The page will reload.');

if (confirmed) {
  yield * BrowserLocation.reload();
}

Performance And Crypto

typescript
const now = yield * BrowserPerformance.now();
const uuid = yield * BrowserCrypto.randomUUID();

API Reference

Every service below is:

  • scope: 'global'
  • browserBoundary: true
  • exposed both as a DSL object and as generated service helpers

Console and ConsoleService

Methods:

  • debug
  • info
  • log
  • warn
  • error
  • trace
  • group
  • groupCollapsed
  • groupEnd
  • time
  • timeEnd

Generated helpers:

  • injectConsoleService
  • ConsoleServiceToYield
  • CONSOLE_SERVICE_META_DATA

LocalStorage and LocalStorageService

Methods:

  • getItem
  • setItem
  • removeItem
  • clear
  • key
  • length

SessionStorage and SessionStorageService

Methods:

  • getItem
  • setItem
  • removeItem
  • clear
  • key
  • length

Cookies and CookiesService

Methods:

  • get
  • getAll
  • set
  • remove
  • has

BrowserLocation and BrowserLocationService

Methods:

  • href
  • origin
  • protocol
  • host
  • hostname
  • port
  • pathname
  • search
  • hash
  • assign
  • replace
  • reload

BrowserHistory and BrowserHistoryService

Methods:

  • length
  • state
  • back
  • forward
  • go
  • pushState
  • replaceState

BrowserNavigator and BrowserNavigatorService

Methods:

  • userAgent
  • language
  • languages
  • onLine
  • cookieEnabled
  • sendBeacon

BrowserPerformance and BrowserPerformanceService

Methods:

  • now
  • mark
  • measure
  • clearMarks
  • clearMeasures

BrowserCrypto and BrowserCryptoService

Methods:

  • randomUUID
  • getRandomValues
  • digest

BrowserDocument and BrowserDocumentService

Methods:

  • title
  • setTitle
  • visibilityState
  • hasFocus

BrowserWindow and BrowserWindowService

Methods:

  • innerWidth
  • innerHeight
  • scrollX
  • scrollY
  • scrollTo
  • alert
  • confirm

CraftHttpClient is implemented, but it is not a browser boundary.

Unlike Console, LocalStorage, or BrowserLocation, Angular's HttpClient is already a DI-managed Angular dependency. It is better modeled as a typed craft adapter than as a browser-host global.

Its contract is intentionally different:

  • it is not treated as browserBoundary: true
  • it requires success: response<T>() inside a declarative builder
  • it can declare ordered exceptions: [function* (...) { ... }] rules
  • it returns a promise of Success | craftException({ code: 'HttpError' })

Usage looks like this:

typescript
const getUsers =
  yield *
  CraftHttpClient.get(({ response }) => ({
    url: '/api/users',
    params: { page: 1 },
    success: response<User[]>(),
  }));

const createUser =
  yield *
  CraftHttpClient.post(({ response }) => ({
    url: '/api/users',
    payload,
    success: response<User>(),
  }));

const login =
  yield *
  CraftHttpClient.post(({ response }) => ({
    url: '/api/login',
    payload,
    success: response<{ token: string }>(),
    exceptions: [
      function* ({ status, code, content }) {
        if (!(yield* status(400))) return;
        if (!(yield* code('PASSWORD_REQUIRED'))) return;
        if (!(yield* content('Password is required'))) return;

        return craftException({ code: 'PASSWORD_REQUIRED' });
      },
    ],
  }));

const users = await getUsers();
const createdUser = await createUser();
const loginResult = await login();

Design Constraints

The browser boundaries stay intentionally narrow.

  • Reads are exposed as methods so the public API stays uniform with yield*.
  • Raw window, document, and DOM nodes are not exposed as public outputs.
  • BrowserDocument and BrowserWindow remain minimal rather than becoming generic escape hatches.

This keeps the API focused on explicit browser interactions instead of reintroducing broad direct access to host globals.

Relationship With craftService And toCraftService

Browser boundaries participate in the same dependency tracking model as any other crafted service.

  • craftService is what you use to consume them and compose higher-level services.
  • toCraftService remains the right tool for adapting Angular or host dependencies that are not part of this built-in browser boundary set.

See Also