mn-angular-lib 0.0.44 → 0.0.46

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.
@@ -1,13 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Optional, Inject, inject, Input, ChangeDetectionStrategy, Component, HostBinding, Self, APP_INITIALIZER, ElementRef, HostListener, forwardRef, Directive, EventEmitter, TemplateRef, Output, ViewChildren, ViewContainerRef, ViewChild, ApplicationRef, EnvironmentInjector, createComponent, SkipSelf, Attribute, Pipe } from '@angular/core';
2
+ import { InjectionToken, Injectable, Optional, Inject, inject, Input, ChangeDetectionStrategy, Component, HostBinding, Self, DestroyRef, APP_INITIALIZER, ElementRef, HostListener, forwardRef, Directive, EventEmitter, TemplateRef, Output, ViewChildren, ViewContainerRef, ViewChild, ApplicationRef, EnvironmentInjector, createComponent, SkipSelf, Attribute, Pipe } from '@angular/core';
3
3
  export { TemplateRef, Type } from '@angular/core';
4
- import { BehaviorSubject, firstValueFrom, Subject, debounceTime } from 'rxjs';
4
+ import { BehaviorSubject, firstValueFrom, skip, Subject, debounceTime, map, catchError, of } from 'rxjs';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, NgClass, NgOptimizedImage, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
7
7
  import { tv } from 'tailwind-variants';
8
8
  import * as i1$2 from '@angular/forms';
9
9
  import { Validators, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
10
10
  import * as i1$1 from '@angular/common/http';
11
+ import { HttpClient, HttpErrorResponse, HttpStatusCode, HttpParams } from '@angular/common/http';
11
12
  import JSON5 from 'json5';
12
13
 
13
14
  // projects/mn-angular-lib/src/lib/mn-mn-alert/mn-mn-alert.tokens.ts
@@ -1118,16 +1119,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
1118
1119
  * providers: [ provideMnComponentConfig(MY_CFG, 'my-component') ]
1119
1120
  * Then in the component:
1120
1121
  * readonly cfg = inject(MY_CFG)
1122
+ *
1123
+ * The returned config object is **reactive**: when the active locale changes,
1124
+ * all translatable values are re-resolved in place so that templates using
1125
+ * `cfg.someLabel` automatically reflect the new language on the next change-detection cycle.
1121
1126
  */
1122
1127
  function provideMnComponentConfig(token, componentName, initial) {
1123
1128
  return {
1124
1129
  provide: token,
1125
- deps: [MnConfigService, [new Optional(), MN_SECTION_PATH], [new Optional(), MN_INSTANCE_ID]],
1126
- useFactory: (svc, sectionPath, instanceId) => {
1127
- const resolved = svc.resolve(componentName, sectionPath ?? [], instanceId ?? undefined);
1128
- // Apply optional initial (local defaults) over resolved? We prefer resolved to override initial local defaults,
1129
- // so merge initial first, then resolved on top.
1130
- return Object.assign({}, initial ?? {}, resolved);
1130
+ deps: [
1131
+ MnConfigService,
1132
+ MnLanguageService,
1133
+ DestroyRef,
1134
+ [new Optional(), MN_SECTION_PATH],
1135
+ [new Optional(), MN_INSTANCE_ID],
1136
+ ],
1137
+ useFactory: (svc, lang, destroyRef, sectionPath, instanceId) => {
1138
+ const resolveConfig = () => {
1139
+ const resolved = svc.resolve(componentName, sectionPath ?? [], instanceId ?? undefined);
1140
+ return Object.assign({}, initial ?? {}, resolved);
1141
+ };
1142
+ // Create the initial config object that will be shared by reference.
1143
+ const cfg = resolveConfig();
1144
+ // Re-resolve translatable values whenever the locale changes.
1145
+ // skip(1) because the current locale was already used for the initial resolve.
1146
+ const sub = lang.locale$.pipe(skip(1)).subscribe(() => {
1147
+ const updated = resolveConfig();
1148
+ // Mutate the existing object in place so all template bindings pick up the new values.
1149
+ for (const key of Object.keys(updated)) {
1150
+ cfg[key] = updated[key];
1151
+ }
1152
+ });
1153
+ destroyRef.onDestroy(() => sub.unsubscribe());
1154
+ return cfg;
1131
1155
  },
1132
1156
  };
1133
1157
  }
@@ -4292,6 +4316,364 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4292
4316
  args: ['mn-instance']
4293
4317
  }] } });
