@lenne.tech/ng-base 22.0.2 → 22.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -5,7 +5,10 @@ It connects an Angular 22 app to [`@lenne.tech/nest-server`](https://github.com/
|
|
|
5
5
|
upload, AI, offline sync), ships a Spartan-based UI layer for Tailwind CSS 4, SSR server helpers and Playwright helpers.
|
|
6
6
|
|
|
7
7
|
- Angular `^22.2` (standalone, zoneless, OnPush, signals), Tailwind CSS `4.3`, Spartan UI `1.9`
|
|
8
|
-
- Version scheme:
|
|
8
|
+
- Version scheme: MAJOR = Angular major (never raised for a breaking change), MINOR = a change an app has to act on
|
|
9
|
+
when it updates, always with a [migration guide](https://github.com/lenneTech/ng-base/tree/main/migration-guides),
|
|
10
|
+
PATCH = a fix or addition that needs no action. `ng-base-starter` carries the same version; there is no 22.0.3.
|
|
11
|
+
`22.0.0` is a rebuild; versions up to `18.x` are reachable via tag `v18.1.3`.
|
|
9
12
|
- Template: [`ng-base-starter`](https://github.com/lenneTech/ng-base-starter) (`lt fullstack init --frontend angular`).
|
|
10
13
|
|
|
11
14
|
## Entry points
|
|
@@ -582,7 +582,7 @@ function messageOf(value) {
|
|
|
582
582
|
if (nested && typeof nested === "object") return messageOf(nested);
|
|
583
583
|
return "";
|
|
584
584
|
}
|
|
585
|
-
const LT_NG_BASE_VERSION = "22.0.
|
|
585
|
+
const LT_NG_BASE_VERSION = "22.0.4";
|
|
586
586
|
const LT_HYDRATED_FLAG = "__ltNgBaseHydrated";
|
|
587
587
|
function provideLtNgBase(config = {}) {
|
|
588
588
|
return makeEnvironmentProviders([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lenne.tech-ng-base.mjs","names":[],"sources":["../../../projects/ng-base/src/lib/warn-once.ts","../../../projects/ng-base/src/lib/pre-hydration-input.ts","../../../projects/ng-base/src/lib/runtime-config.ts","../../../projects/ng-base/src/lib/api-url.ts","../../../projects/ng-base/src/lib/cookie.ts","../../../projects/ng-base/src/lib/crypto.ts","../../../projects/ng-base/src/lib/fetch.ts","../tmp-esm2022/lib/i18n/de.json","../tmp-esm2022/lib/i18n/en.json","../../../projects/ng-base/src/lib/i18n.ts","../../../projects/ng-base/src/lib/toast.ts","../../../projects/ng-base/src/lib/error-translation.ts","../../../projects/ng-base/src/lib/version.ts","../../../projects/ng-base/src/lib/provide-lt-ng-base.ts","../../../projects/ng-base/src/lib/tw.ts"],"sourcesContent":["const warnedKeys = new Set<string>();\n\n/**\n * Log `message` the first time `key` is seen; ignore later calls. The conditions behind these messages (missing API\n * URL, implicit dev proxy) are constant for the process, so repeating them only drowns other output.\n */\nexport function warnLtOnce(key: string, message: string, level: 'error' | 'warn' = 'warn'): void {\n if (warnedKeys.has(key)) {\n return;\n }\n warnedKeys.add(key);\n console[level](message);\n}\n\n/** Test only: forget which one-shot messages were emitted. */\nexport function resetLtWarnOnceState(): void {\n warnedKeys.clear();\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { ApplicationRef, inject, PLATFORM_ID, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\n\n/** Options of the pre-hydration input preservation (`provideLtNgBase({ preHydrationInput })`). */\nexport interface LtPreHydrationInputOptions {\n /** Keep what users type before hydration (default `true`). */\n enabled?: boolean;\n /**\n * How long after hydration to keep restoring, in milliseconds (default `1500`; `0` restores once). Clamped to 30000.\n * When it elapses the snapshot is dropped, so preserved values (passwords included) are not kept for the session.\n */\n maxRestoreMs?: number;\n}\n\ntype LtEditableField = HTMLInputElement | HTMLTextAreaElement;\n\n/** Every `input`/`textarea` under `root` whose value differs from its server-rendered default, with that value. */\nexport function collectLtEditedFields(root: ParentNode): Map<LtEditableField, string> {\n const edited = new Map<LtEditableField, string>();\n for (const node of Array.from(root.querySelectorAll('input, textarea'))) {\n const field = node as LtEditableField;\n if (field.value !== field.defaultValue) {\n edited.set(field, field.value);\n }\n }\n return edited;\n}\n\n/**\n * Write `value` back into `field` and dispatch a bubbling `input` event, so `(input)` handlers, `model()` and Signal\n * Forms adopt it. Returns `false` when the field is detached or already holds the value.\n */\nexport function restoreLtField(field: LtEditableField, value: string): boolean {\n if (!field.isConnected || field.value === value) {\n return false;\n }\n field.value = value;\n field.dispatchEvent(new Event('input', { bubbles: true }));\n return true;\n}\n\n/**\n * Pre-hydration input preservation (browser only).\n *\n * Text typed into a server-rendered field before hydration is in the DOM, but the first change detection after\n * hydration writes the component's (empty) value over it — `[value]` bindings and Signal Forms do — before the replayed\n * events run, so `withEventReplay()` alone loses it (measured in the ng-base playground E2E). The fields are snapshot\n * at app initialisation (the SSR DOM is reused by hydration, so the element references stay valid) and restored once\n * the app is stable, then every 250 ms for `maxRestoreMs` for fields hydrated late; fields stay ordinary editable\n * fields throughout (autofill and screen readers untouched).\n */\nexport function provideLtPreHydrationInput(options: LtPreHydrationInputOptions = {}): EnvironmentProviders {\n return provideAppInitializer(() => {\n if (options.enabled === false || !isPlatformBrowser(inject(PLATFORM_ID)) || typeof document === 'undefined') {\n return;\n }\n const edited = collectLtEditedFields(document);\n if (edited.size === 0) {\n return;\n }\n const configured = options.maxRestoreMs ?? 1500;\n const maxRestoreMs = Number.isFinite(configured) && configured > 0 ? Math.min(configured, 30_000) : 0;\n const sweep = (): void => {\n for (const [field, value] of edited) {\n if (restoreLtField(field, value) || !field.isConnected) {\n edited.delete(field);\n }\n }\n };\n void inject(ApplicationRef)\n .whenStable()\n .then(() => {\n sweep();\n if (maxRestoreMs <= 0 || edited.size === 0) {\n edited.clear();\n return;\n }\n const interval = setInterval(sweep, 250);\n setTimeout(() => {\n clearInterval(interval);\n edited.clear();\n }, maxRestoreMs);\n });\n });\n}\n\n/**\n * Inline script (for `<head>`) that blocks the native submission of forms without `action` until the app handles them.\n * A server-rendered login form submitted before hydration would otherwise send a native GET with the password in the\n * URL. `ltAngularRequestHandler()` writes it into every SSR HTML response; add it to `index.html` yourself without SSR.\n */\nexport const LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT =\n \"document.addEventListener('submit',function(e){var f=e.target;if(f instanceof HTMLFormElement&&!f.hasAttribute('action')){e.preventDefault();}},true);\";\n","import { inject, InjectionToken, REQUEST_CONTEXT } from '@angular/core';\n\nimport { LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT } from './pre-hydration-input';\n\n/**\n * Feature options that can be overridden per stage at runtime (`NG_PUBLIC_LT_<FEATURE>_<OPTION>`).\n *\n * Each feature provider (`provideLtAuth`, `provideLtFiles`, `provideLtAi`, `provideLtSync`, `provideLtNgBase`)\n * merges, in this order: its defaults, the options passed in code, and the values found here.\n */\nexport interface LtPublicFeatureConfig {\n ai?: Record<string, unknown>;\n auth?: Record<string, unknown>;\n errorTranslation?: Record<string, unknown>;\n sync?: Record<string, unknown>;\n tus?: Record<string, unknown>;\n [feature: string]: Record<string, unknown> | undefined;\n}\n\n/**\n * Public runtime configuration of the app, the Angular counterpart of Nuxt's `runtimeConfig.public`.\n *\n * With SSR the server reads it per request from the `NG_*` environment (`readLtRequestContext()` in\n * `@lenne.tech/ng-base/server`), passes it to `AngularNodeAppEngine.handle(req, context)` as request context\n * (`REQUEST_CONTEXT`) and writes it as JSON `<script id=\"ng-runtime-config\">` into every HTML response, also for\n * `RenderMode.Client` routes; the browser reads that script. One build can therefore be promoted from stage to stage.\n * Without SSR the values come from `provideLtNgBase({ runtime })`.\n *\n * Projects may carry additional public values (e.g. `plausibleApiHost`); they travel the same way.\n * Never put a secret here: everything in this object reaches the browser.\n */\nexport interface LtRuntimeConfig {\n [key: string]: unknown;\n /** Commit of the running build (`NG_PUBLIC_APP_COMMIT`, fallback `APP_VERSION_COMMIT`), `unknown` when unset. */\n appCommit?: string;\n /**\n * Deployment stage (`NG_PUBLIC_APP_ENV`), using nest-server's NODE_ENV block names: `local`, `ci`, `e2e`, `develop`,\n * `test`, `production`. Only `production` counts as production; features that are off in production fail closed.\n */\n appEnv?: string;\n /** Version of the running build (from the server, e.g. package.json). */\n appVersion?: string;\n /** Public API base URL (`NG_PUBLIC_API_URL`), e.g. `https://api.example.com`. Empty = relative to the app origin. */\n apiUrl: string;\n /**\n * Route browser API calls through the dev-server proxy under `/api` (`NG_PUBLIC_API_PROXY`).\n * `null` = not configured: the proxy is used in dev mode (`isDevMode()`) with a one-time warning.\n * Never used during SSR.\n */\n apiProxy: boolean | null;\n /** Opt-in cookie namespace (`NG_PUBLIC_COOKIE_PREFIX`); must mirror the backend `COOKIE_PREFIX`. */\n cookiePrefix: string;\n /** Feature options overridden at runtime (`NG_PUBLIC_LT_*`). */\n lt: LtPublicFeatureConfig;\n /** Absolute public URL of the app (`NG_PUBLIC_SITE_URL`), used for redirect/callback URLs, SEO and security.txt. */\n siteUrl: string;\n /** Prefix for `localStorage` keys (`NG_PUBLIC_STORAGE_PREFIX`). */\n storagePrefix: string;\n}\n\n/** Defaults of {@link LtRuntimeConfig}. */\nexport const LT_RUNTIME_CONFIG_DEFAULTS: LtRuntimeConfig = {\n apiProxy: null,\n apiUrl: '',\n cookiePrefix: '',\n lt: {},\n siteUrl: '',\n storagePrefix: '',\n};\n\n/** The resolved public runtime configuration (defaults ← `provideLtNgBase({ runtime })` ← request context / `<script id=\"ng-runtime-config\">`). */\nexport const LT_RUNTIME_CONFIG = new InjectionToken<LtRuntimeConfig>('LT_RUNTIME_CONFIG');\n\n/** What the SSR server passes to `AngularNodeAppEngine.handle(req, context)` (see `readLtRequestContext()`). */\nexport interface LtRequestContext {\n /** Public runtime configuration (also written into the HTML as `<script id=\"ng-runtime-config\">`). */\n runtimeConfig: Partial<LtRuntimeConfig>;\n /** Internal API URL for SSR requests only (`NG_API_URL`), never sent to the browser. */\n serverApiUrl?: string;\n}\n\n/**\n * Runtime configuration on the server: public part plus the server-only API URL. Read from `REQUEST_CONTEXT`\n * (an {@link LtRequestContext}) by default; `provideLtServerRuntimeConfig()` provides it from the environment instead\n * for servers that pass no request context. `null` in the browser.\n */\nexport interface LtServerRuntimeConfig {\n /** Public part. */\n public: Partial<LtRuntimeConfig>;\n /** Internal API URL for SSR requests only (`NG_API_URL`), never sent to the browser. */\n serverApiUrl: string;\n}\n\n/** Server-only runtime configuration (see {@link LtServerRuntimeConfig}). `null` in the browser. */\nexport const LT_SERVER_RUNTIME_CONFIG = new InjectionToken<LtServerRuntimeConfig | null>('LT_SERVER_RUNTIME_CONFIG', {\n factory: () => {\n const context = inject(REQUEST_CONTEXT, { optional: true }) as Partial<LtRequestContext> | null;\n if (!context || typeof context !== 'object' || !context.runtimeConfig) {\n return null;\n }\n return { public: context.runtimeConfig, serverApiUrl: context.serverApiUrl ?? '' };\n },\n});\n\n/** Id of the JSON `<script>` that carries the public runtime configuration into the browser. */\nexport const LT_RUNTIME_CONFIG_SCRIPT_ID = 'ng-runtime-config';\n\n/** Read the runtime configuration the server wrote into the document (`null` when missing or unreadable). */\nexport function readLtRuntimeConfigFromDocument(doc: Document | null | undefined): Partial<LtRuntimeConfig> | null {\n const text = doc?.getElementById?.(LT_RUNTIME_CONFIG_SCRIPT_ID)?.textContent;\n if (!text) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(text);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Partial<LtRuntimeConfig>) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Serialise the public runtime configuration for an inline `<script type=\"application/json\">`: `<`, `>`, `&`, U+2028\n * and U+2029 are escaped, so a value can never close the script element, open a comment or form an HTML entity.\n */\nexport function serializeLtRuntimeConfig(config: Partial<LtRuntimeConfig>): string {\n return JSON.stringify(config)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\n/**\n * Insert the runtime configuration as the first child of `<head>` (documents without `<head>` stay untouched), by\n * default preceded by the pre-hydration submit guard (`LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT`).\n */\nexport function injectLtRuntimeConfigScript(html: string, config: Partial<LtRuntimeConfig>, options: { submitGuard?: boolean } = {}): string {\n const head = /<head(\\s[^>]*)?>/i.exec(html);\n if (!head) {\n return html;\n }\n const at = head.index + head[0].length;\n const guard = options.submitGuard === false ? '' : `<script>${LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT}</script>`;\n return `${html.slice(0, at)}${guard}<script id=\"${LT_RUNTIME_CONFIG_SCRIPT_ID}\" type=\"application/json\">${serializeLtRuntimeConfig(config)}</script>${html.slice(at)}`;\n}\n\n/**\n * Merge partial runtime configurations onto a base. `lt` is merged per feature; `undefined` values never\n * override (an unset env variable keeps the code default).\n */\nexport function mergeLtRuntimeConfig(base: LtRuntimeConfig, ...overrides: (Partial<LtRuntimeConfig> | null | undefined)[]): LtRuntimeConfig {\n const result: LtRuntimeConfig = { ...base, lt: { ...base.lt } };\n for (const override of overrides) {\n if (!override) {\n continue;\n }\n for (const [key, value] of Object.entries(override) as [keyof LtRuntimeConfig, unknown][]) {\n if (value === undefined) {\n continue;\n }\n if (key === 'lt') {\n for (const [feature, options] of Object.entries((value ?? {}) as LtPublicFeatureConfig)) {\n result.lt[feature] = { ...result.lt[feature], ...definedEntries(options ?? {}) };\n }\n continue;\n }\n (result as unknown as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Merge feature options: defaults ← code options ← runtime overrides. Nested plain objects are merged one level\n * deep (e.g. `auth.interceptor`), arrays and primitives replace. `undefined` never overrides.\n */\nexport function mergeLtFeatureOptions<T extends object>(defaults: T, ...overrides: (object | null | undefined)[]): T {\n const result: Record<string, unknown> = { ...(defaults as Record<string, unknown>) };\n for (const override of overrides) {\n if (!override) {\n continue;\n }\n for (const [key, value] of Object.entries(override)) {\n if (value === undefined) {\n continue;\n }\n const current = result[key];\n result[key] = isPlainObject(current) && isPlainObject(value) ? { ...current, ...definedEntries(value) } : value;\n }\n }\n return result as T;\n}\n\nfunction definedEntries(source: object): Record<string, unknown> {\n return Object.fromEntries(Object.entries(source).filter(([, value]) => value !== undefined));\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { isPlatformServer } from '@angular/common';\nimport { inject, isDevMode, PLATFORM_ID, Service } from '@angular/core';\n\nimport { warnLtOnce } from './warn-once';\nimport { LT_RUNTIME_CONFIG, LT_SERVER_RUNTIME_CONFIG } from './runtime-config';\n\n/** Render scope an API URL is resolved for. */\nexport type LtRenderScope = 'browser' | 'server';\n\n/** Outcome of resolving the API base URL for the current render scope. */\nexport interface LtApiUrlResolution {\n /** Base URL to prefix onto API paths: `'/api'` in proxy mode, `''` when unresolved. No trailing slash. */\n baseUrl: string;\n /** `true` when no API URL is configured for {@link scope}. */\n missing: boolean;\n /** `true` when the dev-server proxy prefix `/api` is in use. */\n proxy: boolean;\n /** Scope the resolution was performed for. */\n scope: LtRenderScope;\n}\n\n/** Inputs of {@link resolveLtApiBaseUrl}. */\nexport interface LtApiUrlInputs {\n /** `NG_PUBLIC_API_PROXY` (`null` = unset). */\n apiProxy: boolean | null;\n /** `NG_PUBLIC_API_URL`. */\n apiUrl: string;\n /** Whether the app runs in dev mode (`isDevMode()`), used when `apiProxy` is unset. */\n devMode: boolean;\n /** Render scope. */\n scope: LtRenderScope;\n /** `NG_API_URL` (server only). */\n serverApiUrl?: string;\n}\n\nconst MISSING_API_URL_WARNING: Record<LtRenderScope, string> = {\n browser:\n '[@lenne.tech/ng-base] No API URL configured. Set NG_PUBLIC_API_URL. API paths stay relative and resolve against the app ' +\n 'origin, so auth and setup calls 404 unless the app is served behind a same-origin reverse proxy.',\n server:\n '[@lenne.tech/ng-base] No API URL configured for SSR. Set NG_API_URL or NG_PUBLIC_API_URL. API paths stay relative, so ' + 'server-side requests never reach the backend.',\n};\n\n/**\n * Pure resolution of the API base URL (no DI, no logging).\n *\n * - Server: `NG_API_URL` → `NG_PUBLIC_API_URL` → `''`. The proxy is never used during SSR.\n * - Browser: `/api` when the proxy is on (explicitly, or implicitly in dev mode while unset), else `NG_PUBLIC_API_URL` → `''`.\n *\n * Trailing slashes are stripped so `${baseUrl}${path}` never produces a double slash.\n */\nexport function resolveLtApiBaseUrl(inputs: LtApiUrlInputs): LtApiUrlResolution {\n if (inputs.scope === 'server') {\n const apiUrl = inputs.serverApiUrl || inputs.apiUrl || '';\n return { baseUrl: stripTrailingSlashes(apiUrl), missing: !apiUrl, proxy: false, scope: 'server' };\n }\n if (inputs.apiProxy === true || (inputs.apiProxy === null && inputs.devMode)) {\n return { baseUrl: '/api', missing: false, proxy: true, scope: 'browser' };\n }\n const apiUrl = inputs.apiUrl || '';\n return { baseUrl: stripTrailingSlashes(apiUrl), missing: !apiUrl, proxy: false, scope: 'browser' };\n}\n\n/** Whether `url` is absolute (`scheme:` or protocol-relative `//`). */\nexport function isLtAbsoluteUrl(url: string): boolean {\n return /^[a-z][a-z\\d+\\-.]*:/i.test(url) || url.startsWith('//');\n}\n\n/**\n * The single URL builder used by auth, system setup, error translation, files, AI and sync.\n *\n * Reads {@link LT_RUNTIME_CONFIG} and, on the server, {@link LT_SERVER_RUNTIME_CONFIG}. A missing API URL is warned\n * about once per scope; an implicit dev proxy (unset `NG_PUBLIC_API_PROXY` in dev mode) once as well.\n */\n@Service()\nexport class LtApiUrlService {\n private readonly runtimeConfig = inject(LT_RUNTIME_CONFIG, { optional: true });\n private readonly serverConfig = inject(LT_SERVER_RUNTIME_CONFIG, { optional: true });\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /** Resolve the base URL for the current scope without logging. */\n resolve(): LtApiUrlResolution {\n return resolveLtApiBaseUrl({\n apiProxy: this.runtimeConfig?.apiProxy ?? null,\n apiUrl: this.runtimeConfig?.apiUrl ?? '',\n devMode: isDevMode(),\n scope: this.isServer ? 'server' : 'browser',\n serverApiUrl: this.serverConfig?.serverApiUrl,\n });\n }\n\n /** Base URL (no trailing slash) for the current scope. */\n baseUrl(): string {\n return this.resolveAndWarn().baseUrl;\n }\n\n /** Build a full API URL for a path such as `/iam/sign-in/email`. Absolute URLs are returned unchanged. */\n buildUrl(path: string): string {\n if (isLtAbsoluteUrl(path)) {\n return path;\n }\n const normalized = path.startsWith('/') ? path : `/${path}`;\n return `${this.resolveAndWarn().baseUrl}${normalized}`;\n }\n\n /** Whether browser requests go through the dev-server proxy under `/api`. Always `false` on the server. */\n isProxy(): boolean {\n return this.resolve().proxy;\n }\n\n /** Report a missing API URL (`dev`: warning, production: error), at most once per scope. Used by the config check. */\n reportMissing(): boolean {\n const resolution = this.resolve();\n if (resolution.missing) {\n warnLtOnce(`missing-api-url:${resolution.scope}`, MISSING_API_URL_WARNING[resolution.scope], isDevMode() ? 'warn' : 'error');\n }\n return resolution.missing;\n }\n\n private resolveAndWarn(): LtApiUrlResolution {\n const resolution = this.resolve();\n if (resolution.missing) {\n warnLtOnce(`missing-api-url:${resolution.scope}`, MISSING_API_URL_WARNING[resolution.scope]);\n }\n if (resolution.proxy && this.runtimeConfig?.apiProxy === null) {\n warnLtOnce(\n 'api-proxy-implicit',\n '[@lenne.tech/ng-base] API proxy (/api) activated implicitly because the app runs in dev mode. ' +\n 'Set NG_PUBLIC_API_PROXY=true to make it explicit, or NG_PUBLIC_API_PROXY=false to call the API directly.',\n );\n }\n return resolution;\n }\n}\n\nfunction stripTrailingSlashes(url: string): string {\n return url.replace(/\\/+$/, '');\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { inject, PLATFORM_ID, REQUEST, Service } from '@angular/core';\n\n/** Options for {@link LtCookieService.set}. */\nexport interface LtCookieOptions {\n /** Cookie domain. Omit for a host-only cookie. */\n domain?: string;\n /** Lifetime in seconds. Omit for a session cookie. */\n maxAge?: number;\n /** Cookie path (default `/`). */\n path?: string;\n /** SameSite attribute (default `lax`). */\n sameSite?: 'lax' | 'none' | 'strict';\n /** Secure attribute (default: `true` on `https:`). */\n secure?: boolean;\n}\n\n/**\n * SSR-aware cookie access.\n *\n * - Browser: reads and writes `document.cookie`.\n * - Server: reads the `Cookie` header of the incoming request (`REQUEST`); never writes. A server render must not\n * set auth cookies (it would write `{ user: null }` over a valid browser state), so `set`/`delete` are no-ops there.\n */\n@Service()\nexport class LtCookieService {\n private readonly document = inject(DOCUMENT);\n private readonly request = inject(REQUEST, { optional: true });\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /** Raw cookie string (`a=1; b=2`) of the current request or document. */\n raw(): string {\n if (this.isServer) {\n return this.request?.headers.get('cookie') ?? '';\n }\n try {\n return this.document.cookie ?? '';\n } catch {\n return '';\n }\n }\n\n /** First decoded value of `name`, or `null`. */\n get(name: string): string | null {\n return this.getAll(name)[0] ?? null;\n }\n\n /**\n * Every decoded value of `name`, in header order. A host-only cookie and a domain-scoped twin can coexist; callers\n * that must pick the right one (the auth state) look at all of them.\n */\n getAll(name: string): string[] {\n return parseLtCookies(this.raw(), name);\n }\n\n /** Write a cookie (browser only). The value is URI-encoded. */\n set(name: string, value: string, options: LtCookieOptions = {}): void {\n if (this.isServer) {\n return;\n }\n const secure = options.secure ?? this.document.location?.protocol === 'https:';\n const parts = [`${name}=${encodeURIComponent(value)}`, `path=${options.path ?? '/'}`, `samesite=${options.sameSite ?? 'lax'}`];\n if (options.maxAge !== undefined) {\n parts.push(`max-age=${Math.max(0, Math.floor(options.maxAge))}`);\n }\n if (options.domain) {\n parts.push(`domain=${options.domain}`);\n }\n if (secure) {\n parts.push('secure');\n }\n this.document.cookie = parts.join('; ');\n }\n\n /** Delete a cookie on the given path/domain slot (browser only). */\n delete(name: string, options: Pick<LtCookieOptions, 'domain' | 'path'> = {}): void {\n if (this.isServer) {\n return;\n }\n const parts = [`${name}=`, `path=${options.path ?? '/'}`, 'expires=Thu, 01 Jan 1970 00:00:00 GMT', 'max-age=0'];\n if (options.domain) {\n parts.push(`domain=${options.domain}`);\n }\n this.document.cookie = parts.join('; ');\n }\n}\n\n/** Parse every value of cookie `name` out of a raw cookie string (decoded; malformed encodings are kept raw). */\nexport function parseLtCookies(cookieString: string, name: string): string[] {\n const prefix = `${name}=`;\n const values: string[] = [];\n for (const entry of (cookieString || '').split(/;\\s*/)) {\n if (!entry.startsWith(prefix)) {\n continue;\n }\n const value = entry.slice(prefix.length);\n try {\n values.push(decodeURIComponent(value));\n } catch {\n values.push(value);\n }\n }\n return values;\n}\n","/**\n * SHA-256 of a string as lowercase hex (WebCrypto).\n *\n * nest-server expects every password to arrive SHA-256 hashed (it re-hashes server-side). The auth service applies\n * this to each credential field; use it yourself only for custom endpoints that follow the same rule. Because the\n * hash hides the length, enforce password rules in the form, not on the server.\n */\nexport async function ltSha256(message: string): Promise<string> {\n const data = new TextEncoder().encode(message);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/** Encode bytes as base64url without padding (WebAuthn). */\nexport function ltArrayBufferToBase64Url(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** Decode base64url (with or without padding) into bytes (WebAuthn). */\nexport function ltBase64UrlToUint8Array(base64url: string): Uint8Array<ArrayBuffer> {\n const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(new ArrayBuffer(binary.length));\n for (let index = 0; index < binary.length; index++) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n}\n","import { InjectionToken } from '@angular/core';\n\n/** A `fetch`-compatible function. */\nexport type LtFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * The fetch used by every library call that does not go through `HttpClient`: error translation, system setup,\n * AI (incl. the SSE stream), offline sync and its hint stream, the Better Auth client.\n *\n * The default sends cookies (`credentials: 'include'`). `provideLtAuth()` replaces it with the auth-aware fetch\n * (Bearer token in JWT mode, cookie → JWT switch on 401, stale-JWT refresh, session-loss handling), so SSE and sync\n * get the same auth behaviour as `HttpClient` calls behind `ltAuthInterceptor`.\n */\nexport const LT_FETCH = new InjectionToken<LtFetch>('LT_FETCH', {\n factory: () => ltCredentialsFetch,\n});\n\n/** `fetch` with `credentials: 'include'` unless the caller decided otherwise. */\nexport const ltCredentialsFetch: LtFetch = (input, init) => fetch(input, { credentials: 'include', ...init });\n","","","import { isPlatformBrowser } from '@angular/common';\nimport { computed, inject, InjectionToken, PLATFORM_ID, Service, signal, type Signal } from '@angular/core';\n\nimport de from './i18n/de.json';\nimport en from './i18n/en.json';\n\n/** Nested message tree as in the shipped `i18n/de.json` / `i18n/en.json`. */\nexport interface LtI18nMessages {\n [key: string]: LtI18nMessages | string;\n}\n\n/**\n * Bridge to the app's translation library (Transloco, ngx-translate, …).\n *\n * Bind it when the app already has an i18n setup, so library texts follow the app language and can be overridden\n * in the app's own translation files (keys `lt.error.*` of the core and `lt.<entry>.*` of the entry points: `lt.auth.*`,\n * `lt.files.*`, `lt.share.*`, `lt.ui.*`, `lt.bug.*`; shipped as `i18n/{de,en}.json` and `i18n/<entry>/{de,en}.json`).\n *\n * @example Transloco\n * { provide: LT_I18N_ADAPTER, useFactory: () => {\n * const transloco = inject(TranslocoService);\n * const lang = toSignal(transloco.langChanges$, { initialValue: transloco.getActiveLang() });\n * return { locale: lang, translate: (key, params) => transloco.translate(key, params) };\n * } }\n */\nexport interface LtI18nAdapter {\n /** Active locale (`de`, `en`, …). */\n readonly locale: Signal<string>;\n /** Translate a key; return `null` (or the key itself) when the app has no translation, then the library text is used. */\n translate(key: string, params?: Record<string, unknown>): string | null;\n}\n\n/** Optional adapter to the app's i18n library. */\nexport const LT_I18N_ADAPTER = new InjectionToken<LtI18nAdapter>('LT_I18N_ADAPTER');\n\n/** Options of the built-in i18n (part of `provideLtNgBase({ i18n })`). */\nexport interface LtI18nOptions {\n /** Locale used when neither the adapter nor the browser language (de/en) decides. Default `de`. */\n defaultLocale?: string;\n /** Additional or overriding messages per locale, merged over the shipped de/en texts. */\n messages?: Record<string, LtI18nMessages>;\n}\n\n/** i18n options token (set by `provideLtNgBase`). */\nexport const LT_I18N_OPTIONS = new InjectionToken<LtI18nOptions>('LT_I18N_OPTIONS', { factory: () => ({}) });\n\n/** The core's shipped messages (`lt.error.*`); the entry points register theirs with `registerLibraryMessages()`. */\nexport const LT_I18N_MESSAGES: Readonly<Record<'de' | 'en', LtI18nMessages>> = { de, en };\n\n/**\n * Signal-based translations for library texts, with an optional adapter to the app's i18n library.\n *\n * Lookup order for `t(key)`: adapter → messages of the active locale → German messages → the key.\n * Locale order: adapter → `setLocale()` → browser language (de/en, browser only) → `defaultLocale` → `de`.\n */\n@Service()\nexport class LtI18nService {\n private readonly adapter = inject(LT_I18N_ADAPTER, { optional: true });\n private readonly options = inject(LT_I18N_OPTIONS);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly explicitLocale = signal<string | null>(null);\n private readonly extraMessages = signal<Record<string, LtI18nMessages>>(this.options.messages ?? {});\n /**\n * Texts of the entry points (`lt.ui.*`, `lt.auth.*` …) registered by {@link registerLibraryMessages}. A plain object:\n * every entry registers its texts before its own components read them, so nothing has to react to a registration.\n */\n private readonly libraryMessages: Record<string, LtI18nMessages> = {};\n private readonly libraryIds = new Set<string>();\n\n /** Active locale. */\n readonly locale = computed(() => this.adapter?.locale() || this.explicitLocale() || this.detectBrowserLocale() || this.options.defaultLocale || 'de');\n\n /** Set the locale (ignored for lookups while an adapter provides one). */\n setLocale(locale: string): void {\n this.explicitLocale.set(locale);\n }\n\n /** Merge messages for a locale over the shipped ones. */\n registerMessages(locale: string, messages: LtI18nMessages): void {\n this.extraMessages.update((current) => ({ ...current, [locale]: deepMerge(current[locale] ?? {}, messages) }));\n }\n\n /**\n * Register the default texts of an entry point once per `id` (`ui`, `auth`, `files` …). They rank below the app's\n * own texts (`provideLtNgBase({ i18n: { messages } })`, `registerMessages()`, the adapter), like the shipped core\n * texts. Entry points call it when they are first used (synchronously, on the server and in the browser alike), so\n * their texts travel with their code instead of the initial bundle.\n */\n registerLibraryMessages(id: string, messages: Readonly<Record<string, LtI18nMessages>>): void {\n if (this.libraryIds.has(id)) {\n return;\n }\n this.libraryIds.add(id);\n for (const [locale, tree] of Object.entries(messages)) {\n this.libraryMessages[locale] = deepMerge(this.libraryMessages[locale] ?? {}, tree);\n }\n }\n\n /** Translate `key`, interpolating `{name}` placeholders from `params`. */\n t(key: string, params?: Record<string, unknown>): string {\n const fromAdapter = this.adapter?.translate(key, params);\n if (fromAdapter && fromAdapter !== key) {\n return fromAdapter;\n }\n const locale = this.locale();\n const message =\n lookup(this.extraMessages()[locale], key) ??\n lookup((LT_I18N_MESSAGES as Record<string, LtI18nMessages>)[locale], key) ??\n lookup(this.libraryMessages[locale], key) ??\n lookup(this.extraMessages()['de'], key) ??\n lookup(LT_I18N_MESSAGES.de, key) ??\n lookup(this.libraryMessages['de'], key) ??\n key;\n return interpolate(message, params);\n }\n\n private detectBrowserLocale(): string | null {\n if (!this.isBrowser || typeof navigator === 'undefined' || !navigator.language) {\n return null;\n }\n const language = navigator.language.split('-')[0]?.toLowerCase() ?? '';\n return language === 'de' || language === 'en' ? language : null;\n }\n}\n\nfunction lookup(messages: LtI18nMessages | undefined, key: string): string | null {\n let node: LtI18nMessages | string | undefined = messages;\n for (const part of key.split('.')) {\n if (!node || typeof node === 'string') {\n return null;\n }\n node = node[part];\n }\n return typeof node === 'string' ? node : null;\n}\n\nfunction interpolate(message: string, params?: Record<string, unknown>): string {\n if (!params) {\n return message;\n }\n return message.replace(/\\{(\\w+)\\}/g, (match, name: string) => (name in params ? String(params[name]) : match));\n}\n\nfunction deepMerge(target: LtI18nMessages, source: LtI18nMessages): LtI18nMessages {\n const result: LtI18nMessages = { ...target };\n for (const [key, value] of Object.entries(source)) {\n const current = result[key];\n result[key] = typeof value === 'object' && typeof current === 'object' ? deepMerge(current, value) : value;\n }\n return result;\n}\n","import { inject, InjectionToken, Service } from '@angular/core';\n\n/** Toast colors (same set as Nuxt UI). */\nexport type LtToastColor = 'error' | 'info' | 'neutral' | 'primary' | 'secondary' | 'success' | 'warning';\n\n/** A toast message. */\nexport interface LtToastOptions {\n color?: LtToastColor;\n description?: string;\n /** Display time in milliseconds (adapter default when omitted). */\n duration?: number;\n title?: string;\n}\n\n/**\n * Bridge to the app's toaster. `provideLtUi()` binds it to Spartan Sonner (`<hlm-toaster />`); bind your own for\n * another UI library. Return `false` when nothing could be shown, the caller then logs to the console.\n */\nexport interface LtToastAdapter {\n show(options: LtToastOptions): boolean;\n}\n\n/** Optional toaster bridge (see {@link LtToastAdapter}). */\nexport const LT_TOAST_ADAPTER = new InjectionToken<LtToastAdapter>('LT_TOAST_ADAPTER');\n\n/** Shows toasts through {@link LT_TOAST_ADAPTER}; without an adapter, `show` returns `false`. */\n@Service()\nexport class LtToastService {\n private readonly adapter = inject(LT_TOAST_ADAPTER, { optional: true });\n\n /** Show a toast. Returns `false` when no toaster is bound or it threw. */\n show(options: LtToastOptions): boolean {\n if (!this.adapter) {\n return false;\n }\n try {\n return this.adapter.show(options) !== false;\n } catch {\n return false;\n }\n }\n\n /** Shortcut for a success toast. */\n success(title: string, description?: string): boolean {\n return this.show({ color: 'success', description, title });\n }\n\n /** Shortcut for an error toast. */\n error(title: string, description?: string): boolean {\n return this.show({ color: 'error', description, title });\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { computed, inject, InjectionToken, PLATFORM_ID, Service, signal } from '@angular/core';\n\nimport { LtApiUrlService } from './api-url';\nimport { LT_FETCH } from './fetch';\nimport { LtI18nService } from './i18n';\nimport { LtToastService } from './toast';\n\n/** Response of nest-server `GET /i18n/errors/:locale`. */\nexport interface LtErrorTranslationResponse {\n errors: Record<string, string>;\n}\n\n/** Result of {@link LtErrorTranslationService.parseError}. */\nexport interface LtParsedError {\n /** Error code such as `LTNS_0100`, or `null` when the message carries none. */\n code: string | null;\n /** Message as sent by the backend (without the `#CODE:` marker). */\n developerMessage: string;\n /** Translated message for the active locale; falls back to the developer message, never empty for a message-bearing error. */\n translatedMessage: string;\n}\n\n/** Options of the error translation (part of `provideLtNgBase({ errorTranslation })`, runtime `NG_PUBLIC_LT_ERROR_TRANSLATION_*`). */\nexport interface LtErrorTranslationOptions {\n /** Locale used when i18n does not decide (default `de`). */\n defaultLocale?: string;\n /** Load and apply backend translations (default `true`). */\n enabled?: boolean;\n /** Load the translations for the detected locale while the app starts, browser only (default `true`). */\n preload?: boolean;\n}\n\n/** Resolved error translation options. */\nexport const LT_ERROR_TRANSLATION_OPTIONS = new InjectionToken<Required<LtErrorTranslationOptions>>('LT_ERROR_TRANSLATION_OPTIONS', {\n factory: () => ({ defaultLocale: 'de', enabled: true, preload: true }),\n});\n\nconst ERROR_CODE_REGEX = /^#([A-Z_]+_\\d+):\\s*(.+)$/s;\n\n/** A translation load that takes longer is aborted (the next call retries). */\nexport const LT_ERROR_TRANSLATION_TIMEOUT_MS = 10_000;\n\n/**\n * Translates nest-server error messages of the form `#LTNS_0100: Developer message` with the backend's\n * `GET /i18n/errors/:locale` table.\n */\n@Service()\nexport class LtErrorTranslationService {\n private readonly api = inject(LtApiUrlService);\n private readonly fetch = inject(LT_FETCH);\n private readonly i18n = inject(LtI18nService);\n private readonly toast = inject(LtToastService);\n private readonly options = inject(LT_ERROR_TRANSLATION_OPTIONS);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly translations = signal<Record<string, Record<string, string>>>({});\n private readonly inFlight = new Map<string, Promise<void>>();\n\n /** Locale used for lookups. */\n readonly currentLocale = computed(() => this.i18n.locale() || this.options.defaultLocale || 'de');\n /** Whether translations for the current locale are loaded. */\n readonly isLoaded = computed(() => !!this.translations()[this.currentLocale()]);\n /** Whether a load is running. */\n readonly isLoading = signal(false);\n\n /** Load the backend translations for `locale` (default: current locale). Cached per locale, one request in flight per locale. */\n async loadTranslations(locale?: string): Promise<void> {\n if (!this.options.enabled) {\n return;\n }\n const target = locale || this.currentLocale();\n if (this.translations()[target]) {\n return;\n }\n const running = this.inFlight.get(target);\n if (running) {\n return running;\n }\n const load = (async () => {\n this.isLoading.set(true);\n try {\n const response = await this.fetch(this.api.buildUrl(`/i18n/errors/${encodeURIComponent(target)}`), {\n headers: { accept: 'application/json' },\n signal: AbortSignal.timeout(LT_ERROR_TRANSLATION_TIMEOUT_MS),\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const body = (await response.json()) as Partial<LtErrorTranslationResponse> | null;\n if (body?.errors && typeof body.errors === 'object') {\n this.translations.update((current) => ({ ...current, [target]: body.errors as Record<string, string> }));\n }\n } catch (error) {\n console.warn(`[@lenne.tech/ng-base] Failed to load error translations for ${target}:`, error);\n } finally {\n this.inFlight.delete(target);\n this.isLoading.set(this.inFlight.size > 0);\n }\n })();\n this.inFlight.set(target, load);\n return load;\n }\n\n /** Parse any error shape (string, `Error`, `HttpErrorResponse`, response body) into code + messages. */\n parseError(errorOrMessage: unknown): LtParsedError {\n const message = extractLtErrorMessage(errorOrMessage);\n const match = message.match(ERROR_CODE_REGEX);\n if (!match) {\n return { code: null, developerMessage: message, translatedMessage: message };\n }\n const code = match[1] ?? '';\n const developerMessage = (match[2] ?? '').trim();\n const translatedMessage = this.translations()[this.currentLocale()]?.[code] || developerMessage;\n return { code, developerMessage, translatedMessage };\n }\n\n /** Translated message for an error (never empty for a message-bearing error). */\n translateError(errorOrMessage: unknown): string {\n return this.parseError(errorOrMessage).translatedMessage;\n }\n\n /** Show the translated error as an error toast (browser only); logs to the console when no toaster is bound. */\n showErrorToast(errorOrMessage: unknown, title?: string): void {\n if (!this.isBrowser) {\n return;\n }\n const parsed = this.parseError(errorOrMessage);\n const shown = this.toast.show({ color: 'error', description: parsed.translatedMessage, title: title || this.i18n.t('lt.error.title') });\n if (!shown) {\n console.error('[@lenne.tech/ng-base]', parsed.translatedMessage);\n }\n }\n}\n\n/**\n * Extract the human-relevant message from an error.\n *\n * The response body wins over a transport message: `HttpErrorResponse.message` is\n * `Http failure response for …: 400 Bad Request`, while the backend message (the only one that can carry a\n * `#LTNS_` marker) is in `error.message`. Better Auth errors (`{ code, message }`) and arrays of messages\n * (class-validator) are handled too.\n */\nexport function extractLtErrorMessage(errorOrMessage: unknown): string {\n if (typeof errorOrMessage === 'string') {\n return errorOrMessage;\n }\n if (errorOrMessage === null || errorOrMessage === undefined) {\n return '';\n }\n if (typeof errorOrMessage !== 'object') {\n return String(errorOrMessage);\n }\n const source = errorOrMessage as Record<string, unknown>;\n for (const candidate of [source['error'], source['data'], source['body']]) {\n const fromBody = messageOf(candidate);\n if (fromBody) {\n return fromBody;\n }\n }\n return messageOf(source) || (typeof source['statusText'] === 'string' && source['statusText']) || String(errorOrMessage);\n}\n\nfunction messageOf(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n if (!value || typeof value !== 'object') {\n return '';\n }\n const message = (value as Record<string, unknown>)['message'];\n if (typeof message === 'string') {\n return message;\n }\n if (Array.isArray(message)) {\n return message.filter((part) => typeof part === 'string').join(', ');\n }\n const nested = (value as Record<string, unknown>)['error'];\n if (nested && typeof nested === 'object') {\n return messageOf(nested);\n }\n return '';\n}\n","/** Version of `@lenne.tech/ng-base`; kept in sync with `package.json` by `scripts/sync-version.mjs`. */\nexport const LT_NG_BASE_VERSION = '22.0.2';\n","import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { ApplicationRef, inject, makeEnvironmentProviders, PLATFORM_ID, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\n\nimport { LtApiUrlService } from './api-url';\nimport { LT_ERROR_TRANSLATION_OPTIONS, LtErrorTranslationService, type LtErrorTranslationOptions } from './error-translation';\nimport { LT_I18N_OPTIONS, type LtI18nOptions } from './i18n';\nimport { provideLtPreHydrationInput, type LtPreHydrationInputOptions } from './pre-hydration-input';\nimport {\n LT_RUNTIME_CONFIG,\n LT_RUNTIME_CONFIG_DEFAULTS,\n LT_SERVER_RUNTIME_CONFIG,\n mergeLtFeatureOptions,\n mergeLtRuntimeConfig,\n readLtRuntimeConfigFromDocument,\n type LtRuntimeConfig,\n} from './runtime-config';\nimport { LT_NG_BASE_VERSION } from './version';\n\n/** Options of {@link provideLtNgBase}. */\nexport interface LtNgBaseConfig {\n /**\n * Check at startup that an API URL is configured for the current scope (default `true`): a warning in dev mode, a\n * console error in production. Turn it off for apps without a backend (ng-base/ui only).\n */\n configCheck?: boolean;\n /** Backend error translation (`#LTNS_xxxx`). Turn it off (`enabled: false`) for apps without nest-server. */\n errorTranslation?: LtErrorTranslationOptions;\n /** Library i18n (locale, extra messages). Bind `LT_I18N_ADAPTER` to use the app's i18n library. */\n i18n?: LtI18nOptions;\n /** Keep what users type into server-rendered fields before hydration (default on, see `provideLtPreHydrationInput`). */\n preHydrationInput?: LtPreHydrationInputOptions;\n /**\n * Static runtime configuration: the base that the per-request values of the SSR server (`NG_*` via\n * `readLtRequestContext()`, `<script id=\"ng-runtime-config\">` in the browser) override. Use it for apps without SSR,\n * tests, or defaults.\n */\n runtime?: Partial<LtRuntimeConfig>;\n}\n\n/** Name of the window flag set once the app is stable (hydrated) in the browser; read by `waitForHydration()`. */\nexport const LT_HYDRATED_FLAG = '__ltNgBaseHydrated';\n\n/**\n * Core providers of `@lenne.tech/ng-base`.\n *\n * - {@link LT_RUNTIME_CONFIG}: defaults ← `config.runtime` ← request context (server) / `<script id=\"ng-runtime-config\">` (browser).\n * - Error translation options and preload of the backend translations (browser only, in the background: the start\n * never waits for the API; until they arrive errors show the developer message).\n * - i18n options.\n * - API URL config check.\n * - Pre-hydration input preservation (typed text survives hydration).\n * - Hydration flag `window.__ltNgBaseHydrated` for the Playwright helpers.\n *\n * Feature providers live in the secondary entry points: `provideLtAuth()` (`/auth`), `provideLtFiles()` (`/files`),\n * `provideLtAi()` (`/ai`), `provideLtSync()` (`/sync`), `provideLtUi()` (`/ui`), `provideLtServerRuntimeConfig()` (`/server`).\n *\n * @example app.config.ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([ltAuthInterceptor])),\n * provideLtNgBase(),\n * provideLtAuth({ systemSetup: { enabled: true } }),\n * provideLtUi(),\n * ],\n * };\n */\nexport function provideLtNgBase(config: LtNgBaseConfig = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: LT_RUNTIME_CONFIG,\n useFactory: () => {\n const base = mergeLtRuntimeConfig(LT_RUNTIME_CONFIG_DEFAULTS, config.runtime);\n if (!isPlatformBrowser(inject(PLATFORM_ID))) {\n return mergeLtRuntimeConfig(base, inject(LT_SERVER_RUNTIME_CONFIG, { optional: true })?.public);\n }\n return mergeLtRuntimeConfig(base, readLtRuntimeConfigFromDocument(inject(DOCUMENT)));\n },\n },\n {\n provide: LT_ERROR_TRANSLATION_OPTIONS,\n useFactory: () =>\n mergeLtFeatureOptions<Required<LtErrorTranslationOptions>>(\n { defaultLocale: config.i18n?.defaultLocale ?? 'de', enabled: true, preload: true },\n config.errorTranslation,\n inject(LT_RUNTIME_CONFIG).lt.errorTranslation,\n ),\n },\n { provide: LT_I18N_OPTIONS, useValue: config.i18n ?? {} },\n provideLtPreHydrationInput(config.preHydrationInput),\n provideAppInitializer(() => {\n if (config.configCheck !== false) {\n inject(LtApiUrlService).reportMissing();\n }\n }),\n provideAppInitializer(() => {\n const options = inject(LT_ERROR_TRANSLATION_OPTIONS);\n if (!isPlatformBrowser(inject(PLATFORM_ID)) || !options.enabled || !options.preload) {\n return;\n }\n // Not awaited: a slow or hanging API must never hold the app start (the translations are a signal).\n void inject(LtErrorTranslationService).loadTranslations();\n }),\n provideAppInitializer(() => {\n if (!isPlatformBrowser(inject(PLATFORM_ID))) {\n return;\n }\n void inject(ApplicationRef)\n .whenStable()\n .then(() => {\n (globalThis as Record<string, unknown>)[LT_HYDRATED_FLAG] = true;\n (globalThis as Record<string, unknown>)['__ltNgBaseVersion'] = LT_NG_BASE_VERSION;\n });\n }),\n ]);\n}\n","/**\n * Identity function and tagged template for Tailwind class strings outside templates, so the Tailwind IntelliSense\n * extension recognises them (configure `\"tailwindCSS.experimental.classRegex\": [\"tw`([^`]*)`\", \"tw\\\\(['\\\"]([^'\\\"]*)\"]`).\n *\n * @example\n * const card = tw`rounded-lg border p-4`;\n * const active = tw('bg-primary text-primary-foreground');\n */\nexport function tw(strings: TemplateStringsArray | string, ...values: unknown[]): string {\n if (typeof strings === 'string') {\n return strings;\n }\n return strings.reduce((result, part, index) => result + part + (index < values.length ? String(values[index]) : ''), '');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lenne.tech-ng-base.mjs","names":[],"sources":["../../../projects/ng-base/src/lib/warn-once.ts","../../../projects/ng-base/src/lib/pre-hydration-input.ts","../../../projects/ng-base/src/lib/runtime-config.ts","../../../projects/ng-base/src/lib/api-url.ts","../../../projects/ng-base/src/lib/cookie.ts","../../../projects/ng-base/src/lib/crypto.ts","../../../projects/ng-base/src/lib/fetch.ts","../tmp-esm2022/lib/i18n/de.json","../tmp-esm2022/lib/i18n/en.json","../../../projects/ng-base/src/lib/i18n.ts","../../../projects/ng-base/src/lib/toast.ts","../../../projects/ng-base/src/lib/error-translation.ts","../../../projects/ng-base/src/lib/version.ts","../../../projects/ng-base/src/lib/provide-lt-ng-base.ts","../../../projects/ng-base/src/lib/tw.ts"],"sourcesContent":["const warnedKeys = new Set<string>();\n\n/**\n * Log `message` the first time `key` is seen; ignore later calls. The conditions behind these messages (missing API\n * URL, implicit dev proxy) are constant for the process, so repeating them only drowns other output.\n */\nexport function warnLtOnce(key: string, message: string, level: 'error' | 'warn' = 'warn'): void {\n if (warnedKeys.has(key)) {\n return;\n }\n warnedKeys.add(key);\n console[level](message);\n}\n\n/** Test only: forget which one-shot messages were emitted. */\nexport function resetLtWarnOnceState(): void {\n warnedKeys.clear();\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { ApplicationRef, inject, PLATFORM_ID, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\n\n/** Options of the pre-hydration input preservation (`provideLtNgBase({ preHydrationInput })`). */\nexport interface LtPreHydrationInputOptions {\n /** Keep what users type before hydration (default `true`). */\n enabled?: boolean;\n /**\n * How long after hydration to keep restoring, in milliseconds (default `1500`; `0` restores once). Clamped to 30000.\n * When it elapses the snapshot is dropped, so preserved values (passwords included) are not kept for the session.\n */\n maxRestoreMs?: number;\n}\n\ntype LtEditableField = HTMLInputElement | HTMLTextAreaElement;\n\n/** Every `input`/`textarea` under `root` whose value differs from its server-rendered default, with that value. */\nexport function collectLtEditedFields(root: ParentNode): Map<LtEditableField, string> {\n const edited = new Map<LtEditableField, string>();\n for (const node of Array.from(root.querySelectorAll('input, textarea'))) {\n const field = node as LtEditableField;\n if (field.value !== field.defaultValue) {\n edited.set(field, field.value);\n }\n }\n return edited;\n}\n\n/**\n * Write `value` back into `field` and dispatch a bubbling `input` event, so `(input)` handlers, `model()` and Signal\n * Forms adopt it. Returns `false` when the field is detached or already holds the value.\n */\nexport function restoreLtField(field: LtEditableField, value: string): boolean {\n if (!field.isConnected || field.value === value) {\n return false;\n }\n field.value = value;\n field.dispatchEvent(new Event('input', { bubbles: true }));\n return true;\n}\n\n/**\n * Pre-hydration input preservation (browser only).\n *\n * Text typed into a server-rendered field before hydration is in the DOM, but the first change detection after\n * hydration writes the component's (empty) value over it — `[value]` bindings and Signal Forms do — before the replayed\n * events run, so `withEventReplay()` alone loses it (measured in the ng-base playground E2E). The fields are snapshot\n * at app initialisation (the SSR DOM is reused by hydration, so the element references stay valid) and restored once\n * the app is stable, then every 250 ms for `maxRestoreMs` for fields hydrated late; fields stay ordinary editable\n * fields throughout (autofill and screen readers untouched).\n */\nexport function provideLtPreHydrationInput(options: LtPreHydrationInputOptions = {}): EnvironmentProviders {\n return provideAppInitializer(() => {\n if (options.enabled === false || !isPlatformBrowser(inject(PLATFORM_ID)) || typeof document === 'undefined') {\n return;\n }\n const edited = collectLtEditedFields(document);\n if (edited.size === 0) {\n return;\n }\n const configured = options.maxRestoreMs ?? 1500;\n const maxRestoreMs = Number.isFinite(configured) && configured > 0 ? Math.min(configured, 30_000) : 0;\n const sweep = (): void => {\n for (const [field, value] of edited) {\n if (restoreLtField(field, value) || !field.isConnected) {\n edited.delete(field);\n }\n }\n };\n void inject(ApplicationRef)\n .whenStable()\n .then(() => {\n sweep();\n if (maxRestoreMs <= 0 || edited.size === 0) {\n edited.clear();\n return;\n }\n const interval = setInterval(sweep, 250);\n setTimeout(() => {\n clearInterval(interval);\n edited.clear();\n }, maxRestoreMs);\n });\n });\n}\n\n/**\n * Inline script (for `<head>`) that blocks the native submission of forms without `action` until the app handles them.\n * A server-rendered login form submitted before hydration would otherwise send a native GET with the password in the\n * URL. `ltAngularRequestHandler()` writes it into every SSR HTML response; add it to `index.html` yourself without SSR.\n */\nexport const LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT =\n \"document.addEventListener('submit',function(e){var f=e.target;if(f instanceof HTMLFormElement&&!f.hasAttribute('action')){e.preventDefault();}},true);\";\n","import { inject, InjectionToken, REQUEST_CONTEXT } from '@angular/core';\n\nimport { LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT } from './pre-hydration-input';\n\n/**\n * Feature options that can be overridden per stage at runtime (`NG_PUBLIC_LT_<FEATURE>_<OPTION>`).\n *\n * Each feature provider (`provideLtAuth`, `provideLtFiles`, `provideLtAi`, `provideLtSync`, `provideLtNgBase`)\n * merges, in this order: its defaults, the options passed in code, and the values found here.\n */\nexport interface LtPublicFeatureConfig {\n ai?: Record<string, unknown>;\n auth?: Record<string, unknown>;\n errorTranslation?: Record<string, unknown>;\n sync?: Record<string, unknown>;\n tus?: Record<string, unknown>;\n [feature: string]: Record<string, unknown> | undefined;\n}\n\n/**\n * Public runtime configuration of the app, the Angular counterpart of Nuxt's `runtimeConfig.public`.\n *\n * With SSR the server reads it per request from the `NG_*` environment (`readLtRequestContext()` in\n * `@lenne.tech/ng-base/server`), passes it to `AngularNodeAppEngine.handle(req, context)` as request context\n * (`REQUEST_CONTEXT`) and writes it as JSON `<script id=\"ng-runtime-config\">` into every HTML response, also for\n * `RenderMode.Client` routes; the browser reads that script. One build can therefore be promoted from stage to stage.\n * Without SSR the values come from `provideLtNgBase({ runtime })`.\n *\n * Projects may carry additional public values (e.g. `plausibleApiHost`); they travel the same way.\n * Never put a secret here: everything in this object reaches the browser.\n */\nexport interface LtRuntimeConfig {\n [key: string]: unknown;\n /** Commit of the running build (`NG_PUBLIC_APP_COMMIT`, fallback `APP_VERSION_COMMIT`), `unknown` when unset. */\n appCommit?: string;\n /**\n * Deployment stage (`NG_PUBLIC_APP_ENV`), using nest-server's NODE_ENV block names: `local`, `ci`, `e2e`, `develop`,\n * `test`, `production`. Only `production` counts as production; features that are off in production fail closed.\n */\n appEnv?: string;\n /** Version of the running build (from the server, e.g. package.json). */\n appVersion?: string;\n /** Public API base URL (`NG_PUBLIC_API_URL`), e.g. `https://api.example.com`. Empty = relative to the app origin. */\n apiUrl: string;\n /**\n * Route browser API calls through the dev-server proxy under `/api` (`NG_PUBLIC_API_PROXY`).\n * `null` = not configured: the proxy is used in dev mode (`isDevMode()`) with a one-time warning.\n * Never used during SSR.\n */\n apiProxy: boolean | null;\n /** Opt-in cookie namespace (`NG_PUBLIC_COOKIE_PREFIX`); must mirror the backend `COOKIE_PREFIX`. */\n cookiePrefix: string;\n /** Feature options overridden at runtime (`NG_PUBLIC_LT_*`). */\n lt: LtPublicFeatureConfig;\n /** Absolute public URL of the app (`NG_PUBLIC_SITE_URL`), used for redirect/callback URLs, SEO and security.txt. */\n siteUrl: string;\n /** Prefix for `localStorage` keys (`NG_PUBLIC_STORAGE_PREFIX`). */\n storagePrefix: string;\n}\n\n/** Defaults of {@link LtRuntimeConfig}. */\nexport const LT_RUNTIME_CONFIG_DEFAULTS: LtRuntimeConfig = {\n apiProxy: null,\n apiUrl: '',\n cookiePrefix: '',\n lt: {},\n siteUrl: '',\n storagePrefix: '',\n};\n\n/** The resolved public runtime configuration (defaults ← `provideLtNgBase({ runtime })` ← request context / `<script id=\"ng-runtime-config\">`). */\nexport const LT_RUNTIME_CONFIG = new InjectionToken<LtRuntimeConfig>('LT_RUNTIME_CONFIG');\n\n/** What the SSR server passes to `AngularNodeAppEngine.handle(req, context)` (see `readLtRequestContext()`). */\nexport interface LtRequestContext {\n /** Public runtime configuration (also written into the HTML as `<script id=\"ng-runtime-config\">`). */\n runtimeConfig: Partial<LtRuntimeConfig>;\n /** Internal API URL for SSR requests only (`NG_API_URL`), never sent to the browser. */\n serverApiUrl?: string;\n}\n\n/**\n * Runtime configuration on the server: public part plus the server-only API URL. Read from `REQUEST_CONTEXT`\n * (an {@link LtRequestContext}) by default; `provideLtServerRuntimeConfig()` provides it from the environment instead\n * for servers that pass no request context. `null` in the browser.\n */\nexport interface LtServerRuntimeConfig {\n /** Public part. */\n public: Partial<LtRuntimeConfig>;\n /** Internal API URL for SSR requests only (`NG_API_URL`), never sent to the browser. */\n serverApiUrl: string;\n}\n\n/** Server-only runtime configuration (see {@link LtServerRuntimeConfig}). `null` in the browser. */\nexport const LT_SERVER_RUNTIME_CONFIG = new InjectionToken<LtServerRuntimeConfig | null>('LT_SERVER_RUNTIME_CONFIG', {\n factory: () => {\n const context = inject(REQUEST_CONTEXT, { optional: true }) as Partial<LtRequestContext> | null;\n if (!context || typeof context !== 'object' || !context.runtimeConfig) {\n return null;\n }\n return { public: context.runtimeConfig, serverApiUrl: context.serverApiUrl ?? '' };\n },\n});\n\n/** Id of the JSON `<script>` that carries the public runtime configuration into the browser. */\nexport const LT_RUNTIME_CONFIG_SCRIPT_ID = 'ng-runtime-config';\n\n/** Read the runtime configuration the server wrote into the document (`null` when missing or unreadable). */\nexport function readLtRuntimeConfigFromDocument(doc: Document | null | undefined): Partial<LtRuntimeConfig> | null {\n const text = doc?.getElementById?.(LT_RUNTIME_CONFIG_SCRIPT_ID)?.textContent;\n if (!text) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(text);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Partial<LtRuntimeConfig>) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Serialise the public runtime configuration for an inline `<script type=\"application/json\">`: `<`, `>`, `&`, U+2028\n * and U+2029 are escaped, so a value can never close the script element, open a comment or form an HTML entity.\n */\nexport function serializeLtRuntimeConfig(config: Partial<LtRuntimeConfig>): string {\n return JSON.stringify(config)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\n/**\n * Insert the runtime configuration as the first child of `<head>` (documents without `<head>` stay untouched), by\n * default preceded by the pre-hydration submit guard (`LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT`).\n */\nexport function injectLtRuntimeConfigScript(html: string, config: Partial<LtRuntimeConfig>, options: { submitGuard?: boolean } = {}): string {\n const head = /<head(\\s[^>]*)?>/i.exec(html);\n if (!head) {\n return html;\n }\n const at = head.index + head[0].length;\n const guard = options.submitGuard === false ? '' : `<script>${LT_PRE_HYDRATION_SUBMIT_GUARD_SCRIPT}</script>`;\n return `${html.slice(0, at)}${guard}<script id=\"${LT_RUNTIME_CONFIG_SCRIPT_ID}\" type=\"application/json\">${serializeLtRuntimeConfig(config)}</script>${html.slice(at)}`;\n}\n\n/**\n * Merge partial runtime configurations onto a base. `lt` is merged per feature; `undefined` values never\n * override (an unset env variable keeps the code default).\n */\nexport function mergeLtRuntimeConfig(base: LtRuntimeConfig, ...overrides: (Partial<LtRuntimeConfig> | null | undefined)[]): LtRuntimeConfig {\n const result: LtRuntimeConfig = { ...base, lt: { ...base.lt } };\n for (const override of overrides) {\n if (!override) {\n continue;\n }\n for (const [key, value] of Object.entries(override) as [keyof LtRuntimeConfig, unknown][]) {\n if (value === undefined) {\n continue;\n }\n if (key === 'lt') {\n for (const [feature, options] of Object.entries((value ?? {}) as LtPublicFeatureConfig)) {\n result.lt[feature] = { ...result.lt[feature], ...definedEntries(options ?? {}) };\n }\n continue;\n }\n (result as unknown as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Merge feature options: defaults ← code options ← runtime overrides. Nested plain objects are merged one level\n * deep (e.g. `auth.interceptor`), arrays and primitives replace. `undefined` never overrides.\n */\nexport function mergeLtFeatureOptions<T extends object>(defaults: T, ...overrides: (object | null | undefined)[]): T {\n const result: Record<string, unknown> = { ...(defaults as Record<string, unknown>) };\n for (const override of overrides) {\n if (!override) {\n continue;\n }\n for (const [key, value] of Object.entries(override)) {\n if (value === undefined) {\n continue;\n }\n const current = result[key];\n result[key] = isPlainObject(current) && isPlainObject(value) ? { ...current, ...definedEntries(value) } : value;\n }\n }\n return result as T;\n}\n\nfunction definedEntries(source: object): Record<string, unknown> {\n return Object.fromEntries(Object.entries(source).filter(([, value]) => value !== undefined));\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { isPlatformServer } from '@angular/common';\nimport { inject, isDevMode, PLATFORM_ID, Service } from '@angular/core';\n\nimport { warnLtOnce } from './warn-once';\nimport { LT_RUNTIME_CONFIG, LT_SERVER_RUNTIME_CONFIG } from './runtime-config';\n\n/** Render scope an API URL is resolved for. */\nexport type LtRenderScope = 'browser' | 'server';\n\n/** Outcome of resolving the API base URL for the current render scope. */\nexport interface LtApiUrlResolution {\n /** Base URL to prefix onto API paths: `'/api'` in proxy mode, `''` when unresolved. No trailing slash. */\n baseUrl: string;\n /** `true` when no API URL is configured for {@link scope}. */\n missing: boolean;\n /** `true` when the dev-server proxy prefix `/api` is in use. */\n proxy: boolean;\n /** Scope the resolution was performed for. */\n scope: LtRenderScope;\n}\n\n/** Inputs of {@link resolveLtApiBaseUrl}. */\nexport interface LtApiUrlInputs {\n /** `NG_PUBLIC_API_PROXY` (`null` = unset). */\n apiProxy: boolean | null;\n /** `NG_PUBLIC_API_URL`. */\n apiUrl: string;\n /** Whether the app runs in dev mode (`isDevMode()`), used when `apiProxy` is unset. */\n devMode: boolean;\n /** Render scope. */\n scope: LtRenderScope;\n /** `NG_API_URL` (server only). */\n serverApiUrl?: string;\n}\n\nconst MISSING_API_URL_WARNING: Record<LtRenderScope, string> = {\n browser:\n '[@lenne.tech/ng-base] No API URL configured. Set NG_PUBLIC_API_URL. API paths stay relative and resolve against the app ' +\n 'origin, so auth and setup calls 404 unless the app is served behind a same-origin reverse proxy.',\n server:\n '[@lenne.tech/ng-base] No API URL configured for SSR. Set NG_API_URL or NG_PUBLIC_API_URL. API paths stay relative, so ' + 'server-side requests never reach the backend.',\n};\n\n/**\n * Pure resolution of the API base URL (no DI, no logging).\n *\n * - Server: `NG_API_URL` → `NG_PUBLIC_API_URL` → `''`. The proxy is never used during SSR.\n * - Browser: `/api` when the proxy is on (explicitly, or implicitly in dev mode while unset), else `NG_PUBLIC_API_URL` → `''`.\n *\n * Trailing slashes are stripped so `${baseUrl}${path}` never produces a double slash.\n */\nexport function resolveLtApiBaseUrl(inputs: LtApiUrlInputs): LtApiUrlResolution {\n if (inputs.scope === 'server') {\n const apiUrl = inputs.serverApiUrl || inputs.apiUrl || '';\n return { baseUrl: stripTrailingSlashes(apiUrl), missing: !apiUrl, proxy: false, scope: 'server' };\n }\n if (inputs.apiProxy === true || (inputs.apiProxy === null && inputs.devMode)) {\n return { baseUrl: '/api', missing: false, proxy: true, scope: 'browser' };\n }\n const apiUrl = inputs.apiUrl || '';\n return { baseUrl: stripTrailingSlashes(apiUrl), missing: !apiUrl, proxy: false, scope: 'browser' };\n}\n\n/** Whether `url` is absolute (`scheme:` or protocol-relative `//`). */\nexport function isLtAbsoluteUrl(url: string): boolean {\n return /^[a-z][a-z\\d+\\-.]*:/i.test(url) || url.startsWith('//');\n}\n\n/**\n * The single URL builder used by auth, system setup, error translation, files, AI and sync.\n *\n * Reads {@link LT_RUNTIME_CONFIG} and, on the server, {@link LT_SERVER_RUNTIME_CONFIG}. A missing API URL is warned\n * about once per scope; an implicit dev proxy (unset `NG_PUBLIC_API_PROXY` in dev mode) once as well.\n */\n@Service()\nexport class LtApiUrlService {\n private readonly runtimeConfig = inject(LT_RUNTIME_CONFIG, { optional: true });\n private readonly serverConfig = inject(LT_SERVER_RUNTIME_CONFIG, { optional: true });\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /** Resolve the base URL for the current scope without logging. */\n resolve(): LtApiUrlResolution {\n return resolveLtApiBaseUrl({\n apiProxy: this.runtimeConfig?.apiProxy ?? null,\n apiUrl: this.runtimeConfig?.apiUrl ?? '',\n devMode: isDevMode(),\n scope: this.isServer ? 'server' : 'browser',\n serverApiUrl: this.serverConfig?.serverApiUrl,\n });\n }\n\n /** Base URL (no trailing slash) for the current scope. */\n baseUrl(): string {\n return this.resolveAndWarn().baseUrl;\n }\n\n /** Build a full API URL for a path such as `/iam/sign-in/email`. Absolute URLs are returned unchanged. */\n buildUrl(path: string): string {\n if (isLtAbsoluteUrl(path)) {\n return path;\n }\n const normalized = path.startsWith('/') ? path : `/${path}`;\n return `${this.resolveAndWarn().baseUrl}${normalized}`;\n }\n\n /** Whether browser requests go through the dev-server proxy under `/api`. Always `false` on the server. */\n isProxy(): boolean {\n return this.resolve().proxy;\n }\n\n /** Report a missing API URL (`dev`: warning, production: error), at most once per scope. Used by the config check. */\n reportMissing(): boolean {\n const resolution = this.resolve();\n if (resolution.missing) {\n warnLtOnce(`missing-api-url:${resolution.scope}`, MISSING_API_URL_WARNING[resolution.scope], isDevMode() ? 'warn' : 'error');\n }\n return resolution.missing;\n }\n\n private resolveAndWarn(): LtApiUrlResolution {\n const resolution = this.resolve();\n if (resolution.missing) {\n warnLtOnce(`missing-api-url:${resolution.scope}`, MISSING_API_URL_WARNING[resolution.scope]);\n }\n if (resolution.proxy && this.runtimeConfig?.apiProxy === null) {\n warnLtOnce(\n 'api-proxy-implicit',\n '[@lenne.tech/ng-base] API proxy (/api) activated implicitly because the app runs in dev mode. ' +\n 'Set NG_PUBLIC_API_PROXY=true to make it explicit, or NG_PUBLIC_API_PROXY=false to call the API directly.',\n );\n }\n return resolution;\n }\n}\n\nfunction stripTrailingSlashes(url: string): string {\n return url.replace(/\\/+$/, '');\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { inject, PLATFORM_ID, REQUEST, Service } from '@angular/core';\n\n/** Options for {@link LtCookieService.set}. */\nexport interface LtCookieOptions {\n /** Cookie domain. Omit for a host-only cookie. */\n domain?: string;\n /** Lifetime in seconds. Omit for a session cookie. */\n maxAge?: number;\n /** Cookie path (default `/`). */\n path?: string;\n /** SameSite attribute (default `lax`). */\n sameSite?: 'lax' | 'none' | 'strict';\n /** Secure attribute (default: `true` on `https:`). */\n secure?: boolean;\n}\n\n/**\n * SSR-aware cookie access.\n *\n * - Browser: reads and writes `document.cookie`.\n * - Server: reads the `Cookie` header of the incoming request (`REQUEST`); never writes. A server render must not\n * set auth cookies (it would write `{ user: null }` over a valid browser state), so `set`/`delete` are no-ops there.\n */\n@Service()\nexport class LtCookieService {\n private readonly document = inject(DOCUMENT);\n private readonly request = inject(REQUEST, { optional: true });\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /** Raw cookie string (`a=1; b=2`) of the current request or document. */\n raw(): string {\n if (this.isServer) {\n return this.request?.headers.get('cookie') ?? '';\n }\n try {\n return this.document.cookie ?? '';\n } catch {\n return '';\n }\n }\n\n /** First decoded value of `name`, or `null`. */\n get(name: string): string | null {\n return this.getAll(name)[0] ?? null;\n }\n\n /**\n * Every decoded value of `name`, in header order. A host-only cookie and a domain-scoped twin can coexist; callers\n * that must pick the right one (the auth state) look at all of them.\n */\n getAll(name: string): string[] {\n return parseLtCookies(this.raw(), name);\n }\n\n /** Write a cookie (browser only). The value is URI-encoded. */\n set(name: string, value: string, options: LtCookieOptions = {}): void {\n if (this.isServer) {\n return;\n }\n const secure = options.secure ?? this.document.location?.protocol === 'https:';\n const parts = [`${name}=${encodeURIComponent(value)}`, `path=${options.path ?? '/'}`, `samesite=${options.sameSite ?? 'lax'}`];\n if (options.maxAge !== undefined) {\n parts.push(`max-age=${Math.max(0, Math.floor(options.maxAge))}`);\n }\n if (options.domain) {\n parts.push(`domain=${options.domain}`);\n }\n if (secure) {\n parts.push('secure');\n }\n this.document.cookie = parts.join('; ');\n }\n\n /** Delete a cookie on the given path/domain slot (browser only). */\n delete(name: string, options: Pick<LtCookieOptions, 'domain' | 'path'> = {}): void {\n if (this.isServer) {\n return;\n }\n const parts = [`${name}=`, `path=${options.path ?? '/'}`, 'expires=Thu, 01 Jan 1970 00:00:00 GMT', 'max-age=0'];\n if (options.domain) {\n parts.push(`domain=${options.domain}`);\n }\n this.document.cookie = parts.join('; ');\n }\n}\n\n/** Parse every value of cookie `name` out of a raw cookie string (decoded; malformed encodings are kept raw). */\nexport function parseLtCookies(cookieString: string, name: string): string[] {\n const prefix = `${name}=`;\n const values: string[] = [];\n for (const entry of (cookieString || '').split(/;\\s*/)) {\n if (!entry.startsWith(prefix)) {\n continue;\n }\n const value = entry.slice(prefix.length);\n try {\n values.push(decodeURIComponent(value));\n } catch {\n values.push(value);\n }\n }\n return values;\n}\n","/**\n * SHA-256 of a string as lowercase hex (WebCrypto).\n *\n * nest-server expects every password to arrive SHA-256 hashed (it re-hashes server-side). The auth service applies\n * this to each credential field; use it yourself only for custom endpoints that follow the same rule. Because the\n * hash hides the length, enforce password rules in the form, not on the server.\n */\nexport async function ltSha256(message: string): Promise<string> {\n const data = new TextEncoder().encode(message);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/** Encode bytes as base64url without padding (WebAuthn). */\nexport function ltArrayBufferToBase64Url(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** Decode base64url (with or without padding) into bytes (WebAuthn). */\nexport function ltBase64UrlToUint8Array(base64url: string): Uint8Array<ArrayBuffer> {\n const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(new ArrayBuffer(binary.length));\n for (let index = 0; index < binary.length; index++) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n}\n","import { InjectionToken } from '@angular/core';\n\n/** A `fetch`-compatible function. */\nexport type LtFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * The fetch used by every library call that does not go through `HttpClient`: error translation, system setup,\n * AI (incl. the SSE stream), offline sync and its hint stream, the Better Auth client.\n *\n * The default sends cookies (`credentials: 'include'`). `provideLtAuth()` replaces it with the auth-aware fetch\n * (Bearer token in JWT mode, cookie → JWT switch on 401, stale-JWT refresh, session-loss handling), so SSE and sync\n * get the same auth behaviour as `HttpClient` calls behind `ltAuthInterceptor`.\n */\nexport const LT_FETCH = new InjectionToken<LtFetch>('LT_FETCH', {\n factory: () => ltCredentialsFetch,\n});\n\n/** `fetch` with `credentials: 'include'` unless the caller decided otherwise. */\nexport const ltCredentialsFetch: LtFetch = (input, init) => fetch(input, { credentials: 'include', ...init });\n","","","import { isPlatformBrowser } from '@angular/common';\nimport { computed, inject, InjectionToken, PLATFORM_ID, Service, signal, type Signal } from '@angular/core';\n\nimport de from './i18n/de.json';\nimport en from './i18n/en.json';\n\n/** Nested message tree as in the shipped `i18n/de.json` / `i18n/en.json`. */\nexport interface LtI18nMessages {\n [key: string]: LtI18nMessages | string;\n}\n\n/**\n * Bridge to the app's translation library (Transloco, ngx-translate, …).\n *\n * Bind it when the app already has an i18n setup, so library texts follow the app language and can be overridden\n * in the app's own translation files (keys `lt.error.*` of the core and `lt.<entry>.*` of the entry points: `lt.auth.*`,\n * `lt.files.*`, `lt.share.*`, `lt.ui.*`, `lt.bug.*`; shipped as `i18n/{de,en}.json` and `i18n/<entry>/{de,en}.json`).\n *\n * @example Transloco\n * { provide: LT_I18N_ADAPTER, useFactory: () => {\n * const transloco = inject(TranslocoService);\n * const lang = toSignal(transloco.langChanges$, { initialValue: transloco.getActiveLang() });\n * return { locale: lang, translate: (key, params) => transloco.translate(key, params) };\n * } }\n */\nexport interface LtI18nAdapter {\n /** Active locale (`de`, `en`, …). */\n readonly locale: Signal<string>;\n /** Translate a key; return `null` (or the key itself) when the app has no translation, then the library text is used. */\n translate(key: string, params?: Record<string, unknown>): string | null;\n}\n\n/** Optional adapter to the app's i18n library. */\nexport const LT_I18N_ADAPTER = new InjectionToken<LtI18nAdapter>('LT_I18N_ADAPTER');\n\n/** Options of the built-in i18n (part of `provideLtNgBase({ i18n })`). */\nexport interface LtI18nOptions {\n /** Locale used when neither the adapter nor the browser language (de/en) decides. Default `de`. */\n defaultLocale?: string;\n /** Additional or overriding messages per locale, merged over the shipped de/en texts. */\n messages?: Record<string, LtI18nMessages>;\n}\n\n/** i18n options token (set by `provideLtNgBase`). */\nexport const LT_I18N_OPTIONS = new InjectionToken<LtI18nOptions>('LT_I18N_OPTIONS', { factory: () => ({}) });\n\n/** The core's shipped messages (`lt.error.*`); the entry points register theirs with `registerLibraryMessages()`. */\nexport const LT_I18N_MESSAGES: Readonly<Record<'de' | 'en', LtI18nMessages>> = { de, en };\n\n/**\n * Signal-based translations for library texts, with an optional adapter to the app's i18n library.\n *\n * Lookup order for `t(key)`: adapter → messages of the active locale → German messages → the key.\n * Locale order: adapter → `setLocale()` → browser language (de/en, browser only) → `defaultLocale` → `de`.\n */\n@Service()\nexport class LtI18nService {\n private readonly adapter = inject(LT_I18N_ADAPTER, { optional: true });\n private readonly options = inject(LT_I18N_OPTIONS);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly explicitLocale = signal<string | null>(null);\n private readonly extraMessages = signal<Record<string, LtI18nMessages>>(this.options.messages ?? {});\n /**\n * Texts of the entry points (`lt.ui.*`, `lt.auth.*` …) registered by {@link registerLibraryMessages}. A plain object:\n * every entry registers its texts before its own components read them, so nothing has to react to a registration.\n */\n private readonly libraryMessages: Record<string, LtI18nMessages> = {};\n private readonly libraryIds = new Set<string>();\n\n /** Active locale. */\n readonly locale = computed(() => this.adapter?.locale() || this.explicitLocale() || this.detectBrowserLocale() || this.options.defaultLocale || 'de');\n\n /** Set the locale (ignored for lookups while an adapter provides one). */\n setLocale(locale: string): void {\n this.explicitLocale.set(locale);\n }\n\n /** Merge messages for a locale over the shipped ones. */\n registerMessages(locale: string, messages: LtI18nMessages): void {\n this.extraMessages.update((current) => ({ ...current, [locale]: deepMerge(current[locale] ?? {}, messages) }));\n }\n\n /**\n * Register the default texts of an entry point once per `id` (`ui`, `auth`, `files` …). They rank below the app's\n * own texts (`provideLtNgBase({ i18n: { messages } })`, `registerMessages()`, the adapter), like the shipped core\n * texts. Entry points call it when they are first used (synchronously, on the server and in the browser alike), so\n * their texts travel with their code instead of the initial bundle.\n */\n registerLibraryMessages(id: string, messages: Readonly<Record<string, LtI18nMessages>>): void {\n if (this.libraryIds.has(id)) {\n return;\n }\n this.libraryIds.add(id);\n for (const [locale, tree] of Object.entries(messages)) {\n this.libraryMessages[locale] = deepMerge(this.libraryMessages[locale] ?? {}, tree);\n }\n }\n\n /** Translate `key`, interpolating `{name}` placeholders from `params`. */\n t(key: string, params?: Record<string, unknown>): string {\n const fromAdapter = this.adapter?.translate(key, params);\n if (fromAdapter && fromAdapter !== key) {\n return fromAdapter;\n }\n const locale = this.locale();\n const message =\n lookup(this.extraMessages()[locale], key) ??\n lookup((LT_I18N_MESSAGES as Record<string, LtI18nMessages>)[locale], key) ??\n lookup(this.libraryMessages[locale], key) ??\n lookup(this.extraMessages()['de'], key) ??\n lookup(LT_I18N_MESSAGES.de, key) ??\n lookup(this.libraryMessages['de'], key) ??\n key;\n return interpolate(message, params);\n }\n\n private detectBrowserLocale(): string | null {\n if (!this.isBrowser || typeof navigator === 'undefined' || !navigator.language) {\n return null;\n }\n const language = navigator.language.split('-')[0]?.toLowerCase() ?? '';\n return language === 'de' || language === 'en' ? language : null;\n }\n}\n\nfunction lookup(messages: LtI18nMessages | undefined, key: string): string | null {\n let node: LtI18nMessages | string | undefined = messages;\n for (const part of key.split('.')) {\n if (!node || typeof node === 'string') {\n return null;\n }\n node = node[part];\n }\n return typeof node === 'string' ? node : null;\n}\n\nfunction interpolate(message: string, params?: Record<string, unknown>): string {\n if (!params) {\n return message;\n }\n return message.replace(/\\{(\\w+)\\}/g, (match, name: string) => (name in params ? String(params[name]) : match));\n}\n\nfunction deepMerge(target: LtI18nMessages, source: LtI18nMessages): LtI18nMessages {\n const result: LtI18nMessages = { ...target };\n for (const [key, value] of Object.entries(source)) {\n const current = result[key];\n result[key] = typeof value === 'object' && typeof current === 'object' ? deepMerge(current, value) : value;\n }\n return result;\n}\n","import { inject, InjectionToken, Service } from '@angular/core';\n\n/** Toast colors (same set as Nuxt UI). */\nexport type LtToastColor = 'error' | 'info' | 'neutral' | 'primary' | 'secondary' | 'success' | 'warning';\n\n/** A toast message. */\nexport interface LtToastOptions {\n color?: LtToastColor;\n description?: string;\n /** Display time in milliseconds (adapter default when omitted). */\n duration?: number;\n title?: string;\n}\n\n/**\n * Bridge to the app's toaster. `provideLtUi()` binds it to Spartan Sonner (`<hlm-toaster />`); bind your own for\n * another UI library. Return `false` when nothing could be shown, the caller then logs to the console.\n */\nexport interface LtToastAdapter {\n show(options: LtToastOptions): boolean;\n}\n\n/** Optional toaster bridge (see {@link LtToastAdapter}). */\nexport const LT_TOAST_ADAPTER = new InjectionToken<LtToastAdapter>('LT_TOAST_ADAPTER');\n\n/** Shows toasts through {@link LT_TOAST_ADAPTER}; without an adapter, `show` returns `false`. */\n@Service()\nexport class LtToastService {\n private readonly adapter = inject(LT_TOAST_ADAPTER, { optional: true });\n\n /** Show a toast. Returns `false` when no toaster is bound or it threw. */\n show(options: LtToastOptions): boolean {\n if (!this.adapter) {\n return false;\n }\n try {\n return this.adapter.show(options) !== false;\n } catch {\n return false;\n }\n }\n\n /** Shortcut for a success toast. */\n success(title: string, description?: string): boolean {\n return this.show({ color: 'success', description, title });\n }\n\n /** Shortcut for an error toast. */\n error(title: string, description?: string): boolean {\n return this.show({ color: 'error', description, title });\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { computed, inject, InjectionToken, PLATFORM_ID, Service, signal } from '@angular/core';\n\nimport { LtApiUrlService } from './api-url';\nimport { LT_FETCH } from './fetch';\nimport { LtI18nService } from './i18n';\nimport { LtToastService } from './toast';\n\n/** Response of nest-server `GET /i18n/errors/:locale`. */\nexport interface LtErrorTranslationResponse {\n errors: Record<string, string>;\n}\n\n/** Result of {@link LtErrorTranslationService.parseError}. */\nexport interface LtParsedError {\n /** Error code such as `LTNS_0100`, or `null` when the message carries none. */\n code: string | null;\n /** Message as sent by the backend (without the `#CODE:` marker). */\n developerMessage: string;\n /** Translated message for the active locale; falls back to the developer message, never empty for a message-bearing error. */\n translatedMessage: string;\n}\n\n/** Options of the error translation (part of `provideLtNgBase({ errorTranslation })`, runtime `NG_PUBLIC_LT_ERROR_TRANSLATION_*`). */\nexport interface LtErrorTranslationOptions {\n /** Locale used when i18n does not decide (default `de`). */\n defaultLocale?: string;\n /** Load and apply backend translations (default `true`). */\n enabled?: boolean;\n /** Load the translations for the detected locale while the app starts, browser only (default `true`). */\n preload?: boolean;\n}\n\n/** Resolved error translation options. */\nexport const LT_ERROR_TRANSLATION_OPTIONS = new InjectionToken<Required<LtErrorTranslationOptions>>('LT_ERROR_TRANSLATION_OPTIONS', {\n factory: () => ({ defaultLocale: 'de', enabled: true, preload: true }),\n});\n\nconst ERROR_CODE_REGEX = /^#([A-Z_]+_\\d+):\\s*(.+)$/s;\n\n/** A translation load that takes longer is aborted (the next call retries). */\nexport const LT_ERROR_TRANSLATION_TIMEOUT_MS = 10_000;\n\n/**\n * Translates nest-server error messages of the form `#LTNS_0100: Developer message` with the backend's\n * `GET /i18n/errors/:locale` table.\n */\n@Service()\nexport class LtErrorTranslationService {\n private readonly api = inject(LtApiUrlService);\n private readonly fetch = inject(LT_FETCH);\n private readonly i18n = inject(LtI18nService);\n private readonly toast = inject(LtToastService);\n private readonly options = inject(LT_ERROR_TRANSLATION_OPTIONS);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly translations = signal<Record<string, Record<string, string>>>({});\n private readonly inFlight = new Map<string, Promise<void>>();\n\n /** Locale used for lookups. */\n readonly currentLocale = computed(() => this.i18n.locale() || this.options.defaultLocale || 'de');\n /** Whether translations for the current locale are loaded. */\n readonly isLoaded = computed(() => !!this.translations()[this.currentLocale()]);\n /** Whether a load is running. */\n readonly isLoading = signal(false);\n\n /** Load the backend translations for `locale` (default: current locale). Cached per locale, one request in flight per locale. */\n async loadTranslations(locale?: string): Promise<void> {\n if (!this.options.enabled) {\n return;\n }\n const target = locale || this.currentLocale();\n if (this.translations()[target]) {\n return;\n }\n const running = this.inFlight.get(target);\n if (running) {\n return running;\n }\n const load = (async () => {\n this.isLoading.set(true);\n try {\n const response = await this.fetch(this.api.buildUrl(`/i18n/errors/${encodeURIComponent(target)}`), {\n headers: { accept: 'application/json' },\n signal: AbortSignal.timeout(LT_ERROR_TRANSLATION_TIMEOUT_MS),\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const body = (await response.json()) as Partial<LtErrorTranslationResponse> | null;\n if (body?.errors && typeof body.errors === 'object') {\n this.translations.update((current) => ({ ...current, [target]: body.errors as Record<string, string> }));\n }\n } catch (error) {\n console.warn(`[@lenne.tech/ng-base] Failed to load error translations for ${target}:`, error);\n } finally {\n this.inFlight.delete(target);\n this.isLoading.set(this.inFlight.size > 0);\n }\n })();\n this.inFlight.set(target, load);\n return load;\n }\n\n /** Parse any error shape (string, `Error`, `HttpErrorResponse`, response body) into code + messages. */\n parseError(errorOrMessage: unknown): LtParsedError {\n const message = extractLtErrorMessage(errorOrMessage);\n const match = message.match(ERROR_CODE_REGEX);\n if (!match) {\n return { code: null, developerMessage: message, translatedMessage: message };\n }\n const code = match[1] ?? '';\n const developerMessage = (match[2] ?? '').trim();\n const translatedMessage = this.translations()[this.currentLocale()]?.[code] || developerMessage;\n return { code, developerMessage, translatedMessage };\n }\n\n /** Translated message for an error (never empty for a message-bearing error). */\n translateError(errorOrMessage: unknown): string {\n return this.parseError(errorOrMessage).translatedMessage;\n }\n\n /** Show the translated error as an error toast (browser only); logs to the console when no toaster is bound. */\n showErrorToast(errorOrMessage: unknown, title?: string): void {\n if (!this.isBrowser) {\n return;\n }\n const parsed = this.parseError(errorOrMessage);\n const shown = this.toast.show({ color: 'error', description: parsed.translatedMessage, title: title || this.i18n.t('lt.error.title') });\n if (!shown) {\n console.error('[@lenne.tech/ng-base]', parsed.translatedMessage);\n }\n }\n}\n\n/**\n * Extract the human-relevant message from an error.\n *\n * The response body wins over a transport message: `HttpErrorResponse.message` is\n * `Http failure response for …: 400 Bad Request`, while the backend message (the only one that can carry a\n * `#LTNS_` marker) is in `error.message`. Better Auth errors (`{ code, message }`) and arrays of messages\n * (class-validator) are handled too.\n */\nexport function extractLtErrorMessage(errorOrMessage: unknown): string {\n if (typeof errorOrMessage === 'string') {\n return errorOrMessage;\n }\n if (errorOrMessage === null || errorOrMessage === undefined) {\n return '';\n }\n if (typeof errorOrMessage !== 'object') {\n return String(errorOrMessage);\n }\n const source = errorOrMessage as Record<string, unknown>;\n for (const candidate of [source['error'], source['data'], source['body']]) {\n const fromBody = messageOf(candidate);\n if (fromBody) {\n return fromBody;\n }\n }\n return messageOf(source) || (typeof source['statusText'] === 'string' && source['statusText']) || String(errorOrMessage);\n}\n\nfunction messageOf(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n if (!value || typeof value !== 'object') {\n return '';\n }\n const message = (value as Record<string, unknown>)['message'];\n if (typeof message === 'string') {\n return message;\n }\n if (Array.isArray(message)) {\n return message.filter((part) => typeof part === 'string').join(', ');\n }\n const nested = (value as Record<string, unknown>)['error'];\n if (nested && typeof nested === 'object') {\n return messageOf(nested);\n }\n return '';\n}\n","/** Version of `@lenne.tech/ng-base`; kept in sync with `package.json` by `scripts/sync-version.mjs`. */\nexport const LT_NG_BASE_VERSION = '22.0.4';\n","import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { ApplicationRef, inject, makeEnvironmentProviders, PLATFORM_ID, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\n\nimport { LtApiUrlService } from './api-url';\nimport { LT_ERROR_TRANSLATION_OPTIONS, LtErrorTranslationService, type LtErrorTranslationOptions } from './error-translation';\nimport { LT_I18N_OPTIONS, type LtI18nOptions } from './i18n';\nimport { provideLtPreHydrationInput, type LtPreHydrationInputOptions } from './pre-hydration-input';\nimport {\n LT_RUNTIME_CONFIG,\n LT_RUNTIME_CONFIG_DEFAULTS,\n LT_SERVER_RUNTIME_CONFIG,\n mergeLtFeatureOptions,\n mergeLtRuntimeConfig,\n readLtRuntimeConfigFromDocument,\n type LtRuntimeConfig,\n} from './runtime-config';\nimport { LT_NG_BASE_VERSION } from './version';\n\n/** Options of {@link provideLtNgBase}. */\nexport interface LtNgBaseConfig {\n /**\n * Check at startup that an API URL is configured for the current scope (default `true`): a warning in dev mode, a\n * console error in production. Turn it off for apps without a backend (ng-base/ui only).\n */\n configCheck?: boolean;\n /** Backend error translation (`#LTNS_xxxx`). Turn it off (`enabled: false`) for apps without nest-server. */\n errorTranslation?: LtErrorTranslationOptions;\n /** Library i18n (locale, extra messages). Bind `LT_I18N_ADAPTER` to use the app's i18n library. */\n i18n?: LtI18nOptions;\n /** Keep what users type into server-rendered fields before hydration (default on, see `provideLtPreHydrationInput`). */\n preHydrationInput?: LtPreHydrationInputOptions;\n /**\n * Static runtime configuration: the base that the per-request values of the SSR server (`NG_*` via\n * `readLtRequestContext()`, `<script id=\"ng-runtime-config\">` in the browser) override. Use it for apps without SSR,\n * tests, or defaults.\n */\n runtime?: Partial<LtRuntimeConfig>;\n}\n\n/** Name of the window flag set once the app is stable (hydrated) in the browser; read by `waitForHydration()`. */\nexport const LT_HYDRATED_FLAG = '__ltNgBaseHydrated';\n\n/**\n * Core providers of `@lenne.tech/ng-base`.\n *\n * - {@link LT_RUNTIME_CONFIG}: defaults ← `config.runtime` ← request context (server) / `<script id=\"ng-runtime-config\">` (browser).\n * - Error translation options and preload of the backend translations (browser only, in the background: the start\n * never waits for the API; until they arrive errors show the developer message).\n * - i18n options.\n * - API URL config check.\n * - Pre-hydration input preservation (typed text survives hydration).\n * - Hydration flag `window.__ltNgBaseHydrated` for the Playwright helpers.\n *\n * Feature providers live in the secondary entry points: `provideLtAuth()` (`/auth`), `provideLtFiles()` (`/files`),\n * `provideLtAi()` (`/ai`), `provideLtSync()` (`/sync`), `provideLtUi()` (`/ui`), `provideLtServerRuntimeConfig()` (`/server`).\n *\n * @example app.config.ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([ltAuthInterceptor])),\n * provideLtNgBase(),\n * provideLtAuth({ systemSetup: { enabled: true } }),\n * provideLtUi(),\n * ],\n * };\n */\nexport function provideLtNgBase(config: LtNgBaseConfig = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: LT_RUNTIME_CONFIG,\n useFactory: () => {\n const base = mergeLtRuntimeConfig(LT_RUNTIME_CONFIG_DEFAULTS, config.runtime);\n if (!isPlatformBrowser(inject(PLATFORM_ID))) {\n return mergeLtRuntimeConfig(base, inject(LT_SERVER_RUNTIME_CONFIG, { optional: true })?.public);\n }\n return mergeLtRuntimeConfig(base, readLtRuntimeConfigFromDocument(inject(DOCUMENT)));\n },\n },\n {\n provide: LT_ERROR_TRANSLATION_OPTIONS,\n useFactory: () =>\n mergeLtFeatureOptions<Required<LtErrorTranslationOptions>>(\n { defaultLocale: config.i18n?.defaultLocale ?? 'de', enabled: true, preload: true },\n config.errorTranslation,\n inject(LT_RUNTIME_CONFIG).lt.errorTranslation,\n ),\n },\n { provide: LT_I18N_OPTIONS, useValue: config.i18n ?? {} },\n provideLtPreHydrationInput(config.preHydrationInput),\n provideAppInitializer(() => {\n if (config.configCheck !== false) {\n inject(LtApiUrlService).reportMissing();\n }\n }),\n provideAppInitializer(() => {\n const options = inject(LT_ERROR_TRANSLATION_OPTIONS);\n if (!isPlatformBrowser(inject(PLATFORM_ID)) || !options.enabled || !options.preload) {\n return;\n }\n // Not awaited: a slow or hanging API must never hold the app start (the translations are a signal).\n void inject(LtErrorTranslationService).loadTranslations();\n }),\n provideAppInitializer(() => {\n if (!isPlatformBrowser(inject(PLATFORM_ID))) {\n return;\n }\n void inject(ApplicationRef)\n .whenStable()\n .then(() => {\n (globalThis as Record<string, unknown>)[LT_HYDRATED_FLAG] = true;\n (globalThis as Record<string, unknown>)['__ltNgBaseVersion'] = LT_NG_BASE_VERSION;\n });\n }),\n ]);\n}\n","/**\n * Identity function and tagged template for Tailwind class strings outside templates, so the Tailwind IntelliSense\n * extension recognises them (configure `\"tailwindCSS.experimental.classRegex\": [\"tw`([^`]*)`\", \"tw\\\\(['\\\"]([^'\\\"]*)\"]`).\n *\n * @example\n * const card = tw`rounded-lg border p-4`;\n * const active = tw('bg-primary text-primary-foreground');\n */\nexport function tw(strings: TemplateStringsArray | string, ...values: unknown[]): string {\n if (typeof strings === 'string') {\n return strings;\n }\n return strings.reduce((result, part, index) => result + part + (index < values.length ? String(values[index]) : ''), '');\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/ng-base",
|
|
3
|
-
"version": "22.0.
|
|
3
|
+
"version": "22.0.4",
|
|
4
4
|
"description": "Angular library for lenne.Tech projects: nest-server integration (Better Auth, TUS upload, AI, offline sync), Spartan-based UI, SSR server helpers and Playwright helpers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -492,7 +492,7 @@ export declare class LtToastService {
|
|
|
492
492
|
*/
|
|
493
493
|
export declare function tw(strings: TemplateStringsArray | string, ...values: unknown[]): string;
|
|
494
494
|
/** Version of `@lenne.tech/ng-base`; kept in sync with `package.json` by `scripts/sync-version.mjs`. */
|
|
495
|
-
export declare const LT_NG_BASE_VERSION = "22.0.
|
|
495
|
+
export declare const LT_NG_BASE_VERSION = "22.0.4";
|
|
496
496
|
/**
|
|
497
497
|
* Log `message` the first time `key` is seen; ignore later calls. The conditions behind these messages (missing API
|
|
498
498
|
* URL, implicit dev proxy) are constant for the process, so repeating them only drowns other output.
|