@nutin/cli 1.2.3 → 1.3.1

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/README.md +25 -18
  3. package/lib/index.mjs +23 -20
  4. package/lib/src/context-builder.mjs +40 -0
  5. package/lib/src/file-generator.mjs +26 -126
  6. package/lib/src/git-manager.mjs +1 -1
  7. package/lib/src/json-generator.mjs +31 -0
  8. package/lib/src/package-manager.mjs +21 -18
  9. package/lib/src/print.mjs +1 -0
  10. package/lib/src/project-generator.mjs +53 -0
  11. package/lib/src/prompts.mjs +16 -75
  12. package/lib/src/template-compiler.mjs +1 -1
  13. package/lib/src/utils.mjs +9 -9
  14. package/lib/src/version.mjs +1 -0
  15. package/package.json +1 -1
  16. package/templates/base/.gitignore.hbs +1 -0
  17. package/templates/base/builder.config.js.hbs +8 -0
  18. package/templates/base/src/app/globals.d.ts.hbs +12 -8
  19. package/templates/base/src/app/main.ts.hbs +2 -1
  20. package/templates/base/src/app/views/home/home.view.test.js.hbs +26 -0
  21. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
  22. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +1 -1
  23. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +46 -26
  24. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +36 -22
  25. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +4 -4
  26. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +60 -13
  27. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +16 -17
  28. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +40 -8
  29. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +16 -16
  30. package/templates/base/src/core/base-classes/component/component.ts.hbs +20 -24
  31. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +19 -2
  32. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +2 -2
  33. package/templates/base/src/core/base-classes/service/service.ts.hbs +35 -12
  34. package/templates/base/src/core/base-classes/view/view.ts.hbs +6 -6
  35. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +13 -13
  36. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +18 -18
  37. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +24 -24
  38. package/templates/base/src/core/services/http-client/http-client.ts.hbs +19 -19
  39. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +6 -6
  40. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +15 -14
  41. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +22 -22
  42. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +4 -36
  43. package/templates/base/src/core/services/router/router.ts.hbs +45 -46
  44. package/templates/base/src/core/services/store/store.ts.hbs +6 -6
  45. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +41 -15
  46. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +40 -19
  47. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +11 -5
  48. package/templates/base/src/libs/pipes/pipes.ts.hbs +1 -1
  49. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +197 -0
  50. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +75 -0
  51. package/templates/base/src/libs/popover/popover.ts.hbs +89 -96
  52. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +7 -4
  53. package/templates/base/src/styles/base/_variables.scss.hbs +1 -0
  54. package/templates/base/src/styles/core/_anchors.scss.hbs +10 -0
  55. package/templates/base/src/styles/core/_index.scss.hbs +6 -0
  56. package/templates/base/src/styles/core/_popover.scss.hbs +96 -0
  57. package/templates/base/src/styles/core/_snackbar.scss.hbs +50 -0
  58. package/templates/base/src/styles/main.scss.hbs +1 -1
  59. package/templates/base/tools/builder/builder.js.hbs +9 -3
  60. package/templates/base/tools/builder/core/add-tags.js.hbs +46 -0
  61. package/templates/base/tools/builder/core/copy-static.js.hbs +35 -35
  62. package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -6
  63. package/templates/base/tools/builder/core/hash-files.js.hbs +52 -0
  64. package/templates/base/tools/builder/core/validate-html.js.hbs +12 -32
  65. package/templates/base/tools/utils/get-files-recursive.js.hbs +17 -8
  66. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +56 -0
  67. package/templates/{base → features/deployHelper}/tools/deployment/Dockerfile.hbs +12 -4
  68. package/templates/{base → features/deployHelper}/tools/deployment/nginx.conf.hbs +30 -2
  69. package/templates/features/i18n/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +4 -6
  70. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +76 -52
  71. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +31 -0
  72. package/templates/features/template/src/app/views/home/home.view.html.hbs +1 -1
  73. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +2 -2
  74. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +2 -2
  75. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +1 -0
  76. package/templates/features/testinNutin/testin-nutin.config.js.hbs +2 -2
  77. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  78. package/templates/base/tools/deployment/gzip.conf.hbs +0 -19
  79. package/templates/features/i18n/src/app/views/home/locales/fr.json.hbs +0 -9
  80. /package/templates/{base → features/deployHelper}/.dockerignore.hbs +0 -0
