import * as i0 from '@angular/core'; import { Provider, InjectionToken, PipeTransform } from '@angular/core'; import { HttpStatusCode, HttpHeaders, HttpErrorResponse, HttpClient, HttpResponse, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { ValidationErrors } from '@angular/forms'; import { LucideIconData } from '@lucide/angular'; import { IconNode } from 'lucide'; /** * Types for mn-lib configuration. */ type MnConfigSettings = { /** Application or library version. */ version?: string; /** Application or library name. */ name?: string; }; type MnConfigFile = { /** * General settings such as version and name. */ settings?: MnConfigSettings; /** * Base defaults by component name. Each value is a plain object with inputs/options for that component. */ defaults: Record; /** * Nested object tree keyed by section names. Leaf nodes may contain * component-name keys (component override objects) and keys starting with '#' * representing instance-id overrides. */ overrides: Record; }; /** * Parses a config file. Strict JSON goes through `JSON.parse`; only a file that needs JSON5 * syntax (comments, unquoted keys, trailing commas) loads the `json5` parser, as its own chunk. * A static import put the CommonJS `json5` package (~32 kB) into every consumer's startup bundle. * * @param text The raw file content. * @returns The parsed value. */ declare function parseConfigText(text: string): Promise; declare class MnConfigService { private readonly http; private _config; private _settings; private _debugMode; /** Reactive version counter — incremented on every config load. */ private _configVersion; readonly configVersion: i0.Signal; private readonly lang; /** General settings from the config file (version, name, etc.). */ get settings(): Readonly; /** * Load the configuration JSON from the provided URL and cache it in memory. * Consumers should typically call this via the APP_INITIALIZER helper. */ load(url: string, debugMode?: boolean): Promise; /** * Load configuration from a pre-parsed object (no HTTP fetch). * Used for live preview scenarios where config is pushed via postMessage. * Optionally re-bootstraps the language service if a `language` section is present. */ loadFromObject(config: Record, bootstrapLanguage?: boolean): Promise; /** * Resolve a configuration object for a component, optionally scoped to a section path * and optionally overridden by an instance id. */ resolve>(componentName: string, sectionPath?: string[], instanceId?: string): T; /** * Walk the overrides nested object using the provided section path and return the leaf node. * If any segment is missing or the current node is not a plain object, returns undefined. */ walkOverrides(overridesRoot: unknown, sectionPath: string[]): unknown | undefined; /** * Recursively walk a resolved config object and replace any `{ $translate: "key" }` markers * with their translated values from MnLanguageService. */ private resolveTranslatables; /** * Deep merge two plain-object trees. Arrays and non-plain values are replaced by the patch. * Does not mutate inputs; returns a new object. */ deepMerge, B extends Record>(base: A, patch: B): A & B; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Provides an APP_INITIALIZER that loads the mn-lib configuration from the given URL * during application bootstrap. The consuming application is responsible for providing * HttpClient (e.g., via HttpClientModule or provideHttpClient()). */ declare function provideMnConfig(url: string, debugMode?: boolean): Provider[]; /** * Helper to provide a resolved, typed component config via DI. * * Usage in a component/module providers: * const MY_CFG = new InjectionToken('MY_CFG'); * providers: [ provideMnComponentConfig(MY_CFG, 'my-component') ] * Then in the component: * readonly cfg = inject(MY_CFG) * * The returned config object is **reactive**: when the active locale changes, * all translatable values are re-resolved in place so that templates using * `cfg.someLabel` automatically reflect the new language on the next change-detection cycle. */ declare function provideMnComponentConfig(token: InjectionToken, componentName: string, initial?: Partial): Provider; /** * Represents the current section path based on nested mn-section directives. */ declare const MN_SECTION_PATH: InjectionToken; /** * Represents the current component instance id provided by [mn-instance]. */ declare const MN_INSTANCE_ID: InjectionToken; declare class MnSectionDirective { /** Section name contributed by this DOM node to the section path */ mnSection: string | undefined; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } declare class MnInstanceDirective { /** Instance id for targeting per-component instance overrides */ mnInstance: string | undefined; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } /** * A structured representation of an API error. * * Captures all relevant details — status, message, validation errors, * and retry information — so consumers can log, display, or act on * failures without inspecting the raw HTTP response. */ type ApiError = { status: HttpStatusCode | null; message: string; details?: unknown; backendMessage?: string; validationErrors?: Record; url?: string | null; headers?: HttpHeaders; original: HttpErrorResponse | Error; retryable: boolean; timestamp: string; }; /** * Metadata associated with an API result. * * Attached to both `SuccessResult` and `FailureResult` to provide * transport-level details such as the HTTP status code, response * headers, and the final URL after any redirects. */ type ResultMeta = { statusCode?: number; headers?: HttpHeaders; url?: string; }; /** * Represents a successful API result containing the response data. * * Discriminated by `ok: true`. Use `result.ok` to narrow the union * before accessing `data`. * * @template T The type of the response data. */ type SuccessResult = { ok: true; data: T; meta?: ResultMeta; }; /** * Represents a failed API result containing the structured error. * * Discriminated by `ok: false`. Use `result.ok` to narrow the union * before accessing `error`. */ type FailureResult = { ok: false; error: ApiError; meta?: ResultMeta; }; /** * A discriminated union representing either a successful or failed API result. * * Discriminated by `ok`. Use `result.ok` to narrow the type * before accessing `data` or `error`. * * @template T The type of the response data on success. */ type Result = SuccessResult | FailureResult; /** * A JavaScript primitive value. * * Used as the building block for query parameter values. */ type Primitive = string | number | boolean | null | undefined; /** * A value that can be used as a query parameter. * * Either a single primitive or an array of primitives. * Array values are appended as multiple entries for the same key. */ type QueryValue = Primitive | Primitive[]; /** * A record of query parameter key-value pairs. * * Passed to CRUD service methods and converted to `HttpParams` * before the request is sent. `null` and `undefined` values * are silently skipped during conversion. */ type QueryParams = Record; /** * Configuration for a CRUD service endpoint. * * Passed to the `CrudService` constructor to define which API * resource the service operates on. */ type CrudConfig = { endpoint: string; }; /** * Abstract base class for CRUD services. * Provides standard HTTP operations with typed `Result` responses. * * @template TEntity The entity type returned by single-item operations. * @template TListResponse The response type for list operations (defaults to `TEntity[]`). * @template TCreatePayload The payload type for create operations (defaults to `Partial`). * @template TUpdatePayload The payload type for update operations (defaults to `Partial`). * @template TId The type of the entity identifier (defaults to `number`). * @template TGetByIdResponse The response type for getById (defaults to `TEntity`). * @template TCreateResponse The response type for create (defaults to `TEntity`). * @template TUpdateResponse The response type for update and patch (defaults to `TEntity`). * @template TDeleteResponse The response type for delete (defaults to `void`). */ declare abstract class CrudService, TUpdatePayload = Partial, TId extends string | number = number, TGetByIdResponse = TEntity, TCreateResponse = TEntity, TUpdateResponse = TEntity, TDeleteResponse = void> { protected readonly http: HttpClient; protected readonly baseUrl: string; protected readonly endpoint: string; protected constructor(config: CrudConfig); /** * Retrieves all entities from the configured endpoint. * * Sends a GET request to the base endpoint. Query values are * converted to `HttpParams` before the request is sent. * * @param query Optional query parameters appended to the request URL. * @returns An observable emitting a `Result` with the list response or a structured failure. */ getAll(query?: QueryParams): Observable>; /** * Retrieves a single entity by its identifier. * * Sends a GET request to `{endpoint}/{id}`. * * @param id The unique identifier of the entity to retrieve. * @returns An observable emitting a `Result` with the entity or a structured failure. */ getById(id: TId): Observable>; /** * Creates a new entity at the configured endpoint. * * Sends a POST request with the provided payload as the request body. * * @param payload The data used to create the entity. * @returns An observable emitting a `Result` with the created entity or a structured failure. */ create(payload: TCreatePayload): Observable>; /** * Fully replaces an existing entity. * * Sends a PUT request to `{endpoint}/{id}` with the provided payload, * replacing the entire entity. * * @param id The unique identifier of the entity to update. * @param payload The complete data to replace the existing entity with. * @returns An observable emitting a `Result` with the updated entity or a structured failure. */ update(id: TId, payload: TUpdatePayload): Observable>; /** * Partially updates an existing entity. * * Sends a PATCH request to `{endpoint}/{id}` with the provided payload, * merging changes into the existing entity. * * @param id The unique identifier of the entity to patch. * @param payload A partial set of fields to update on the existing entity. * @returns An observable emitting a `Result` with the updated entity or a structured failure. */ patch(id: TId, payload: Partial): Observable>; /** * Deletes an entity by its identifier. * * Sends a DELETE request to `{endpoint}/{id}`. * * @param id The unique identifier of the entity to delete. * @returns An observable emitting a `Result` with the delete response or a structured failure. */ delete(id: TId): Observable>; /** * Retrieves all entities with the full `HttpResponse` wrapper. * * Behaves like {@link getAll} but observes the complete HTTP response, * giving access to headers, status code, and URL alongside the body. * * @param query Optional query parameters appended to the request URL. * @returns An observable emitting a `Result` with the full HTTP response or a structured failure. */ getAllResponse(query?: QueryParams): Observable>>; /** * Builds the URL for a single entity by appending the identifier to the endpoint. * * @param id The unique identifier to append. * @returns The full URL targeting the specific entity. */ protected itemUrl(id: TId): string; /** * Wraps a value in a `SuccessResult`. * * @template T The type of the response data. * @param data The response data to wrap. * @param meta Optional metadata (status code, headers, URL) to attach. * @returns A `SuccessResult` containing the provided data. */ protected success(data: T, meta?: ResultMeta): SuccessResult; /** * Wraps an error in a `FailureResult`. * * When no explicit metadata is provided, metadata is derived from * the `ApiError` itself (status code, headers, URL). * * @param error The structured API error. * @param meta Optional metadata to override the error-derived values. * @returns A `FailureResult` containing the error and metadata. */ protected failure(error: ApiError, meta?: ResultMeta): FailureResult; /** * Maps an unknown error into a structured `ApiError`. * * Handles both `HttpErrorResponse` instances and unexpected error types. * Extracts backend messages, validation errors, and retry information * so callers receive a consistent error shape. * * @param error The raw error caught from the HTTP pipeline. * @returns A fully populated `ApiError` object. */ protected mapHttpError(error: unknown): ApiError; /** * Extracts a human-readable message from the error response body. * * Checks common keys (`message`, `title`, `detail`, `error`) on the * body object and returns the first non-empty string found. * * @param body The parsed error response body. * @returns The extracted message, or `undefined` if none was found. */ protected extractBackendMessage(body: unknown): string | undefined; /** * Extracts field-level validation errors from the error response body. * * Expects an `errors` property on the body containing a record of * field names to error messages (string or string array). * * @param body The parsed error response body. * @returns A record mapping field names to their error messages, or `undefined` if none were found. */ protected extractValidationErrors(body: unknown): Record | undefined; /** * Returns a default user-facing message for the given HTTP status code. * * Provides human-readable messages for common HTTP status codes. * Override this method to customise messages. * * @param status The HTTP status code, or `null` when unknown. * @returns A descriptive error message. */ protected defaultMessage(status: HttpStatusCode | null): string; /** * Determines whether a request with the given status can be retried. * * Timeouts, rate-limiting responses, and server errors * (5xx) are considered retryable by default. * * @param status The HTTP status code, or `null` when unknown. * @returns `true` if the request is safe to retry. */ protected isRetryable(status: HttpStatusCode | null): boolean; /** * Normalises a raw HTTP status into an `HttpStatusCode | null` value. * * Converts `undefined`, `NaN`, and `0` (network error) to `null` * so downstream code only needs to handle `HttpStatusCode | null`. * * @param status The raw status value from the HTTP response. * @returns The normalised status code, or `null` when indeterminate. */ protected normalizeStatus(status: number | null | undefined): HttpStatusCode | null; /** * Converts query parameters into Angular `HttpParams`. * * `null` and `undefined` values are silently skipped. * Array values are appended as multiple entries for the same key. * * @param query The query parameter record to convert. * @returns An `HttpParams` instance, or `undefined` when no parameters are provided. */ protected toHttpParams(query?: QueryParams): HttpParams | undefined; } /** * Injection token for the base URL used by all CRUD service requests. * * Provide this token at the application or module level to configure * the root API URL that `CrudService` prepends to every endpoint. */ declare const API_BASE_URL: InjectionToken; /** A single query parameter value. */ type MnQueryValue = string | number | boolean | null | undefined; /** A record of query parameter key-value pairs. Arrays are appended as multiple entries. */ type MnQueryParams = Record; /** * Lightweight abstract HTTP base class that removes common boilerplate * from API services. * * Provides typed `get`, `post`, `patch`, `put`, and `delete` methods * that return Promises, automatic query-param building, and base-URL * injection via the `API_BASE_URL` token. * * Subclass this directly for services with static or mixed endpoints. * No CRUD structure is imposed — every method accepts a free-form path. */ declare abstract class MnHttpService { /** Angular HTTP client injected automatically. */ protected readonly http: HttpClient; /** Base API URL provided via the `API_BASE_URL` injection token. */ protected readonly baseUrl: string; /** * Sends a typed GET request. * @param path The path appended to the base URL. * @param query Optional query parameters. * @returns A promise resolving to the typed response body. */ protected get(path: string, query?: MnQueryParams): Promise; /** * Sends a typed POST request. * @param path The path appended to the base URL. * @param body Optional request body. * @param query Optional query parameters. * @returns A promise resolving to the typed response body. */ protected post(path: string, body?: unknown, query?: MnQueryParams): Promise; /** * Sends a typed PATCH request. * @param path The path appended to the base URL. * @param body Optional request body. * @param query Optional query parameters. * @returns A promise resolving to the typed response body. */ protected patch(path: string, body?: unknown, query?: MnQueryParams): Promise; /** * Sends a typed PUT request. * @param path The path appended to the base URL. * @param body Optional request body. * @param query Optional query parameters. * @returns A promise resolving to the typed response body. */ protected put(path: string, body?: unknown, query?: MnQueryParams): Promise; /** * Sends a typed DELETE request. * @param path The path appended to the base URL. * @param query Optional query parameters. * @returns A promise resolving to the typed response body. */ protected delete(path: string, query?: MnQueryParams): Promise; /** * Converts a query-params record to Angular `HttpParams`. * Null and undefined values are silently skipped. * Array values are appended as multiple entries for the same key. * @param query The query parameter record to convert. * @returns An `HttpParams` instance, or `undefined` when no parameters are provided. */ protected toHttpParams(query?: MnQueryParams): HttpParams | undefined; } type MnImageType = { id: number; url: string; alt?: string; }; /** * Known shapes of Angular validator error argument objects. * Covers built-in validators (minlength, maxlength, min, max, pattern) * and the library's custom validators (mnMin, mnMax). */ type MnValidationErrorArgs = { /** Produced by Validators.minLength / Validators.maxLength */ requiredLength?: number; actualLength?: number; /** Produced by Validators.min / Validators.max */ min?: number | string; max?: number | string; actual?: number | string; /** Produced by Validators.pattern */ requiredPattern?: string; actualValue?: unknown; }; type MnErrorMessageFn = (args: MnValidationErrorArgs, errors: ValidationErrors) => string; /** * Turns icon nodes from the vanilla `lucide` package into the icon data that * `` (`LucideDynamicIcon`) and MnLib's icon inputs accept. * * Why not `@lucide/angular`'s per-icon components (``): that * package is a single module, and the Angular linker compiles a full copy of the * SVG template into every icon class, so each icon cost ~2.8 kB and all of them * landed in the startup chunk. With `lucide` an icon is a few hundred bytes of data, and * only the icons something imports are bundled. esbuild keeps all of them in one shared * chunk (they hang off the package's re-export barrel), about 30 kB for the whole app. * * Import the namespace and name each icon, so an icon called `Component`, `Map` * or `X` never shadows another import: * * ```ts * import * as lucide from 'lucide'; * const ICONS = lucideIcons({ ArrowLeft: lucide.ArrowLeft, Trash2: lucide.Trash2 }); * // template: * ``` * * @param nodes Icon nodes keyed by their PascalCase `lucide` export name. * @returns Icon data under the same keys, named `arrow-left`-style for the * `lucide-` class the icon renders with. */ declare function lucideIcons(nodes: Record): Record; /** * A marker object used in config values to indicate that the value * should be resolved via the MnLanguageService. * * Example in mn-config.json5: * label: { $translate: "form.email.label" } */ type MnTranslatable = { $translate: string; params?: Record; }; /** * A config value that is either a plain value or a translatable marker. */ type MnConfigValue = T | MnTranslatable; /** * Translations for a single locale, as a tree of keys. * * Both shapes resolve through the same dotted lookup: a bundle may nest * (`{ form: { email: { label } } }`), flatten (`{ "form.email.label": … }`), or mix the two. */ type MnTranslationMap = { [key: string]: string | MnTranslationMap; }; /** * All loaded translations keyed by locale code (e.g. "en", "nl", "de"). */ type MnTranslations = Record; /** * Configuration for the language provider. */ type MnLanguageConfig = { /** URL pattern for loading translation files. Use `{locale}` as placeholder. e.g. "assets/i18n/{locale}.json" */ urlPattern: string; /** The default/fallback locale. */ defaultLocale: string; /** Locales to preload at bootstrap. */ preload?: string[]; /** * Optional mapping of domain hostnames to locale codes. * When set, the service will use the current domain to determine the initial locale. * Example: { "example.nl": "nl", "example.de": "de", "example.com": "en" } */ domainLocaleMap?: Record; /** Whether to enable debug logging. */ debug?: boolean; }; /** * Type guard: checks whether a value is a translatable marker object. */ declare function isTranslatable(value: unknown): value is MnTranslatable; declare class MnLanguageService { private readonly http; private readonly appRef; private _translations; private _locale$; private _urlPattern; private _debug; /** * `Intl.PluralRules` per locale. Cached because {@link translate} runs on every change * detection through the impure `mnTranslate` pipe, and constructing one is not cheap. */ private readonly _pluralRules; /** Observable of the current active locale. */ readonly locale$: Observable; /** Current active locale. */ get locale(): string; /** * Enable or disable debug logging. */ setDebug(enabled: boolean): void; /** * Configure the URL pattern used to fetch translation files. * Use `{locale}` as placeholder, e.g. `"assets/i18n/{locale}.json"`. */ configure(urlPattern: string): void; /** * Load translations for a locale from the configured URL pattern. * If translations are already loaded for this locale, this is a no-op. */ loadLocale(locale: string): Promise; /** * Switch the active locale. Loads translations if not yet loaded. */ setLocale(locale: string): Promise; /** * Register translations for a locale directly from code (no HTTP needed). */ registerTranslations(locale: string, translations: MnTranslationMap): void; /** * Translate a key using the current locale, with optional parameter interpolation. * Falls back to the key itself if no translation is found. * * Interpolation replaces `{{paramName}}` with the provided value. * * A `count` param additionally selects the wording that agrees with it: the key is * resolved against its CLDR plural category first (`key` + `One`/`Two`/`Few`/`Many`/ * `Zero`), falling back to `key` when that sibling is undefined. Nothing has to opt in — * a key with no sibling behaves exactly as before. * * ```ts * // 'shift.asked' → '{{count}} members are notified' * // 'shift.askedOne' → '{{count}} member is notified' * lang.translate('shift.asked', { count: 3 }); // 3 members are notified * lang.translate('shift.asked', { count: 1 }); // 1 member is notified * ``` */ translate(key: string, params?: Record): string; /** * Picks the wording that agrees with a `count` param. * * A key carrying a count resolves against its CLDR plural category first, so * `askedMessage` + `askedMessageOne` render "3 leden krijgen bericht" and "1 lid krijgt * bericht" off the same call. Both languages change the verb as well as the noun, which * is why each form is a whole sentence under its own key rather than a swapped noun. * * Falls back to `key` whenever the sibling is undefined, so a key that never needed a * plural — or an app that has not written one yet — behaves exactly as it did before. * @param map The active locale's translations. * @param key The dot-notated translation key. * @param params The interpolation values, inspected for `count`. * @returns The key to look up: the plural sibling, or `key` itself. */ private resolvePluralKey; /** * The CLDR plural category of a count in the active locale. * @param count The count being quoted. * @returns The category, falling back to English rules for an unusable locale. */ private pluralCategory; /** * Helper to retrieve a value from a potentially nested translation map using a dot-notated key. */ private getValueFromMap; /** * Translate a key **only if it is defined**, returning `undefined` otherwise. * * {@link translate} deliberately returns the key itself when it is missing, which * makes it unusable for a library's own default labels: a consumer that never * defined `mnCollection.rowsPerPage` would see that raw string in their UI. This * lets a caller try a conventional key and fall back to a readable English default * when the app has not translated it, so components ship translatable strings * without forcing every consumer to define them. * * @param key The dot-notated translation key. * @param params Optional `{{name}}` interpolation values. * @returns The translation, or `undefined` when the key is not defined. */ translateIfPresent(key: string, params?: Record): string | undefined; /** * Shorthand alias for `translate`. */ t(key: string, params?: Record): string; /** * Resolve the effective default locale from a domain-to-locale map. * Matches `window.location.hostname` against the map keys. * Returns the mapped locale, or the provided fallback if no match is found. */ resolveLocaleForDomain(domainLocaleMap: Record | undefined, fallback: string): string; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Provides an APP_INITIALIZER that configures the MnLanguageService and * preloads the requested locales during application bootstrap. * * Usage in app.config.ts: * ...provideMnLanguage({ * urlPattern: 'assets/i18n/{locale}.json', * defaultLocale: 'en', * preload: ['en', 'nl'], * }) */ declare function provideMnLanguage(config: MnLanguageConfig): Provider[]; /** * Pipe that translates a key via MnLanguageService. * * Usage in templates: * {{ 'form.email.label' | mnTranslate }} * {{ 'greeting' | mnTranslate:{ name: 'World' } }} * * Note: This pipe is impure so it re-evaluates when the locale changes. */ declare class MnTranslatePipe implements PipeTransform { private readonly lang; transform(key: string, params?: Record): string; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵpipe: i0.ɵɵPipeDeclaration; } type MnPreviewMessage = { type: 'mn-config-update' | 'mn-translations-update'; config?: Record; translations?: Record>; }; /** * Enable live preview mode. Listens for postMessage events from * Mn Web Manager and hot-swaps config/translations at runtime. * * Call this once in your app's bootstrap (e.g., APP_INITIALIZER or root component). * * @param configService - The MnConfigService instance * @param langService - The MnLanguageService instance * @param allowedOrigins - Optional whitelist of allowed origins (security) */ declare function enableMnPreviewMode(configService: MnConfigService, langService: MnLanguageService, allowedOrigins?: string[]): void; export { API_BASE_URL, CrudService, MN_INSTANCE_ID, MN_SECTION_PATH, MnConfigService, MnHttpService, MnInstanceDirective, MnLanguageService, MnSectionDirective, MnTranslatePipe, enableMnPreviewMode, isTranslatable, lucideIcons, parseConfigText, provideMnComponentConfig, provideMnConfig, provideMnLanguage }; export type { ApiError, CrudConfig, FailureResult, MnConfigFile, MnConfigSettings, MnConfigValue, MnErrorMessageFn, MnImageType, MnLanguageConfig, MnPreviewMessage, MnQueryParams, MnTranslatable, MnTranslationMap, MnTranslations, MnValidationErrorArgs, Primitive, QueryParams, QueryValue, Result, ResultMeta, SuccessResult };