@harpy-js/core 0.4.7

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 (147) hide show
  1. package/README.md +326 -0
  2. package/dist/cli.d.ts +12 -0
  3. package/dist/cli.js +53 -0
  4. package/dist/client/Link.d.ts +5 -0
  5. package/dist/client/Link.js +62 -0
  6. package/dist/client/__tests__/getActiveItemId.test.d.ts +1 -0
  7. package/dist/client/__tests__/getActiveItemId.test.js +38 -0
  8. package/dist/client/getActiveItemId.d.ts +7 -0
  9. package/dist/client/getActiveItemId.js +55 -0
  10. package/dist/client/use-i18n.d.ts +7 -0
  11. package/dist/client/use-i18n.js +64 -0
  12. package/dist/core/__tests__/component-analyzer.test.d.ts +1 -0
  13. package/dist/core/__tests__/component-analyzer.test.js +151 -0
  14. package/dist/core/__tests__/hydration-manifest.test.d.ts +1 -0
  15. package/dist/core/__tests__/hydration-manifest.test.js +211 -0
  16. package/dist/core/__tests__/jsx.engine.test.d.ts +1 -0
  17. package/dist/core/__tests__/jsx.engine.test.js +118 -0
  18. package/dist/core/app-setup.d.ts +7 -0
  19. package/dist/core/app-setup.js +79 -0
  20. package/dist/core/auto-register.module.d.ts +9 -0
  21. package/dist/core/auto-register.module.js +18 -0
  22. package/dist/core/auto-wrap-middleware.d.ts +4 -0
  23. package/dist/core/auto-wrap-middleware.js +130 -0
  24. package/dist/core/client-component-wrapper.d.ts +5 -0
  25. package/dist/core/client-component-wrapper.js +37 -0
  26. package/dist/core/client-hydration.d.ts +2 -0
  27. package/dist/core/client-hydration.js +93 -0
  28. package/dist/core/client-wrapper-browser.d.ts +2 -0
  29. package/dist/core/client-wrapper-browser.js +22 -0
  30. package/dist/core/component-analyzer.d.ts +4 -0
  31. package/dist/core/component-analyzer.js +98 -0
  32. package/dist/core/component-auto-wrapper.d.ts +2 -0
  33. package/dist/core/component-auto-wrapper.js +63 -0
  34. package/dist/core/component-client-wrapper.d.ts +4 -0
  35. package/dist/core/component-client-wrapper.js +80 -0
  36. package/dist/core/hydration-generator.d.ts +2 -0
  37. package/dist/core/hydration-generator.js +98 -0
  38. package/dist/core/hydration-manifest.d.ts +7 -0
  39. package/dist/core/hydration-manifest.js +83 -0
  40. package/dist/core/hydration.d.ts +16 -0
  41. package/dist/core/hydration.js +72 -0
  42. package/dist/core/jsx.engine.d.ts +9 -0
  43. package/dist/core/jsx.engine.js +161 -0
  44. package/dist/core/live-reload-client.js +32 -0
  45. package/dist/core/live-reload.controller.d.ts +10 -0
  46. package/dist/core/live-reload.controller.js +38 -0
  47. package/dist/core/navigation.service.d.ts +18 -0
  48. package/dist/core/navigation.service.js +206 -0
  49. package/dist/core/router.module.d.ts +2 -0
  50. package/dist/core/router.module.js +21 -0
  51. package/dist/core/static-assets.controller.d.ts +4 -0
  52. package/dist/core/static-assets.controller.js +51 -0
  53. package/dist/core/types/nav.types.d.ts +22 -0
  54. package/dist/core/types/nav.types.js +2 -0
  55. package/dist/core/views/layout.d.ts +8 -0
  56. package/dist/core/views/layout.js +35 -0
  57. package/dist/decorators/jsx.decorator.d.ts +26 -0
  58. package/dist/decorators/jsx.decorator.js +10 -0
  59. package/dist/decorators/layout.decorator.d.ts +4 -0
  60. package/dist/decorators/layout.decorator.js +29 -0
  61. package/dist/i18n/__tests__/i18n.helper.test.d.ts +1 -0
  62. package/dist/i18n/__tests__/i18n.helper.test.js +105 -0
  63. package/dist/i18n/__tests__/i18n.interceptor.test.d.ts +1 -0
  64. package/dist/i18n/__tests__/i18n.interceptor.test.js +195 -0
  65. package/dist/i18n/__tests__/i18n.module.test.d.ts +1 -0
  66. package/dist/i18n/__tests__/i18n.module.test.js +83 -0
  67. package/dist/i18n/__tests__/i18n.service.test.d.ts +1 -0
  68. package/dist/i18n/__tests__/i18n.service.test.js +109 -0
  69. package/dist/i18n/__tests__/t.test.d.ts +1 -0
  70. package/dist/i18n/__tests__/t.test.js +66 -0
  71. package/dist/i18n/i18n-module.options.d.ts +10 -0
  72. package/dist/i18n/i18n-module.options.js +4 -0
  73. package/dist/i18n/i18n-switcher.controller.d.ts +12 -0
  74. package/dist/i18n/i18n-switcher.controller.js +80 -0
  75. package/dist/i18n/i18n-types.d.ts +8 -0
  76. package/dist/i18n/i18n-types.js +2 -0
  77. package/dist/i18n/i18n.helper.d.ts +14 -0
  78. package/dist/i18n/i18n.helper.js +70 -0
  79. package/dist/i18n/i18n.interceptor.d.ts +9 -0
  80. package/dist/i18n/i18n.interceptor.js +99 -0
  81. package/dist/i18n/i18n.module.d.ts +5 -0
  82. package/dist/i18n/i18n.module.js +51 -0
  83. package/dist/i18n/i18n.service.d.ts +12 -0
  84. package/dist/i18n/i18n.service.js +61 -0
  85. package/dist/i18n/index.d.ts +10 -0
  86. package/dist/i18n/index.js +20 -0
  87. package/dist/i18n/locale.decorator.d.ts +1 -0
  88. package/dist/i18n/locale.decorator.js +8 -0
  89. package/dist/i18n/t.d.ts +3 -0
  90. package/dist/i18n/t.js +16 -0
  91. package/dist/index.d.ts +19 -0
  92. package/dist/index.js +40 -0
  93. package/package.json +79 -0
  94. package/scripts/analyze-styles.ts +124 -0
  95. package/scripts/auto-wrap-exports.ts +239 -0
  96. package/scripts/build-css.ts +38 -0
  97. package/scripts/build-hydration.ts +313 -0
  98. package/scripts/build-page-styles.ts +43 -0
  99. package/scripts/copy-assets.ts +34 -0
  100. package/scripts/dev.sh +3 -0
  101. package/scripts/dev.ts +257 -0
  102. package/src/cli.ts +71 -0
  103. package/src/client/Link.tsx +62 -0
  104. package/src/client/__tests__/getActiveItemId.test.ts +49 -0
  105. package/src/client/getActiveItemId.ts +54 -0
  106. package/src/client/use-i18n.ts +111 -0
  107. package/src/core/__tests__/component-analyzer.test.ts +141 -0
  108. package/src/core/__tests__/hydration-manifest.test.ts +223 -0
  109. package/src/core/__tests__/jsx.engine.test.ts +137 -0
  110. package/src/core/app-setup.ts +114 -0
  111. package/src/core/auto-register.module.ts +30 -0
  112. package/src/core/auto-wrap-middleware.ts +165 -0
  113. package/src/core/client-component-wrapper.ts +72 -0
  114. package/src/core/client-hydration.tsx +99 -0
  115. package/src/core/client-wrapper-browser.ts +40 -0
  116. package/src/core/component-analyzer.ts +89 -0
  117. package/src/core/component-auto-wrapper.ts +68 -0
  118. package/src/core/component-client-wrapper.ts +112 -0
  119. package/src/core/hydration-generator.ts +94 -0
  120. package/src/core/hydration-manifest.ts +79 -0
  121. package/src/core/hydration.ts +70 -0
  122. package/src/core/jsx.engine.ts +205 -0
  123. package/src/core/live-reload-client.js +32 -0
  124. package/src/core/live-reload.controller.ts +55 -0
  125. package/src/core/navigation.service.ts +257 -0
  126. package/src/core/router.module.ts +9 -0
  127. package/src/core/static-assets.controller.ts +19 -0
  128. package/src/core/types/nav.types.ts +53 -0
  129. package/src/core/views/layout.tsx +61 -0
  130. package/src/decorators/jsx.decorator.ts +49 -0
  131. package/src/decorators/layout.decorator.ts +66 -0
  132. package/src/i18n/__tests__/i18n.helper.test.ts +126 -0
  133. package/src/i18n/__tests__/i18n.interceptor.test.ts +229 -0
  134. package/src/i18n/__tests__/i18n.module.test.ts +98 -0
  135. package/src/i18n/__tests__/i18n.service.test.ts +129 -0
  136. package/src/i18n/__tests__/t.test.ts +88 -0
  137. package/src/i18n/i18n-module.options.ts +53 -0
  138. package/src/i18n/i18n-switcher.controller.ts +99 -0
  139. package/src/i18n/i18n-types.ts +56 -0
  140. package/src/i18n/i18n.helper.ts +75 -0
  141. package/src/i18n/i18n.interceptor.ts +114 -0
  142. package/src/i18n/i18n.module.ts +45 -0
  143. package/src/i18n/i18n.service.ts +95 -0
  144. package/src/i18n/index.ts +37 -0
  145. package/src/i18n/locale.decorator.ts +10 -0
  146. package/src/i18n/t.ts +62 -0
  147. package/src/index.ts +31 -0