@@ -3,9 +3,9 @@ import { TokenHelper } from './token.helper.js';
3
3
 
4
4
  export class EventHelper {
5
5
  public static bindEvents(
6
- component: BaseComponent,
7
- element: HTMLElement,
8
- eventListeners: Array<[string, EventListener]>
6
+ component: BaseComponent,
7
+ element: HTMLElement,
8
+ eventListeners: Array<[EventTarget, string, EventListener]>
9
9
  ): void {
10
10
  element.querySelectorAll('[data-event]').forEach(el => {
11
11
  const parts = el.getAttribute('data-event')!.split(':');
@@ -23,6 +23,14 @@ export class EventHelper {
23
23
  });
24
24
  }
25
25
 
26
+ public static destroyEvents(
27
+ eventListeners: Array<[EventTarget, string, EventListener]>
28
+ ): void {
29
+ eventListeners.forEach(([target, event, listener]) => {
30
+ target.removeEventListener(event, listener);
31
+ });
32
+ }
33
+
26
34
  private static createBoundHandler(
27
35
  el: Element,
28
36
  component: BaseComponent,
@@ -37,21 +45,12 @@ export class EventHelper {
37
45
  }
38
46
 
39
47
  private static addEvent(
40
- target: EventTarget,
41
- event: string,
42
- listener: EventListener,
43
- eventListeners: Array<[string, EventListener]>
48
+ target: EventTarget,
49
+ event: string,
50
+ listener: EventListener,
51
+ eventListeners: Array<[EventTarget, string, EventListener]>
44
52
  ): void {
45
53
  target.addEventListener(event, listener);
46
- eventListeners.push([event, listener]);
47
- }
48
-
49
- public static destroyEvents(
50
- element: HTMLElement,
51
- eventListeners: Array<[string, EventListener]>
52
- ): void {
53
- eventListeners.forEach(([event, listener]) => {
54
- element.removeEventListener(event, listener);
55
- });
54
+ eventListeners.push([target, event, listener]);
56
55
  }
57
56
  }
@@ -1,13 +1,11 @@
1
+ export type TrustLevel = 'strict' | 'normal' | 'trusted';
2
+
1
3
  export class SecurityHelper {
2
- public static escapeHtml(value: unknown): string {
4
+ public static sanitizeTemplate(value: unknown, trustLevel: TrustLevel = 'normal'): string {
3
5
  if (value === null || value === undefined) return '';
4
- const str = String(value);
5
- return str
6
- .replace(/&/g, "&amp;")
7
- .replace(/</g, "&lt;")
8
- .replace(/>/g, "&gt;")
9
- .replace(/"/g, "&quot;")
10
- .replace(/'/g, "&#039;");
6
+ let template = String(value);
7
+
8
+ return this.sanitizeInputTemplate(template, trustLevel);
11
9
  }
12
10
 
13
11
  public static sanitizeInputElement(
@@ -21,4 +19,38 @@ export class SecurityHelper {
21
19
  }
22
20
  return '';
23
21
  }
22
+
23
+ public static escapeHtml(value: unknown): string {
24
+ if (value === null || value === undefined) return '';
25
+ const str = String(value);
26
+ return str
27
+ .replace(/&/g, "&amp;")
28
+ .replace(/</g, "&lt;")
29
+ .replace(/>/g, "&gt;")
30
+ .replace(/"/g, "&quot;")
31
+ .replace(/'/g, "&#039;");
32
+ }
33
+
34
+ private static sanitizeInputTemplate(template: string, trustLevel: TrustLevel): string {
35
+ // Trusted: no tamplate sanitization
36
+ if (trustLevel === 'trusted') {
37
+ return template;
38
+ }
39
+
40
+ // Always remove scripts and inline event handlers
41
+ template = template
42
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
43
+ .replace(/\son\w+\s*=\s*["'][^"']*["']/gi, '');
44
+
45
+ // Strict: remove iframe, object, embed, href, data: protocol
46
+ if (trustLevel === 'strict') {
47
+ template = template
48
+ .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
49
+ .replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
50
+ .replace(/<embed\b[^>]*>/gi, '')
51
+ .replace(/href\s*=\s*["']javascript:[^"']*["']/gi, '')
52
+ .replace(/src\s*=\s*["']data:[^"']*["']/gi, '');
53
+ }
54
+ return template;
55
+ }
24
56
  }
@@ -47,6 +47,22 @@ export class TokenHelper {
47
47
  );
48
48
  }
49
49
 
50
+ /** Register a new exact token like "@foo" */
51
+ public static registerCustomToken(
52
+ name: string,
53
+ resolver: (el: HTMLElement, ev: Event) => any
54
+ ): void {
55
+ this.customResolvers[name] = resolver;
56
+ }
57
+
58
+ /** Register a new prefixed token like "@style:" */
59
+ public static registerPrefixedToken(
60
+ prefix: string,
61
+ resolver: (suffix: string, el: HTMLElement) => any
62
+ ): void {
63
+ PREFIXED_TOKEN_RESOLVERS[prefix] = resolver;
64
+ }
65
+
50
66
  private static resolveExact(token: string, el: HTMLElement, ev: Event): any | null {
51
67
  const resolver = EXACT_TOKEN_RESOLVERS[token];
52
68
  return resolver ? resolver(el, ev) : null;
@@ -80,20 +96,4 @@ export class TokenHelper {
80
96
  }
81
97
  return null;
82
98
  }
83
-
84
- /** Register a new exact token like "@foo" */
85
- public static registerCustomToken(
86
- name: string,
87
- resolver: (el: HTMLElement, ev: Event) => any
88
- ) {
89
- this.customResolvers[name] = resolver;
90
- }
91
-
92
- /** Register a new prefixed token like "@style:" */
93
- public static registerPrefixedToken(
94
- prefix: string,
95
- resolver: (suffix: string, el: HTMLElement) => any
96
- ) {
97
- PREFIXED_TOKEN_RESOLVERS[prefix] = resolver;
98
- }
99
99
  }
@@ -56,20 +56,30 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
56
56
  normalizeKeys = [],
57
57
  defaults = {},
58
58
  }: ComponentOptions) {
59
- let normalizedConfig = ConfigHelper.normalize(config, defaults);
60
- normalizedConfig = ConfigHelper.normalizeStrings(normalizedConfig, normalizeKeys);
61
-
62
- const template = templateFn(normalizedConfig);
63
-
59
+ const template = ConfigHelper.createNormalizedTemplate({
60
+ config, defaults, normalizeKeys, templateFn,
61
+ });
64
62
  super({template, mountTarget, tagName});
65
63
 
66
- this.config = normalizeKeys && config
67
- ? ConfigHelper.normalizeStrings(config, normalizeKeys)
68
- : config ?? ({} as T);
69
-
64
+ this.config = ConfigHelper.setConfigValue(config, normalizeKeys);
70
65
  this.props = props;
66
+ this.buttonManager = this.createButtonManager();
67
+ }
68
+
69
+ public getValues(): Record<string, string> {
70
+ return DataBindingHelper.getDataBindingValues(this.element);
71
+ }
72
+
73
+ public override render(): HTMLElement {
74
+ this.applyProps();
75
+ this.appendDynamicButtons();
76
+ this.autoBindEvents();
77
+ this.parseDataAttributes();
78
+ return super.render();
79
+ }
71
80
 
72
- this.buttonManager = new ButtonManager(
81
+ private createButtonManager(): ButtonManager {
82
+ return new ButtonManager(
73
83
  this,
74
84
  this.props.buttons,
75
85
  { containerClassName: 'component-buttons' }
@@ -80,11 +90,9 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
80
90
  if (this.props.className) {
81
91
  this.element.classList.add(this.props.className);
82
92
  }
83
-
84
93
  if (this.props.style) {
85
94
  this.element.style = this.props.style;
86
95
  }
87
-
88
96
  this.applyDataBindings();
89
97
  }
90
98
 
@@ -95,16 +103,4 @@ export abstract class Component<T extends HTMLElement = HTMLElement, K = any> ex
95
103
  private applyDataBindings(): void {
96
104
  DataBindingHelper.applyDataBindings(this.element, this.props);
97
105
  }
98
-
99
- public getValues(): Record<string, string> {
100
- return DataBindingHelper.getDataBindingValues(this.element);
101
- }
102
-
103
- public override render(): HTMLElement {
104
- this.applyProps();
105
- this.appendDynamicButtons();
106
- this.autoBindEvents();
107
- this.parseDataAttributes();
108
- return super.render();
109
- }
110
106
  }
@@ -1,5 +1,5 @@
1
1
  export class ConfigHelper {
2
- static normalize<T extends Record<string, any>>(
2
+ public static normalize<T extends Record<string, any>>(
3
3
  config: T,
4
4
  defaults: Partial<T>
5
5
  ): T {
@@ -9,7 +9,7 @@ export class ConfigHelper {
9
9
  } as T;
10
10
  }
11
11
 
12
- static normalizeStrings<T extends Record<string, any>>(
12
+ public static normalizeStrings<T extends Record<string, any>>(
13
13
  config: T,
14
14
  stringKeys: (keyof T)[]
15
15
  ): T {
@@ -23,4 +23,21 @@ export class ConfigHelper {
23
23
 
24
24
  return normalized;
25
25
  }
26
+
27
+ public static createNormalizedTemplate<T extends Record<string, any>>({
28
+ config,
29
+ defaults = {},
30
+ normalizeKeys = [],
31
+ templateFn = () => ''
32
+ }: T) {
33
+ let normalizedConfig = ConfigHelper.normalize(config, defaults);
34
+ normalizedConfig = ConfigHelper.normalizeStrings(normalizedConfig, normalizeKeys);
35
+ return templateFn(normalizedConfig);
36
+ }
37
+
38
+ public static setConfigValue<T extends Record<string, any>>(config: T, normalizeKeys: (keyof T)[]): T {
39
+ if (normalizeKeys) return ConfigHelper.normalizeStrings(config, normalizeKeys) as T;
40
+ else if (config) return config as T
41
+ else return ({} as T);
42
+ }
26
43
  }
@@ -1,7 +1,7 @@
1
1
  import { ComponentProps } from "../component.js";
2
2
 
3
3
  export class DataBindingHelper {
4
- static applyDataBindings(element: HTMLElement, props: ComponentProps): void {
4
+ public static applyDataBindings(element: HTMLElement, props: ComponentProps): void {
5
5
  const bindEls = element.querySelectorAll('[data-bind]');
6
6
  bindEls.forEach(el => {
7
7
  const key = el.getAttribute('data-bind');
@@ -18,7 +18,7 @@ export class DataBindingHelper {
18
18
  });
19
19
  }
20
20
 
21
- static getDataBindingValues(element: HTMLElement): Record<string, string> {
21
+ public static getDataBindingValues(element: HTMLElement): Record<string, string> {
22
22
  const values: Record<string, string> = {};
23
23
  element.querySelectorAll('[data-bind]').forEach(el => {
24
24
  const key = el.getAttribute('data-bind');
@@ -15,16 +15,10 @@ export abstract class Service<T extends Service<T>> {
15
15
  `${constructor.name} is a Service. Use ${constructor.name}.getInstance() instead.`
16
16
  );
17
17
  }
18
+ this.autoBindMethods();
18
19
  window.addEventListener('beforeunload', this.dispose);
19
20
  }
20
21
 
21
- /**
22
- * For async and custom cleanup operations
23
- */
24
- protected onDestroy<T extends Service<T>>(this: () => T): void {
25
- console.log(`No onDestroy operations.`)
26
- }
27
-
28
22
  public static createInstance<T extends Service<T>>(
29
23
  constructor: new (...args: any[]) => T,
30
24
  ...args: any[]
@@ -62,11 +56,7 @@ export abstract class Service<T extends Service<T>> {
62
56
  }
63
57
  }
64
58
 
65
- protected registerCleanup(callback: () => void) {
66
- this._cleanupCallbacks.push(callback);
67
- }
68
-
69
- public dispose = () => {
59
+ public dispose = (): void => {
70
60
  this._cleanupCallbacks.forEach(fn => fn());
71
61
  this._cleanupCallbacks = [];
72
62
  Service._instances.delete(this.constructor);
@@ -81,6 +71,39 @@ export abstract class Service<T extends Service<T>> {
81
71
  Service._instantiating.clear();
82
72
  }
83
73
 
74
+ protected registerCleanup(callback: () => void): void {
75
+ this._cleanupCallbacks.push(callback);
76
+ }
77
+
78
+ /**
79
+ * For async and custom cleanup operations
80
+ */
81
+ protected onDestroy<T extends Service<T>>(this: () => T): void {
82
+ console.log(`No onDestroy operations.`)
83
+ }
84
+
85
+ private autoBindMethods(): void {
86
+ let proto = Object.getPrototypeOf(this);
87
+
88
+ while (proto && proto !== Service.prototype) {
89
+ const propertyNames = Object.getOwnPropertyNames(proto);
90
+
91
+ for (const key of propertyNames) {
92
+ const descriptor = Object.getOwnPropertyDescriptor(proto, key);
93
+
94
+ if (
95
+ key !== 'constructor' &&
96
+ descriptor &&
97
+ typeof descriptor.value === 'function'
98
+ ) {
99
+ (this as any)[key] = descriptor.value.bind(this);
100
+ }
101
+ }
102
+
103
+ proto = Object.getPrototypeOf(proto);
104
+ }
105
+ }
106
+
84
107
  /**
85
108
  * Resets the Service instance without cleanup.
86
109
  */
@@ -18,12 +18,6 @@ export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComp
18
18
  this.viewName = viewName || this.getKebabCaseViewName();
19
19
  }
20
20
 
21
- private getKebabCaseViewName(): string {
22
- const className = this.constructor.name;
23
- const baseName = className.replace(/View$/, '');
24
- return baseName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
25
- }
26
-
27
21
  /**
28
22
  * Set route parameters for this view instance
29
23
  * Called by the router when the view is instantiated
@@ -74,4 +68,10 @@ export abstract class View<T extends HTMLElement = HTMLElement> extends BaseComp
74
68
  super.destroy();
75
69
  this.onExit();
76
70
  }
71
+
72
+ private getKebabCaseViewName(): string {
73
+ const className = this.constructor.name;
74
+ const baseName = className.replace(/View$/, '');
75
+ return baseName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
76
+ }
77
77
  }
@@ -34,18 +34,6 @@ export class EventBus extends Service<EventBus> {
34
34
  this.addHandler(event, callback, true);
35
35
  }
36
36
 
37
- private addHandler<K extends EventKey>(
38
- event: K,
39
- callback: (data: EventMap[K]) => void,
40
- once: boolean
41
- ) {
42
- if (!this.handlers[event]) {
43
- this.handlers[event] = [];
44
- }
45
- this.handlers[event]!.push(callback);
46
- this._subscriptions.push({ event, callback, once });
47
- }
48
-
49
37
  public emit<K extends EventKey>(event: K, data?: EventMap[K]): void {
50
38
  const callbacks = this.handlers[event];
51
39
  if (!callbacks) return;
@@ -80,7 +68,19 @@ export class EventBus extends Service<EventBus> {
80
68
  );
81
69
  }
82
70
 
83
- private cleanupEventListeners = () => {
71
+ private addHandler<K extends EventKey>(
72
+ event: K,
73
+ callback: (data: EventMap[K]) => void,
74
+ once: boolean
75
+ ): void {
76
+ if (!this.handlers[event]) {
77
+ this.handlers[event] = [];
78
+ }
79
+ this.handlers[event]!.push(callback);
80
+ this._subscriptions.push({ event, callback, once });
81
+ }
82
+
83
+ private cleanupEventListeners = (): void => {
84
84
  this._subscriptions.forEach(({ event, callback }) => {
85
85
  this.off(event, callback);
86
86
  });
@@ -12,11 +12,21 @@ export interface IRequestConfig {
12
12
  }
13
13
 
14
14
  export class HttpBuilder {
15
- static buildRequestBody(data: unknown): string | undefined {
16
- return data ? JSON.stringify(data) : undefined;
15
+ public static buildRequestOptions(
16
+ method: HttpMethod,
17
+ data: unknown,
18
+ config: IRequestConfig,
19
+ signal: AbortSignal
20
+ ): RequestInit {
21
+ return {
22
+ method,
23
+ headers: config.headers,
24
+ body: this.buildRequestBody(data),
25
+ signal
26
+ };
17
27
  }
18
28
 
19
- static buildRequestUrl(endpoint: string, queryParams?: Record<string, string>): URL {
29
+ public static buildRequestUrl(endpoint: string, queryParams?: Record<string, string>): URL {
20
30
  const url = new URL(endpoint);
21
31
 
22
32
  if (queryParams) {
@@ -26,23 +36,13 @@ export class HttpBuilder {
26
36
  return url;
27
37
  }
28
38
 
29
- static appendQueryParams(url: URL, queryParams: Record<string, string>): void {
39
+ private static buildRequestBody(data: unknown): string | undefined {
40
+ return data ? JSON.stringify(data) : undefined;
41
+ }
42
+
43
+ private static appendQueryParams(url: URL, queryParams: Record<string, string>): void {
30
44
  Object.entries(queryParams).forEach(([key, value]) => {
31
45
  url.searchParams.append(key, value);
32
46
  });
33
47
  }
34
-
35
- static buildRequestOptions(
36
- method: HttpMethod,
37
- data: unknown,
38
- config: IRequestConfig,
39
- signal: AbortSignal
40
- ): RequestInit {
41
- return {
42
- method,
43
- headers: config.headers,
44
- body: this.buildRequestBody(data),
45
- signal
46
- };
47
- }
48
48
  }
@@ -16,7 +16,7 @@ export class HttpError extends Error {
16
16
  }
17
17
 
18
18
  export class HttpManager {
19
- static createAbortController(timeout?: number): IAbortControllerSetup {
19
+ public static createAbortController(timeout?: number): IAbortControllerSetup {
20
20
  const controller = new AbortController();
21
21
  const timeoutId = timeout ?
22
22
  setTimeout(() => controller.abort(), timeout) :
@@ -25,25 +25,13 @@ export class HttpManager {
25
25
  return { controller, timeoutId };
26
26
  }
27
27
 
28
- static async safeParseErrorResponse(response: Response): Promise<unknown> {
29
- try {
30
- return await response.json();
31
- } catch {
32
- return null;
33
- }
34
- }
35
-
36
- static async validateResponse(response: Response): Promise<void> {
28
+ public static async validateResponse(response: Response): Promise<void> {
37
29
  if (!response.ok) {
38
30
  await this.handleResponseError(response);
39
31
  }
40
32
  }
41
-
42
- static isJsonResponse(contentType: string | null): boolean {
43
- return contentType?.includes('application/json') ?? false;
44
- }
45
33
 
46
- static async parseSuccessResponse<T>(response: Response): Promise<T> {
34
+ public static async parseSuccessResponse<T>(response: Response): Promise<T> {
47
35
  const contentType = response.headers.get('content-type');
48
36
 
49
37
  if (this.isJsonResponse(contentType)) {
@@ -53,25 +41,37 @@ export class HttpManager {
53
41
  return await response.text() as unknown as T;
54
42
  }
55
43
 
56
- static isTimeoutError(error: unknown): boolean {
57
- return error instanceof DOMException && error.name === 'AbortError';
58
- }
59
-
60
- static handleRequestError(error: unknown): never {
44
+ public static handleRequestError(error: unknown): never {
61
45
  if (this.isTimeoutError(error)) {
62
46
  throw new Error('Request timed out');
63
47
  }
64
48
  throw error;
65
49
  }
66
50
 
67
- static async handleResponseError(response: Response): Promise<never> {
51
+ public static cleanupTimeout(timeoutId: TimeoutType | null): void {
52
+ if (timeoutId) {
53
+ clearTimeout(timeoutId);
54
+ }
55
+ }
56
+
57
+ private static async handleResponseError(response: Response): Promise<never> {
68
58
  const errorData = await this.safeParseErrorResponse(response);
69
59
  throw new HttpError(response.status, response.statusText, errorData);
70
60
  }
71
61
 
72
- static cleanupTimeout(timeoutId: TimeoutType | null): void {
73
- if (timeoutId) {
74
- clearTimeout(timeoutId);
62
+ private static isJsonResponse(contentType: string | null): boolean {
63
+ return contentType?.includes('application/json') ?? false;
64
+ }
65
+
66
+ private static async safeParseErrorResponse(response: Response): Promise<unknown> {
67
+ try {
68
+ return await response.json();
69
+ } catch {
70
+ return null;
75
71
  }
76
72
  }
73
+
74
+ private static isTimeoutError(error: unknown): boolean {
75
+ return error instanceof DOMException && error.name === 'AbortError';
76
+ }
77
77
  }
@@ -17,15 +17,31 @@ export class HttpClient extends Service<HttpClient> {
17
17
  this._defaultHeaders = { 'Content-Type': 'application/json', ...defaultHeaders };
18
18
  }
19
19
 
20
- public addRequestInterceptor(fn: (url: string, options: RequestInit) => void) {
20
+ public get<T = unknown>(endpoint: string, config?: IRequestConfig): Promise<T> {
21
+ return this.request<T>('GET', endpoint, null, config);
22
+ }
23
+ public post<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
24
+ return this.request<T>('POST', endpoint, data, config);
25
+ }
26
+ public put<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
27
+ return this.request<T>('PUT', endpoint, data, config);
28
+ }
29
+ public patch<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig): Promise<T> {
30
+ return this.request<T>('PATCH', endpoint, data, config);
31
+ }
32
+ public delete<T = unknown>(endpoint: string, config?: IRequestConfig): Promise<T> {
33
+ return this.request<T>('DELETE', endpoint, undefined, config);
34
+ }
35
+
36
+ public addRequestInterceptor(fn: (url: string, options: RequestInit) => void): void {
21
37
  this._requestInterceptors.push(fn);
22
38
  }
23
39
 
24
- public addResponseInterceptor(fn: (response: Response) => void) {
40
+ public addResponseInterceptor(fn: (response: Response) => void): void {
25
41
  this._responseInterceptors.push(fn);
26
42
  }
27
43
 
28
- onDestroy(): void {
44
+ protected onDestroy(): void {
29
45
  this._baseUrl = '';
30
46
  this._defaultHeaders = {};
31
47
  this._requestInterceptors = [];
@@ -60,22 +76,6 @@ export class HttpClient extends Service<HttpClient> {
60
76
  HttpManager.cleanupTimeout(timeoutId);
61
77
  }
62
78
  }
63
-
64
- public get<T = unknown>(endpoint: string, config?: IRequestConfig) {
65
- return this.request<T>('GET', endpoint, null, config);
66
- }
67
- public post<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
68
- return this.request<T>('POST', endpoint, data, config);
69
- }
70
- public put<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
71
- return this.request<T>('PUT', endpoint, data, config);
72
- }
73
- public patch<T = unknown>(endpoint: string, data?: unknown, config?: IRequestConfig) {
74
- return this.request<T>('PATCH', endpoint, data, config);
75
- }
76
- public delete<T = unknown>(endpoint: string, config?: IRequestConfig) {
77
- return this.request<T>('DELETE', endpoint, undefined, config);
78
- }
79
79
  }
80
80
 
81
81
  export const AppHttpClient = HttpClient.getInstance();
@@ -7,11 +7,7 @@ class PipeRegistry extends Service<PipeRegistry> {
7
7
 
8
8
  constructor() { super(); }
9
9
 
10
- onDestroy() {
11
- this._pipes = {};
12
- }
13
-
14
- register(name: string, fn: PipeFunction): void {
10
+ public register(name: string, fn: PipeFunction): void {
15
11
  if(this._pipes[name]) {
16
12
  console.warn(`Skipping pipe "${name}": A pipe with this name already exists.`);
17
13
  return;
@@ -19,7 +15,7 @@ class PipeRegistry extends Service<PipeRegistry> {
19
15
  this._pipes[name] = fn;
20
16
  }
21
17
 
22
- apply(name: string, value: any, args: string[] = []): string {
18
+ public apply(name: string, value: any, args: string[] = []): string {
23
19
  const pipe = this._pipes[name];
24
20
  if (!pipe) {
25
21
  console.warn(`Pipe "${name}" not found.`);
@@ -27,6 +23,10 @@ class PipeRegistry extends Service<PipeRegistry> {
27
23
  }
28
24
  return pipe(value, ...args);
29
25
  }
26
+
27
+ protected onDestroy(): void {
28
+ this._pipes = {};
29
+ }
30
30
  }
31
31
 
32
32
  export const AppPipeRegistry = PipeRegistry.getInstance();