Framework-agnostic utility classes, error types, and an Axios client factory shared across polargold's frontend apps.
Framework-agnostic (no Vue) TypeScript utility library for polargold frontend apps: date/currency formatting, a
namespaced localStorage wrapper, an Axios client factory, an OIDC auth wrapper, Sentry config resolution, and
typed error classes.
Extracted from the near-duplicate composables/utilities found across luv-ui-tools, bws-ui-tools, and four app
repos (luv-autoq-frontend, ama-deep, data-sheet-portal, bws-checkout-service-spa) during the September 2026
frontend tooling audit. Vue-specific composables built on top of this package live in the sibling
@polargold/pg-frontend-vue package, not here — see What belongs here.
nvm use && npm install
# Configure access to the polargold registry (once, globally). Token in Bitwarden under "[NPM Registry] pg-admin"
npm config set -- //npm.white-lan.de/:_authToken=${token}
cd your-project/
echo "@polargold:registry=https://npm.white-lan.de" >> .npmrc
npm install -S @polargold/pg-frontend-core
Peer dependencies (installed automatically by npm ≥7, but listed explicitly since this is a library):
{
"axios": "^1.17.0",
"dayjs": "^1.11.21"
}
Two more are optional peer dependencies, only needed if you actually use the utility that wraps them — install them yourself if so, npm won't pull them in automatically:
{
"@sentry/browser": "^10.69.0",
"oidc-client-ts": "^3.5.0"
}
@sentry/browser is needed only for InitializeSentry.initialize() (not resolveConfig(), the documented
Vue path — see InitializeSentry); oidc-client-ts is needed only for AuthService.
npm run lint
npm run lint:fix
npm run vitest:run
npm run vitest:coverage
npm run docs:build
npm run docs:serve
npm run vite:build
| Service | URL |
|---|---|
| Package registry | Package registry |
| TypeDoc | pg-frontend-core.polargold.dev |
Framework-light, component-agnostic code only:
src/utils)src/errors)Anything that needs Vue (composables, anything using ref/computed/useRouter) belongs in
@polargold/pg-frontend-vue, which depends on this package. Anything specific to one brand (colors, Tailwind
config, useSeminarType, useHomeLink) belongs in that brand's own *-ui-library package, not here.
LocalStorageReplaces the StorageHelper class duplicated in luv-autoq-frontend, ama-deep, data-sheet-portal, and
bws-checkout-service-spa.
Behavior change: the original
StorageHelper.write()rejected every falsy value (if (!value) throw), which incorrectly rejected0,false, and"".LocalStorage.write()only rejectsundefined— check any code that relied on the old (buggy) rejection of falsy values when migrating.
import { LocalStorage } from "@polargold/pg-frontend-core";
import { version } from "../package.json";
// Most keys: versioned, so stale state doesn't carry across a deploy.
const filterStore = new LocalStorage("productSearchFilterParamsStore", {
mode: import.meta.env.MODE,
version,
});
// Access/session tokens: omit `version` so a deploy doesn't force everyone to
// sign in again - matches luv-autoq-frontend's `new StorageHelper(StorageName.AccessToken, false)`.
const accessTokenStore = new LocalStorage("accessToken", { mode: import.meta.env.MODE });
accessTokenStore.write("some-token");
accessTokenStore.read<string>(); // "some-token"
accessTokenStore.remove();
DateFormatterReplaces luv-ui-tools'/bws-ui-tools' DateFormatter (unchanged) — used internally by useDateTime in
@polargold/pg-frontend-vue, or directly for non-Vue formatting needs.
import { DateFormatter } from "@polargold/pg-frontend-core";
DateFormatter.date(new Date()); // "04.09.26"
DateFormatter.internationalDate(new Date()); // "2026-09-04"
CurrencyFormatterReplaces luv-ui-tools'/bws-ui-tools' CurrencyFormatter and bws-checkout-service-spa's standalone
currencyHelper (constructor now takes optional currency/locale params, matching what currencyHelper
already exposed).
import { CurrencyFormatter } from "@polargold/pg-frontend-core";
new CurrencyFormatter().formatCurrency(19.9); // "19,90 €"
new CurrencyFormatter("USD", "en-US").formatCurrency(19.9); // "$19.90"
ColorGradientReplaces luv-ui-tools'/bws-ui-tools' ColorGradient (brand-neutral ColorGradientDirectionName instead of a
Luv/Bws-prefixed enum).
import { ColorGradient, ColorGradientDirectionName } from "@polargold/pg-frontend-core";
new ColorGradient().generate("#FFFFFF", "#000000", ColorGradientDirectionName.Vertical);
TypeCastUtilsReplaces the copies in luv-autoq-frontend and bws-checkout-service-spa. Ships with the regex fix
luv-autoq-frontend already had (see the class's own doc comment) — bws-checkout-service-spa's copy was still
affected by the bug this fixes.
import { TypeCastUtils } from "@polargold/pg-frontend-core";
new TypeCastUtils("42").typedValue; // 42
new TypeCastUtils("true").typedValue; // true
new TypeCastUtils("a,b,c").stringToArray; // ["a", "b", "c"]
removeWhiteSpaceReplaces the identical copies in luv-autoq-frontend and bws-checkout-service-spa.
import { removeWhiteSpace } from "@polargold/pg-frontend-core";
removeWhiteSpace(" hello world "); // "helloworld"
cleanseObjectReplaces ama-deep's hand-rolled cleanseObject and bws-checkout-service-spa's removeEmptyProperties.
import { cleanseObject } from "@polargold/pg-frontend-core";
cleanseObject({ name: "x", note: "", count: 0, tags: [] }); // { name: "x", count: 0 }
createApiClientNew — generalizes the near-identical axios.create() + interceptor blocks in luv-autoq-frontend's, ama-deep's
and data-sheet-portal's api/modules/base files.
import { createApiClient } from "@polargold/pg-frontend-core";
import { accessTokenStore, appLanguageStore } from "./storage";
import router from "@/router";
import { ROUTE } from "@/router/routeDefinitions";
export const apiClient = createApiClient({
baseURL: import.meta.env.VITE_API_URL,
getAccessToken: () => accessTokenStore.read<string>(),
getAcceptLanguage: () => appLanguageStore.read<string>() ?? "en",
onForbidden: () => router.push({ name: ROUTE.PUBLIC_PAGE_NOT_ALLOWED.NAME }),
onServerError: () => router.push({ name: ROUTE.PUBLIC_ERROR_PAGE.NAME }),
});
A request can opt out of the forbidden handler with { suppressForbiddenRedirect: true } in its own Axios config
— same escape hatch data-sheet-portal already relies on for downloads that handle a 403 locally.
AuthServiceReplaces the copies in ama-deep and data-sheet-portal. Configuration is passed in explicitly (see the class's
own doc comment for why), instead of being read from import.meta.env inside the class.
import { AuthService } from "@polargold/pg-frontend-core";
const authService = new AuthService({
authority: import.meta.env.VITE_OPEN_ID_REALM_URL,
clientId: import.meta.env.VITE_OPEN_ID_CLIENT_ID,
redirectUri: import.meta.env.VITE_OPEN_ID_REDIRECT_URL,
postLogoutRedirectUri: import.meta.env.VITE_OPEN_ID_POST_LOGOUT_REDIRECT_URL,
});
await authService.signInRedirect();
InitializeSentryReplaces the copies in luv-autoq-frontend, ama-deep and data-sheet-portal — but narrower on purpose. The
source copies call @sentry/vue's Sentry.init({ app, ... }) with browserTracingIntegration({ router }), which
needs a Vue App/Router — genuinely Vue-specific, so it doesn't belong in this framework-agnostic package. Here,
InitializeSentry only resolves configuration (DSN gating, sample rates); a Vue app calls resolveConfig() and
passes the result into @sentry/vue's own Sentry.init together with its own integrations
(@polargold/pg-frontend-vue is expected to wrap this pattern).
import { InitializeSentry } from "@polargold/pg-frontend-core";
import * as Sentry from "@sentry/vue";
import { version } from "../package.json";
const sentry = new InitializeSentry({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT,
release: version,
validAppDomains: ["https://app.example.polargold.dev"],
});
const config = sentry.resolveConfig();
if (config) {
Sentry.init({
app,
...config,
integrations: [Sentry.browserTracingIntegration({ router }), Sentry.replayIntegration()],
});
}
A non-Vue consumer can instead call await sentry.initialize() directly for plain @sentry/browser error
capture with no tracing integration. @sentry/browser is only ever loaded (dynamically) if this method is
actually called — importing InitializeSentry for resolveConfig() alone never pulls it in.
truncateStringUnifies luv-autoq-frontend's truncateWordByLetterCount and dspace-relaunch's getShortenedString — same
character-count-based truncation, found duplicated during a follow-up scan of dspace-relaunch.
import { truncateString } from "@polargold/pg-frontend-core";
truncateString("A very long product description", 20); // "A very long product..."
isStorageAvailableNew (ported from an unused dspace-relaunch helper) — feature-detects whether localStorage/sessionStorage
actually works, e.g. in private-browsing modes that disable storage.
import { isStorageAvailable, LocalStorage } from "@polargold/pg-frontend-core";
if (isStorageAvailable("localStorage")) {
const store = new LocalStorage("accessToken", { mode: import.meta.env.MODE });
}
getUrlParam / getTypedUrlParam / setUrlParam / removeUrlParam / clearUrlParamsUnifies dspace-relaunch's getUrlParam/updateUrlParam and luv-autoq-frontend's
UrlQueryHelper/UrlQueryHelperGlobal class — sync a value to the URL query string via history.pushState,
without a page reload.
import { getTypedUrlParam, setUrlParam, clearUrlParams } from "@polargold/pg-frontend-core";
setUrlParam("page", 2); // ?page=2
getTypedUrlParam<number>("page"); // 2 (typed via TypeCastUtils, not the raw "2" string)
clearUrlParams(); // strips every query param
Behavior change:
setUrlParamonly removes a param forundefined/null/""/an empty array.luv-autoq-frontend'sUrlQueryHelperused a bare!valuecheck, which would also have deleted a legitimate0(e.g. resetting a page-number filter to the first page).
buildI18nMessageReplaces a copy byte-identical between ama-deep and data-sheet-portal — found in the original audit but
missed at the time. Generalized to accept the target key instead of hardcoding "$vuetify".
import { buildI18nMessage } from "@polargold/pg-frontend-core";
import deLocale from "vuetify/locale/de";
const messages = buildI18nMessage(appTranslations.de, deLocale); // { ...appTranslations.de, $vuetify: deLocale }
triggerBlobDownloadReplaces the copy in luv-autoq-frontend. The same anchor-click-and-revoke sub-routine is hand-rolled again
inside data-sheet-portal's useAuthenticatedFileDownload composable — that composable should call this
instead.
import { triggerBlobDownload } from "@polargold/pg-frontend-core";
const { data } = await apiClient.get("/documents/123", { responseType: "blob" });
triggerBlobDownload(data, "certificate.pdf");
createBroadcastChannelReplaces the copy in luv-autoq-frontend. Returns undefined instead of throwing when the BroadcastChannel
API isn't available.
import { createBroadcastChannel } from "@polargold/pg-frontend-core";
const logoutChannel = createBroadcastChannel("auth-logout");
logoutChannel?.postMessage("logged-out"); // in the tab that logged out
logoutChannel?.addEventListener("message", () => signOutLocally()); // in every other tab
convertFileToBase64 / convertFilesToBase64Replaces luv-autoq-frontend's FileHelper class — simplified to plain functions (see the function's own doc
comment for why).
import { convertFileToBase64, convertFilesToBase64 } from "@polargold/pg-frontend-core";
const base64 = await convertFileToBase64(fileInput.files[0]);
const allBase64 = await convertFilesToBase64(Array.from(fileInput.files));
ApiCallError / LocalStorageErrorPorted unchanged from luv-ui-tools/bws-ui-tools. ApiCallError is thrown by API-calling wrappers such as
@polargold/pg-frontend-vue's useApi; LocalStorageError (with a typed reason: "NO_KEY" | "NO_VALUE") is
thrown by LocalStorage above.
Every symbol is re-exported through its category's own index.ts (src/utils/index.ts, src/errors/index.ts)
and through src/main.ts — nothing is auto-discovered. Follow this for anything new.
Everything pushed as a tag is linted, tested, has its TypeDoc built, and is published to the internal
package registry automatically — see bitbucket-pipelines.yml. Versioning itself is manual:
npm version [major | minor | patch | x.x.x]
| Release type | Description |
|---|---|
| Major | Breaking change to an exported class/function's public shape |
| Minor | A new exported utility |
| Patch | Bugfixes/improvements to existing utilities |
polargold GmbH, Lilienstraße 5-9 / Semperhaus C, 20095 Hamburg