@@ -0,0 +1,95 @@
1
+ import { Injectable, Scope, Inject } from "@nestjs/common";
2
+ import { REQUEST } from "@nestjs/core";
3
+ import { I18N_MODULE_OPTIONS } from "./i18n-module.options";
4
+ import type { I18nModuleOptions } from "./i18n-module.options";
5
+
6
+ /**
7
+ * I18n Service - Request-scoped service for translations
8
+ *
9
+ * NOTE: This service uses a generic dictionary type. In your application,
10
+ * you should import your specific Dictionary type from get-dictionary.ts
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { I18nService } from '@harpy-js/core';
15
+ *
16
+ * @Controller()
17
+ * export class MyController {
18
+ * constructor(private i18n: I18nService) {}
19
+ *
20
+ * @Get()
21
+ * async getPage() {
22
+ * const dict = await this.i18n.getDict();
23
+ * const locale = this.i18n.getLocale();
24
+ * return { dict, locale };
25
+ * }
26
+ * }
27
+ * ```
28
+ */
29
+ @Injectable({ scope: Scope.REQUEST })
30
+ export class I18nService {
31
+ private dict: Record<string, any> | null = null;
32
+ private getDictionaryFn: ((locale: string) => Promise<any>) | null = null;
33
+
34
+ constructor(
35
+ @Inject(REQUEST) private request: any,
36
+ @Inject(I18N_MODULE_OPTIONS) private options: I18nModuleOptions,
37
+ ) {}
38
+
39
+ /**
40
+ * Register a custom dictionary loader function
41
+ * This should be called in your application's getDictionary.ts
42
+ */
43
+ registerDictionaryLoader(fn: (locale: string) => Promise<any>): void {
44
+ this.getDictionaryFn = fn;
45
+ }
46
+
47
+ /**
48
+ * Get the current dictionary
49
+ * Returns empty object if no dictionary loader is registered
50
+ */
51
+ async getDict(): Promise<Record<string, any>> {
52
+ if (!this.dict) {
53
+ const locale = this.request.locale || this.options.defaultLocale;
54
+
55
+ if (this.getDictionaryFn) {
56
+ this.dict = await this.getDictionaryFn(locale);
57
+ } else {
58
+ // Return empty object if no loader registered
59
+ console.warn(
60
+ "No dictionary loader registered. Call registerDictionaryLoader() in your app.",
61
+ );
62
+ this.dict = {};
63
+ }
64
+ }
65
+ return this.dict ?? {};
66
+ }
67
+
68
+ /**
69
+ * Get the current locale
70
+ */
71
+ getLocale(): string {
72
+ return this.request.locale || this.options.defaultLocale;
73
+ }
74
+
75
+ /**
76
+ * Translate a key with optional variables
77
+ * This is a helper method - for type-safe translations, use the t() function directly
78
+ */
79
+ async translate(
80
+ key: string,
81
+ vars?: Record<string, string | number>,
82
+ ): Promise<string> {
83
+ const dict = await this.getDict();
84
+
85
+ // Simple nested key resolution
86
+ const value = key.split(".").reduce((acc, k) => acc?.[k], dict as any);
87
+
88
+ if (typeof value !== "string") return "";
89
+
90
+ // Variable interpolation
91
+ return value.replace(/\{\{(.*?)\}\}/g, (_match: string, k: string) =>
92
+ String(vars?.[k.trim()] ?? ""),
93
+ );
94
+ }
95
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * I18n Module - Internationalization for Harpy Framework
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * // In app.module.ts
7
+ * import { I18nModule } from '@harpy-js/core';
8
+ *
9
+ * @Module({
10
+ * imports: [
11
+ * I18nModule.forRoot({
12
+ * defaultLocale: 'en',
13
+ * locales: ['en', 'fr', 'es'],
14
+ * urlPattern: 'query', // or 'header'
15
+ * translationsPath: '../dictionaries',
16
+ * }),
17
+ * ],
18
+ * })
19
+ * export class AppModule {}
20
+ * ```
21
+ */
22
+
23
+ export { I18nModule } from "./i18n.module";
24
+ export { I18nService } from "./i18n.service";
25
+ export { I18nInterceptor } from "./i18n.interceptor";
26
+ export { I18nHelper } from "./i18n.helper";
27
+ export { I18nSwitcherController } from "./i18n-switcher.controller";
28
+ export { CurrentLocale } from "./locale.decorator";
29
+ export { t, tUnsafe } from "./t";
30
+ export { I18N_MODULE_OPTIONS } from "./i18n-module.options";
31
+ export type { I18nModuleOptions, I18nUrlPattern } from "./i18n-module.options";
32
+ export type {
33
+ NestedKeyOf,
34
+ DeepValue,
35
+ ExtractVariables,
36
+ RequiresVariables,
37
+ } from "./i18n-types";
@@ -0,0 +1,10 @@
1
+ import { createParamDecorator, ExecutionContext } from "@nestjs/common";
2
+
3
+ export const CurrentLocale = createParamDecorator(
4
+ (data: unknown, ctx: ExecutionContext): string => {
5
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
6
+ const request = ctx.switchToHttp().getRequest();
7
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
8
+ return request.locale;
9
+ },
10
+ );
package/src/i18n/t.ts ADDED
@@ -0,0 +1,62 @@
1
+ import type { NestedKeyOf, DeepValue, ExtractVariables } from "./i18n-types";
2
+
3
+ /**
4
+ * Type-safe translation function - extracts translated values from dictionary
5
+ * Provides full type safety including:
6
+ * - Key autocomplete from dictionary structure
7
+ * - Required variables detection and type checking
8
+ * - Return type inference
9
+ *
10
+ * @param dict - The dictionary object containing translations
11
+ * @param key - The key to lookup (supports nested keys with dot notation)
12
+ * @param vars - Optional variables for interpolation using {{variable}} syntax
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Simple translation
17
+ * t(dict, 'welcome') // Type-safe key, returns string
18
+ *
19
+ * // Nested key
20
+ * t(dict, 'nav.home') // Autocomplete for nested keys
21
+ *
22
+ * // With variables
23
+ * t(dict, 'greeting', { name: 'John' }) // Variables are type-checked
24
+ * ```
25
+ */
26
+ export function t<
27
+ TDict extends Record<string, any>,
28
+ TKey extends NestedKeyOf<TDict>,
29
+ >(
30
+ dict: TDict,
31
+ key: TKey,
32
+ vars?: DeepValue<TDict, TKey> extends string
33
+ ? ExtractVariables<DeepValue<TDict, TKey>>
34
+ : Record<string, string | number>,
35
+ ): string {
36
+ // Resolve nested keys
37
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
38
+ const value = key.split(".").reduce((acc, k) => acc?.[k], dict as any);
39
+
40
+ if (typeof value !== "string") return "";
41
+ return value.replace(/\{\{(.*?)\}\}/g, (_match: string, k: string) =>
42
+ String(vars?.[k.trim()] ?? ""),
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Unsafe version of t() for dynamic keys or when types are not available
48
+ * Use this only when you need to bypass type checking
49
+ */
50
+ export function tUnsafe(
51
+ dict: Record<string, any>,
52
+ key: string,
53
+ vars?: Record<string, string | number>,
54
+ ): string {
55
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
56
+ const value = key.split(".").reduce((acc, k) => acc?.[k], dict as any);
57
+
58
+ if (typeof value !== "string") return "";
59
+ return value.replace(/\{\{(.*?)\}\}/g, (_match: string, k: string) =>
60
+ String(vars?.[k.trim()] ?? ""),
61
+ );
62
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ // Core exports
2
+ export { autoWrapClientComponent } from "./core/client-component-wrapper";
3
+ export { hydrationContext, initializeHydrationContext } from "./core/hydration";
4
+ export { getChunkPath, getHydrationManifest } from "./core/hydration-manifest";
5
+ export { withJsxEngine } from "./core/jsx.engine";
6
+ export { LiveReloadController } from "./core/live-reload.controller";
7
+ export { StaticAssetsController } from "./core/static-assets.controller";
8
+
9
+ // Decorators
10
+ export { JsxRender } from "./decorators/jsx.decorator";
11
+ export { WithLayout } from "./decorators/layout.decorator";
12
+ export type { MetaOptions, RenderOptions } from "./decorators/jsx.decorator";
13
+
14
+ // I18n is provided in a separate package: @harpy-js/i18n
15
+ // Consumers should import i18n types and modules from that package.
16
+
17
+ // Types
18
+ export type { JsxLayout, JsxLayoutProps } from "./core/jsx.engine";
19
+ export { RouterModule } from "./core/router.module";
20
+ export { NavigationService } from "./core/navigation.service";
21
+ export { AutoRegisterModule } from "./core/auto-register.module";
22
+ export type { NavItem, NavSection } from "./core/types/nav.types";
23
+ export type { NavigationRegistry } from "./core/types/nav.types";
24
+ export { configureHarpyApp, HarpyAppOptions } from "./core/app-setup";
25
+ export { setupHarpyApp } from "./core/app-setup";
26
+ export { default as Link } from "./client/Link";
27
+ export {
28
+ buildHrefIndex,
29
+ getActiveItemIdFromIndex,
30
+ getActiveItemIdFromManifest,
31
+ } from "./client/getActiveItemId";