@msflib/core¶
Framework-agnostic core package for msflib. Provides global application configuration, a safe browser storage wrapper, and a multi-tenant runtime used by all other msflib modules.
Table of Contents¶
- Installation
- Quick Start
- API Reference
- configureApplication
- getApplicationConfig
- getApplicationEndpoints
- storage
- Workspace Runtime
- Multi-Tenant Guide
- Single-Tenant Mode
- Usage in Module Packages
Installation¶
pnpm add @msflib/core
Quick Start¶
Call configureApplication() once before mounting any msflib providers — typically in your app root or an initialisation file.
import { configureApplication } from '@msflib/core';
configureApplication({
baseURL: 'https://api.example.com/api/v1',
accessTokenKey: 'access_token',
});
Next.js (App Router)¶
// src/application.client.ts
'use client';
import { configureApplication } from '@msflib/core';
configureApplication({
baseURL: process.env.NEXT_PUBLIC_API_URL!,
accessTokenKey: 'access_token',
});
// src/app/providers.tsx
'use client';
import '@/application.client'; // must be imported before any msflib provider
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider } from '@msflib/react-auth';
import { NotificationProvider } from '@msflib/react-notification';
const qc = new QueryClient();
export default function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={qc}>
<AuthProvider>
<NotificationProvider>{children}</NotificationProvider>
</AuthProvider>
</QueryClientProvider>
);
}
Important:
configureApplication()is a one-time call. Subsequent calls are silently ignored to prevent accidental reconfiguration.
API Reference¶
configureApplication¶
Initialises global application configuration. Must be called once before any module is used.
configureApplication(config: {
baseURL: string;
accessTokenKey: string;
workspace?: string | null;
endpoints?: Record<string, Record<string, string>>;
}): void
| Field | Type | Required | Description |
|---|---|---|---|
baseURL |
string |
✅ | Base URL for all API requests |
accessTokenKey |
string |
✅ | localStorage key used to read the access token |
workspace |
string \| null |
— | Initial workspace slug for multi-tenant apps |
endpoints |
object |
— | Per-module endpoint path overrides |
configureApplication({
baseURL: 'https://api.example.com/api/v1',
accessTokenKey: 'access_token',
workspace: 'acme-corp', // optional: initial workspace
endpoints: {
notification: {
notifications: '/alerts', // override default path
},
},
});
getApplicationConfig¶
Returns the full configuration object. Throws if called before configureApplication().
getApplicationConfig(): ApplicationConfig
import { getApplicationConfig } from '@msflib/core';
const cfg = getApplicationConfig();
console.log(cfg.baseURL); // 'https://api.example.com/api/v1'
getApplicationEndpoints¶
Returns the endpoint overrides for a specific module, or undefined if none were configured.
getApplicationEndpoints(moduleName: string): Record<string, string> | undefined
const endpoints = getApplicationEndpoints('notification');
// { notifications: '/alerts' } or undefined
storage¶
A safe localStorage wrapper that is a no-op on the server (SSR-safe). Use this wherever you need to read or write tokens and preferences.
import { storage } from '@msflib/core';
storage.setItem('access_token', 'eyJ...');
const token = storage.getItem('access_token'); // string | null
storage.removeItem('access_token');
| Method | Signature | Description |
|---|---|---|
getItem |
(key: string) => string \| null |
Reads a value from localStorage |
setItem |
(key: string, value: string) => void |
Writes a value to localStorage |
removeItem |
(key: string) => void |
Removes a key from localStorage |
On the server or in environments without
window, all methods are safe no-ops —getItemreturnsnull.
Workspace Runtime¶
The workspace runtime is a lightweight pub/sub state manager for the active workspace. It is fully framework-agnostic — no React required. React modules integrate via the useActiveWorkspace hook from @msflib/react-shared.
setActiveWorkspace¶
Updates the current active workspace. Notifies all subscribers. Slug is trimmed and normalised — empty or whitespace-only strings are treated as null.
setActiveWorkspace(workspace?: string | null): void
import { setActiveWorkspace } from '@msflib/core';
setActiveWorkspace('acme-corp'); // switch to workspace
setActiveWorkspace(null); // clear workspace (single-workspace mode)
setActiveWorkspace(''); // treated as null
Calling
setActiveWorkspacewith the same value that is already set is a no-op — no listeners are notified.
getActiveWorkspace¶
Returns the current active workspace slug, or null if no workspace is set.
getActiveWorkspace(): string | null
const workspace = getActiveWorkspace(); // 'acme-corp' | null
subscribeActiveWorkspace¶
Registers a listener that is called whenever the active workspace changes. Returns an unsubscribe function.
subscribeActiveWorkspace(listener: () => void): () => void
const unsubscribe = subscribeActiveWorkspace(() => {
console.log('Workspace changed to:', getActiveWorkspace());
});
// Later, clean up:
unsubscribe();
This is the subscription API used internally by
useActiveWorkspacein@msflib/react-sharedto drive React re-renders on workspace change.
resolveWorkspaceScopedEndpoint¶
Registers an endpoint as workspace-scoped (or explicitly unscoped) and returns its normalized path. Each module's API layer calls this once per endpoint, typically via configuredApiClient(), rather than being called directly by consumers.
resolveWorkspaceScopedEndpoint(
endpoint: string,
options?: { workspaceScoped?: boolean; matchPath?: string },
): string
| Argument | Description |
|---|---|
endpoint |
The API endpoint path. |
options.workspaceScoped |
false registers the endpoint as explicitly unscoped (e.g. auth). Defaults to scoped. |
options.matchPath |
Override which path gets registered, when it differs from endpoint (e.g. a narrower opt-out under a broader scoped prefix). |
isWorkspaceScopedEndpoint¶
Checks whether a given path is currently registered as workspace-scoped, respecting any explicit unscoped overrides.
isWorkspaceScopedEndpoint(endpoint: string): boolean
import { addWorkspaceScopedEndpoint, addWorkspaceUnscopedEndpoint, isWorkspaceScopedEndpoint } from '@msflib/core';
addWorkspaceScopedEndpoint('/notifications');
isWorkspaceScopedEndpoint('/notifications'); // → true
addWorkspaceUnscopedEndpoint('/notifications/public');
isWorkspaceScopedEndpoint('/notifications/public'); // → false (explicit opt-out)
The registry only tracks which endpoints are scoped — it does not rewrite URLs itself. The actual workspace prefix or header is applied by the
apiClientDecoratoryou pass toconfigureApplication(e.g.workspaceHookDecoratorfrom@msflib/react-workspace), which consults this registry at request time.
Multi-Tenant Guide¶
This package has built-in support for multi-tenant SaaS apps where different workspaces are served from different URL namespaces (e.g. /acme/notifications vs /globex/notifications).
How it works¶
- Each API module registers its endpoints via
resolveWorkspaceScopedEndpoint()(throughconfiguredApiClient()); the configuredapiClientDecoratorapplies the workspace prefix/header at request time, so switching is instant. - The active workspace is stored as module-level state with a pub/sub listener set.
- React modules subscribe to workspace changes via
useActiveWorkspace()from@msflib/react-shared. - TanStack Query caches are scoped per workspace using workspace-first query keys.
Static workspace (known at login time)¶
configureApplication({
baseURL: 'https://api.example.com/api/v1',
accessTokenKey: 'access_token',
workspace: 'acme-corp',
});
Dynamic workspace switching (org switcher, workspace switcher)¶
// Somewhere in your app (sidebar, dropdown, etc.)
import { setActiveWorkspace } from '@msflib/core';
function OrgSwitcher({ orgs }) {
return (
<select onChange={(e) => setActiveWorkspace(e.target.value)}>
{orgs.map((org) => (
<option key={org.slug} value={org.slug}>{org.name}</option>
))}
</select>
);
}
This single call cascades through modules that subscribe to it via useActiveWorkspace():
- All API requests immediately use the new workspace path
- Query keys that include the workspace change, triggering a fresh fetch
- No page reload required
Not every provider subscribes, though — @msflib/react-auth's AuthProvider uses a fixed query key and does not call useActiveWorkspace(), so it does not auto-refetch on a workspace switch. See React Auth's Multi-Tenancy Support for the current contract and how to invalidate it explicitly.
Single-Tenant Mode¶
Just omit workspace from configuration. Everything works as normal.
configureApplication({
baseURL: 'https://api.example.com/api/v1',
accessTokenKey: 'access_token',
// No workspace field
});
Usage in Module Packages¶
All msflib modules use this package internally. You typically do not call these APIs directly from a module — they are consumed automatically.
// Inside a module API file
import { configuredApiClient } from '@msflib/core';
export const createNotificationApi = (isWorkspaceScoped = true) => {
const { apiClient, loginClient } = configuredApiClient({ isWorkspaceScoped });
return {
list: () => apiClient<Notification[]>('GET', '/notifications'),
// Auth endpoints opt out of workspace scoping per-call
login: (payload: LoginPayload) =>
loginClient('/login', payload, { isWorkspaceScoped: false }),
};
};
Exports¶
import {
configureApplication,
getApplicationConfig,
getApplicationEndpoints,
resetApplicationConfig,
storage,
createStorage,
configuredApiClient,
setActiveWorkspace,
getActiveWorkspace,
subscribeActiveWorkspace,
resolveWorkspaceScopedEndpoint,
isWorkspaceScopedEndpoint,
} from '@msflib/core';
Available APIs¶
-
configureApplication(config) Set the global Application configuration. Must be called once at app startup before using any Application.
-
getApplicationConfig() Retrieve the resolved global configuration anywhere inside Application or consumer apps.
-
storage Default safe browser storage wrapper (uses localStorage with SSR guards).
-
createStorage(options?) Factory to create a custom storage adapter (e.g. sessionStorage, memory storage, or custom persistence layer).
-
configuredApiClient(options?) Factory used internally by module API layers to build a workspace-aware HTTP client.