4294
4318
 
4319
+ /**
4320
+ * Injection token for the base URL used by all CRUD service requests.
4321
+ *
4322
+ * Provide this token at the application or module level to configure
4323
+ * the root API URL that `CrudService` prepends to every endpoint.
4324
+ */
4325
+ const API_BASE_URL = new InjectionToken('API_BASE_URL');
4326
+
4327
+ /**
4328
+ * Abstract base class for CRUD services.
4329
+ * Provides standard HTTP operations with typed `Result<T>` responses.
4330
+ *
4331
+ * @template TEntity The entity type returned by single-item operations.
4332
+ * @template TListResponse The response type for list operations (defaults to `TEntity[]`).
4333
+ * @template TCreatePayload The payload type for create operations (defaults to `Partial<TEntity>`).
4334
+ * @template TUpdatePayload The payload type for update operations (defaults to `Partial<TEntity>`).
4335
+ * @template TId The type of the entity identifier (defaults to `number`).
4336
+ * @template TGetByIdResponse The response type for getById (defaults to `TEntity`).
4337
+ * @template TCreateResponse The response type for create (defaults to `TEntity`).
4338
+ * @template TUpdateResponse The response type for update and patch (defaults to `TEntity`).
4339
+ * @template TDeleteResponse The response type for delete (defaults to `void`).
4340
+ */
4341
+ class CrudService {
4342
+ http = inject(HttpClient);
4343
+ baseUrl = inject(API_BASE_URL);
4344
+ endpoint;
4345
+ constructor(config) {
4346
+ this.endpoint = `${this.baseUrl}${config.endpoint}`;
4347
+ }
4348
+ /**
4349
+ * Retrieves all entities from the configured endpoint.
4350
+ *
4351
+ * Sends a GET request to the base endpoint. Query values are
4352
+ * converted to `HttpParams` before the request is sent.
4353
+ *
4354
+ * @param query Optional query parameters appended to the request URL.
4355
+ * @returns An observable emitting a `Result` with the list response or a structured failure.
4356
+ */
4357
+ getAll(query) {
4358
+ return this.http
4359
+ .get(this.endpoint, {
4360
+ params: this.toHttpParams(query)
4361
+ })
4362
+ .pipe(map((data) => this.success(data)), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4363
+ }
4364
+ /**
4365
+ * Retrieves a single entity by its identifier.
4366
+ *
4367
+ * Sends a GET request to `{endpoint}/{id}`.
4368
+ *
4369
+ * @param id The unique identifier of the entity to retrieve.
4370
+ * @returns An observable emitting a `Result` with the entity or a structured failure.
4371
+ */
4372
+ getById(id) {
4373
+ return this.http
4374
+ .get(this.itemUrl(id))
4375
+ .pipe(map((data) => this.success(data)), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4376
+ }
4377
+ /**
4378
+ * Creates a new entity at the configured endpoint.
4379
+ *
4380
+ * Sends a POST request with the provided payload as the request body.
4381
+ *
4382
+ * @param payload The data used to create the entity.
4383
+ * @returns An observable emitting a `Result` with the created entity or a structured failure.
4384
+ */
4385
+ create(payload) {
4386
+ return this.http
4387
+ .post(this.endpoint, payload)
4388
+ .pipe(map((data) => this.success(data)), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4389
+ }
4390
+ /**
4391
+ * Fully replaces an existing entity.
4392
+ *
4393
+ * Sends a PUT request to `{endpoint}/{id}` with the provided payload,
4394
+ * replacing the entire entity.
4395
+ *
4396
+ * @param id The unique identifier of the entity to update.
4397
+ * @param payload The complete data to replace the existing entity with.
4398
+ * @returns An observable emitting a `Result` with the updated entity or a structured failure.
4399
+ */
4400
+ update(id, payload) {
4401
+ return this.http
4402
+ .put(this.itemUrl(id), payload)
4403
+ .pipe(map((data) => this.success(data)), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4404
+ }
4405
+ /**
4406
+ * Partially updates an existing entity.
4407
+ *
4408
+ * Sends a PATCH request to `{endpoint}/{id}` with the provided payload,
4409
+ * merging changes into the existing entity.
4410
+ *
4411
+ * @param id The unique identifier of the entity to patch.
4412
+ * @param payload A partial set of fields to update on the existing entity.
4413
+ * @returns An observable emitting a `Result` with the updated entity or a structured failure.
4414
+ */
4415
+ patch(id, payload) {
4416
+ return this.http
4417
+ .patch(this.itemUrl(id), payload)
4418
+ .pipe(map((data) => this.success(data)), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4419
+ }
4420
+ /**
4421
+ * Deletes an entity by its identifier.
4422
+ *
4423
+ * Sends a DELETE request to `{endpoint}/{id}`.
4424
+ *
4425
+ * @param id The unique identifier of the entity to delete.
4426
+ * @returns An observable emitting a `Result` with the delete response or a structured failure.
4427
+ */
4428
+ delete(id) {
4429
+ return this.http
4430
+ .delete(this.itemUrl(id))
4431
+ .pipe(map((data) => this.success(data)), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4432
+ }
4433
+ /**
4434
+ * Retrieves all entities with the full `HttpResponse` wrapper.
4435
+ *
4436
+ * Behaves like {@link getAll} but observes the complete HTTP response,
4437
+ * giving access to headers, status code, and URL alongside the body.
4438
+ *
4439
+ * @param query Optional query parameters appended to the request URL.
4440
+ * @returns An observable emitting a `Result` with the full HTTP response or a structured failure.
4441
+ */
4442
+ getAllResponse(query) {
4443
+ return this.http
4444
+ .get(this.endpoint, {
4445
+ params: this.toHttpParams(query),
4446
+ observe: 'response',
4447
+ })
4448
+ .pipe(map((response) => this.success(response, {
4449
+ statusCode: response.status,
4450
+ headers: response.headers,
4451
+ url: response.url ?? undefined,
4452
+ })), catchError((error) => of(this.failure(this.mapHttpError(error)))));
4453
+ }
4454
+ /**
4455
+ * Builds the URL for a single entity by appending the identifier to the endpoint.
4456
+ *
4457
+ * @param id The unique identifier to append.
4458
+ * @returns The full URL targeting the specific entity.
4459
+ */
4460
+ itemUrl(id) {
4461
+ return `${this.endpoint}/${id}`;
4462
+ }
4463
+ /**
4464
+ * Wraps a value in a `SuccessResult`.
4465
+ *
4466
+ * @template T The type of the response data.
4467
+ * @param data The response data to wrap.
4468
+ * @param meta Optional metadata (status code, headers, URL) to attach.
4469
+ * @returns A `SuccessResult` containing the provided data.
4470
+ */
4471
+ success(data, meta) {
4472
+ return { ok: true, data, meta };
4473
+ }
4474
+ /**
4475
+ * Wraps an error in a `FailureResult`.
4476
+ *
4477
+ * When no explicit metadata is provided, metadata is derived from
4478
+ * the `ApiError` itself (status code, headers, URL).
4479
+ *
4480
+ * @param error The structured API error.
4481
+ * @param meta Optional metadata to override the error-derived values.
4482
+ * @returns A `FailureResult` containing the error and metadata.
4483
+ */
4484
+ failure(error, meta) {
4485
+ return {
4486
+ ok: false,
4487
+ error,
4488
+ meta: meta ?? {
4489
+ statusCode: error.status ?? undefined,
4490
+ headers: error.headers,
4491
+ url: error.url ?? undefined,
4492
+ },
4493
+ };
4494
+ }
4495
+ /**
4496
+ * Maps an unknown error into a structured `ApiError`.
4497
+ *
4498
+ * Handles both `HttpErrorResponse` instances and unexpected error types.
4499
+ * Extracts backend messages, validation errors, and retry information
4500
+ * so callers receive a consistent error shape.
4501
+ *
4502
+ * @param error The raw error caught from the HTTP pipeline.
4503
+ * @returns A fully populated `ApiError` object.
4504
+ */
4505
+ mapHttpError(error) {
4506
+ const timestamp = new Date().toISOString();
4507
+ if (!(error instanceof HttpErrorResponse)) {
4508
+ return {
4509
+ status: null,
4510
+ message: 'Unknown error',
4511
+ original: error instanceof Error ? error : new Error(String(error)),
4512
+ retryable: false,
4513
+ timestamp,
4514
+ };
4515
+ }
4516
+ const status = this.normalizeStatus(error.status);
4517
+ const details = error.error;
4518
+ const backendMessage = this.extractBackendMessage(details);
4519
+ const validationErrors = this.extractValidationErrors(details);
4520
+ return {
4521
+ status,
4522
+ message: backendMessage ?? this.defaultMessage(status),
4523
+ details,
4524
+ backendMessage,
4525
+ validationErrors,
4526
+ url: error.url,
4527
+ headers: error.headers,
4528
+ original: error,
4529
+ retryable: this.isRetryable(status),
4530
+ timestamp,
4531
+ };
4532
+ }
4533
+ /**
4534
+ * Extracts a human-readable message from the error response body.
4535
+ *
4536
+ * Checks common keys (`message`, `title`, `detail`, `error`) on the
4537
+ * body object and returns the first non-empty string found.
4538
+ *
4539
+ * @param body The parsed error response body.
4540
+ * @returns The extracted message, or `undefined` if none was found.
4541
+ */
4542
+ extractBackendMessage(body) {
4543
+ if (typeof body === 'string' && body.trim()) {
4544
+ return body;
4545
+ }
4546
+ if (!body || typeof body !== 'object') {
4547
+ return undefined;
4548
+ }
4549
+ const obj = body;
4550
+ for (const key of ['message', 'title', 'detail', 'error']) {
4551
+ const value = obj[key];
4552
+ if (typeof value === 'string' && value.trim()) {
4553
+ return value;
4554
+ }
4555
+ }
4556
+ return undefined;
4557
+ }
4558
+ /**
4559
+ * Extracts field-level validation errors from the error response body.
4560
+ *
4561
+ * Expects an `errors` property on the body containing a record of
4562
+ * field names to error messages (string or string array).
4563
+ *
4564
+ * @param body The parsed error response body.
4565
+ * @returns A record mapping field names to their error messages, or `undefined` if none were found.
4566
+ */
4567
+ extractValidationErrors(body) {
4568
+ if (!body || typeof body !== 'object') {
4569
+ return undefined;
4570
+ }
4571
+ const obj = body;
4572
+ const errors = obj['errors'];
4573
+ if (!errors || typeof errors !== 'object') {
4574
+ return undefined;
4575
+ }
4576
+ const result = {};
4577
+ for (const [key, value] of Object.entries(errors)) {
4578
+ if (Array.isArray(value)) {
4579
+ result[key] = value.map(String);
4580
+ }
4581
+ else if (typeof value === 'string') {
4582
+ result[key] = [value];
4583
+ }
4584
+ }
4585
+ return Object.keys(result).length ? result : undefined;
4586
+ }
4587
+ /**
4588
+ * Returns a default user-facing message for the given HTTP status code.
4589
+ *
4590
+ * Provides human-readable messages for common HTTP status codes.
4591
+ * Override this method to customise messages.
4592
+ *
4593
+ * @param status The HTTP status code, or `null` when unknown.
4594
+ * @returns A descriptive error message.
4595
+ */
4596
+ defaultMessage(status) {
4597
+ switch (status) {
4598
+ case HttpStatusCode.BadRequest:
4599
+ return 'Bad request';
4600
+ case HttpStatusCode.Unauthorized:
4601
+ return 'Unauthorized';
4602
+ case HttpStatusCode.Forbidden:
4603
+ return 'Forbidden';
4604
+ case HttpStatusCode.NotFound:
4605
+ return 'Not found';
4606
+ case HttpStatusCode.Conflict:
4607
+ return 'Conflict';
4608
+ case HttpStatusCode.UnprocessableEntity:
4609
+ return 'Unprocessable entity';
4610
+ case HttpStatusCode.InternalServerError:
4611
+ return 'Internal server error';
4612
+ case null:
4613
+ return 'Unknown error';
4614
+ default:
4615
+ return `Request failed with status ${status}`;
4616
+ }
4617
+ }
4618
+ /**
4619
+ * Determines whether a request with the given status can be retried.
4620
+ *
4621
+ * Timeouts, rate-limiting responses, and server errors
4622
+ * (5xx) are considered retryable by default.
4623
+ *
4624
+ * @param status The HTTP status code, or `null` when unknown.
4625
+ * @returns `true` if the request is safe to retry.
4626
+ */
4627
+ isRetryable(status) {
4628
+ if (status === HttpStatusCode.RequestTimeout)
4629
+ return true;
4630
+ if (status === HttpStatusCode.TooManyRequests)
4631
+ return true;
4632
+ if (typeof status === 'number' && status >= 500)
4633
+ return true;
4634
+ return false;
4635
+ }
4636
+ /**
4637
+ * Normalises a raw HTTP status into an `HttpStatusCode | null` value.
4638
+ *
4639
+ * Converts `undefined`, `NaN`, and `0` (network error) to `null`
4640
+ * so downstream code only needs to handle `HttpStatusCode | null`.
4641
+ *
4642
+ * @param status The raw status value from the HTTP response.
4643
+ * @returns The normalised status code, or `null` when indeterminate.
4644
+ */
4645
+ normalizeStatus(status) {
4646
+ if (status === null || status === undefined || status === 0 || Number.isNaN(status))
4647
+ return null;
4648
+ return status;
4649
+ }
4650
+ /**
4651
+ * Converts query parameters into Angular `HttpParams`.
4652
+ *
4653
+ * `null` and `undefined` values are silently skipped.
4654
+ * Array values are appended as multiple entries for the same key.
4655
+ *
4656
+ * @param query The query parameter record to convert.
4657
+ * @returns An `HttpParams` instance, or `undefined` when no parameters are provided.
4658
+ */
4659
+ toHttpParams(query) {
4660
+ if (!query)
4661
+ return undefined;
4662
+ let params = new HttpParams();
4663
+ for (const [key, rawValue] of Object.entries(query)) {
4664
+ if (rawValue === null || rawValue === undefined)
4665
+ continue;
4666
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
4667
+ for (const value of values) {
4668
+ if (value === null || value === undefined)
4669
+ continue;
4670
+ params = params.append(key, String(value));
4671
+ }
4672
+ }
4673
+ return params;
4674
+ }
4675
+ }
4676
+
4295
4677
  /**
4296
4678
  * Provides an APP_INITIALIZER that configures the MnLanguageService and
4297
4679
  * preloads the requested locales during application bootstrap.
@@ -4356,5 +4738,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4356
4738
  * Generated bundle index. Do not edit.
4357
4739
  */
4358
4740
 
4359
- export { ActionStyle, BackdropMode, BaseModalBuilder, CloseMode, ColumnSortType, ConfirmationModalBuilder, ConfirmationTone, CustomModalBuilder, DEFAULT_MN_ALERT_CONFIG, FieldAppearance, FieldKind, FormLayoutMode, FormModalBuilder, KeyboardMode, MN_ALERT_CONFIG, MN_CHECKBOX_CONFIG, MN_DATETIME_CONFIG, MN_INPUT_FIELD_CONFIG, MN_INSTANCE_ID, MN_LIB_DUAL_HORIZONTAL_IMAGE, MN_MULTI_SELECT_CONFIG, MN_SECTION_PATH, MN_TEST_COMPONENT_CONFIG, MN_TEXTAREA_CONFIG, MnAlertOutletComponent, MnAlertService, MnAlertStore, MnButton, MnCheckbox, MnConfigService, MnConfirmationBodyComponent, MnCustomBodyHostComponent, MnDatetime, MnDualHorizontalImage, MnFormBodyComponent, MnInformationCard, MnInputField, MnInstanceDirective, MnLanguageService, MnModalRef, MnModalService, MnModalShellComponent, MnMultiSelect, MnSectionDirective, MnTable, MnTestComponent, MnTextarea, MnTranslatePipe, MnWizardBodyComponent, ModalBuilder, ModalCloseReason, ModalIntent, ModalKind, ModalSize, NavigationDirection, OptionState, SelectionMode, StepBuilder, StepState, SubmitMode, Test, ValidationCode, ValidationStatus, WizardFlowMode, WizardModalBuilder, dateTimeAdapter, defaultTextAdapter, isTranslatable, mnAlertVariants, mnButtonVariants, mnCheckboxVariants, mnDatetimeVariants, mnInformationCardVariants, mnInputFieldVariants, mnMultiSelectVariants, mnTextareaVariants, numberAdapter, pickAdapter, provideMnAlerts, provideMnComponentConfig, provideMnConfig, provideMnLanguage };
4741
+ export { API_BASE_URL, ActionStyle, BackdropMode, BaseModalBuilder, CloseMode, ColumnSortType, ConfirmationModalBuilder, ConfirmationTone, CrudService, CustomModalBuilder, DEFAULT_MN_ALERT_CONFIG, FieldAppearance, FieldKind, FormLayoutMode, FormModalBuilder, KeyboardMode, MN_ALERT_CONFIG, MN_CHECKBOX_CONFIG, MN_DATETIME_CONFIG, MN_INPUT_FIELD_CONFIG, MN_INSTANCE_ID, MN_LIB_DUAL_HORIZONTAL_IMAGE, MN_MULTI_SELECT_CONFIG, MN_SECTION_PATH, MN_TEST_COMPONENT_CONFIG, MN_TEXTAREA_CONFIG, MnAlertOutletComponent, MnAlertService, MnAlertStore, MnButton, MnCheckbox, MnConfigService, MnConfirmationBodyComponent, MnCustomBodyHostComponent, MnDatetime, MnDualHorizontalImage, MnFormBodyComponent, MnInformationCard, MnInputField, MnInstanceDirective, MnLanguageService, MnModalRef, MnModalService, MnModalShellComponent, MnMultiSelect, MnSectionDirective, MnTable, MnTestComponent, MnTextarea, MnTranslatePipe, MnWizardBodyComponent, ModalBuilder, ModalCloseReason, ModalIntent, ModalKind, ModalSize, NavigationDirection, OptionState, SelectionMode, StepBuilder, StepState, SubmitMode, Test, ValidationCode, ValidationStatus, WizardFlowMode, WizardModalBuilder, dateTimeAdapter, defaultTextAdapter, isTranslatable, mnAlertVariants, mnButtonVariants, mnCheckboxVariants, mnDatetimeVariants, mnInformationCardVariants, mnInputFieldVariants, mnMultiSelectVariants, mnTextareaVariants, numberAdapter, pickAdapter, provideMnAlerts, provideMnComponentConfig, provideMnConfig, provideMnLanguage };
4360
4742
  //# sourceMappingURL=mn-angular-lib.mjs.map