@alepha/react 0.14.4 → 0.15.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 (82) hide show
  1. package/README.md +10 -0
  2. package/dist/auth/index.browser.js +603 -242
  3. package/dist/auth/index.browser.js.map +1 -1
  4. package/dist/auth/index.d.ts +2 -2
  5. package/dist/auth/index.d.ts.map +1 -1
  6. package/dist/auth/index.js +1317 -952
  7. package/dist/auth/index.js.map +1 -1
  8. package/dist/core/index.d.ts +17 -17
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +20 -20
  11. package/dist/core/index.js.map +1 -1
  12. package/dist/form/index.d.ts +9 -10
  13. package/dist/form/index.d.ts.map +1 -1
  14. package/dist/form/index.js +15 -15
  15. package/dist/form/index.js.map +1 -1
  16. package/dist/head/index.browser.js +20 -0
  17. package/dist/head/index.browser.js.map +1 -1
  18. package/dist/head/index.d.ts +62 -64
  19. package/dist/head/index.d.ts.map +1 -1
  20. package/dist/head/index.js +20 -0
  21. package/dist/head/index.js.map +1 -1
  22. package/dist/i18n/index.d.ts +9 -9
  23. package/dist/i18n/index.d.ts.map +1 -1
  24. package/dist/i18n/index.js.map +1 -1
  25. package/dist/router/index.browser.js +605 -244
  26. package/dist/router/index.browser.js.map +1 -1
  27. package/dist/router/index.d.ts +100 -111
  28. package/dist/router/index.d.ts.map +1 -1
  29. package/dist/router/index.js +1317 -952
  30. package/dist/router/index.js.map +1 -1
  31. package/dist/websocket/index.d.ts +0 -1
  32. package/dist/websocket/index.d.ts.map +1 -1
  33. package/package.json +6 -6
  34. package/src/auth/__tests__/$auth.spec.ts +164 -150
  35. package/src/auth/index.ts +9 -3
  36. package/src/auth/services/ReactAuth.ts +15 -5
  37. package/src/core/hooks/useAction.ts +1 -2
  38. package/src/core/index.ts +4 -4
  39. package/src/form/errors/FormValidationError.ts +4 -6
  40. package/src/form/hooks/useFormState.ts +1 -1
  41. package/src/form/index.ts +1 -1
  42. package/src/form/services/FormModel.ts +31 -25
  43. package/src/head/helpers/SeoExpander.ts +2 -1
  44. package/src/head/hooks/useHead.spec.tsx +2 -2
  45. package/src/head/index.browser.ts +2 -2
  46. package/src/head/index.ts +4 -4
  47. package/src/head/interfaces/Head.ts +15 -3
  48. package/src/head/primitives/$head.ts +2 -5
  49. package/src/head/providers/BrowserHeadProvider.ts +55 -0
  50. package/src/head/providers/HeadProvider.ts +4 -1
  51. package/src/i18n/__tests__/integration.spec.tsx +1 -1
  52. package/src/i18n/components/Localize.spec.tsx +2 -2
  53. package/src/i18n/hooks/useI18n.browser.spec.tsx +2 -2
  54. package/src/i18n/index.ts +1 -1
  55. package/src/i18n/primitives/$dictionary.ts +1 -1
  56. package/src/i18n/providers/I18nProvider.spec.ts +1 -1
  57. package/src/i18n/providers/I18nProvider.ts +1 -1
  58. package/src/router/__tests__/page-head-browser.browser.spec.ts +5 -1
  59. package/src/router/__tests__/page-head.spec.ts +11 -7
  60. package/src/router/__tests__/seo-head.spec.ts +7 -3
  61. package/src/router/atoms/ssrManifestAtom.ts +2 -11
  62. package/src/router/components/ErrorViewer.tsx +626 -167
  63. package/src/router/components/Link.tsx +4 -2
  64. package/src/router/components/NestedView.tsx +7 -9
  65. package/src/router/components/NotFound.tsx +2 -2
  66. package/src/router/hooks/useQueryParams.ts +1 -1
  67. package/src/router/hooks/useRouter.ts +1 -1
  68. package/src/router/hooks/useRouterState.ts +1 -1
  69. package/src/router/index.browser.ts +10 -11
  70. package/src/router/index.shared.ts +7 -7
  71. package/src/router/index.ts +10 -7
  72. package/src/router/primitives/$page.browser.spec.tsx +6 -1
  73. package/src/router/primitives/$page.spec.tsx +7 -1
  74. package/src/router/primitives/$page.ts +5 -9
  75. package/src/router/providers/ReactBrowserProvider.ts +17 -6
  76. package/src/router/providers/ReactBrowserRouterProvider.ts +1 -1
  77. package/src/router/providers/ReactPageProvider.ts +4 -3
  78. package/src/router/providers/ReactServerProvider.ts +32 -50
  79. package/src/router/providers/ReactServerTemplateProvider.ts +336 -155
  80. package/src/router/providers/SSRManifestProvider.ts +17 -60
  81. package/src/router/services/ReactPageService.ts +4 -1
  82. package/src/router/services/ReactRouter.ts +6 -5
@@ -1,10 +1,10 @@
1
1
  import { AlephaContext } from "@alepha/react";
2
- import type { Head } from "../index.ts";
3
- import { useHead } from "../index.ts";
4
2
  import { render } from "@testing-library/react";
5
3
  import { Alepha } from "alepha";
6
4
  import { act, type ReactNode } from "react";
7
5
  import { beforeEach, describe, it, vi } from "vitest";
6
+ import type { Head } from "../index.ts";
7
+ import { useHead } from "../index.ts";
8
8
 
9
9
  /**
10
10
  * @vitest-environment jsdom
@@ -5,10 +5,10 @@ import { BrowserHeadProvider } from "./providers/BrowserHeadProvider.ts";
5
5
 
6
6
  // ---------------------------------------------------------------------------------------------------------------------
7
7
 
8
- export * from "./primitives/$head.ts";
8
+ export * from "./helpers/SeoExpander.ts";
9
9
  export * from "./hooks/useHead.ts";
10
10
  export * from "./interfaces/Head.ts";
11
- export * from "./helpers/SeoExpander.ts";
11
+ export * from "./primitives/$head.ts";
12
12
  export * from "./providers/BrowserHeadProvider.ts";
13
13
 
14
14
  // ---------------------------------------------------------------------------------------------------------------------
package/src/head/index.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  import { AlephaReact } from "@alepha/react";
2
2
  import { $module } from "alepha";
3
+ import { SeoExpander } from "./helpers/SeoExpander.ts";
3
4
  import { $head } from "./primitives/$head.ts";
4
5
  import { BrowserHeadProvider } from "./providers/BrowserHeadProvider.ts";
5
6
  import { HeadProvider } from "./providers/HeadProvider.ts";
6
- import { SeoExpander } from "./helpers/SeoExpander.ts";
7
7
  import { ServerHeadProvider } from "./providers/ServerHeadProvider.ts";
8
8
 
9
9
  // ---------------------------------------------------------------------------------------------------------------------
10
10
 
11
- export * from "./primitives/$head.ts";
11
+ export * from "./helpers/SeoExpander.ts";
12
12
  export * from "./hooks/useHead.ts";
13
13
  export * from "./interfaces/Head.ts";
14
- export * from "./helpers/SeoExpander.ts";
15
- export * from "./providers/ServerHeadProvider.ts";
14
+ export * from "./primitives/$head.ts";
16
15
  export * from "./providers/BrowserHeadProvider.ts";
16
+ export * from "./providers/ServerHeadProvider.ts";
17
17
 
18
18
  // ---------------------------------------------------------------------------------------------------------------------
19
19
 
@@ -74,9 +74,21 @@ export interface SimpleHead {
74
74
  /** Meta tags - supports both name and property attributes */
75
75
  meta?: Array<HeadMeta>;
76
76
  /** Link tags (e.g., stylesheets, preload, canonical) */
77
- link?: Array<{ rel: string; href: string }>;
78
- /** Script tags - any valid script attributes (src, type, async, defer, etc.) */
79
- script?: Array<Record<string, string | boolean>>;
77
+ link?: Array<{
78
+ rel: string;
79
+ href: string;
80
+ type?: string;
81
+ as?: string;
82
+ crossorigin?: string;
83
+ }>;
84
+ /** Script tags - string for inline code, or object with attributes */
85
+ script?: Array<
86
+ | string
87
+ | (Record<string, string | boolean | undefined> & {
88
+ /** Inline JavaScript code */
89
+ content?: string;
90
+ })
91
+ >;
80
92
  }
81
93
 
82
94
  export interface HeadMeta {
@@ -1,4 +1,4 @@
1
- import { $inject, createPrimitive, Primitive, KIND } from "alepha";
1
+ import { $inject, createPrimitive, KIND, Primitive } from "alepha";
2
2
  import type { Head } from "../interfaces/Head.ts";
3
3
  import { HeadProvider } from "../providers/HeadProvider.ts";
4
4
 
@@ -18,10 +18,7 @@ export type HeadPrimitiveOptions = Head | (() => Head);
18
18
  export class HeadPrimitive extends Primitive<HeadPrimitiveOptions> {
19
19
  protected readonly provider = $inject(HeadProvider);
20
20
  protected onInit() {
21
- this.provider.global = [
22
- ...(this.provider.global ?? []),
23
- this.options,
24
- ];
21
+ this.provider.global = [...(this.provider.global ?? []), this.options];
25
22
  }
26
23
  }
27
24
 
@@ -116,10 +116,65 @@ export class BrowserHeadProvider {
116
116
  link = document.createElement("link");
117
117
  link.setAttribute("rel", rel);
118
118
  link.setAttribute("href", href);
119
+ if (it.type) {
120
+ link.setAttribute("type", it.type);
121
+ }
122
+ if (it.as) {
123
+ link.setAttribute("as", it.as);
124
+ }
125
+ if (it.crossorigin != null) {
126
+ link.setAttribute("crossorigin", "");
127
+ }
119
128
  document.head.appendChild(link);
120
129
  }
121
130
  }
122
131
  }
132
+
133
+ if (head.script) {
134
+ for (const it of head.script) {
135
+ this.renderScriptTag(document, it);
136
+ }
137
+ }
138
+ }
139
+
140
+ protected renderScriptTag(
141
+ document: Document,
142
+ script:
143
+ | string
144
+ | (Record<string, string | boolean | undefined> & { content?: string }),
145
+ ): void {
146
+ const el = document.createElement("script");
147
+
148
+ // Handle plain string as inline script
149
+ if (typeof script === "string") {
150
+ el.textContent = script;
151
+ document.head.appendChild(el);
152
+ return;
153
+ }
154
+
155
+ const { content, ...attrs } = script;
156
+
157
+ // For scripts with src, check if already exists
158
+ if (attrs.src) {
159
+ const existing = document.querySelector(`script[src="${attrs.src}"]`);
160
+ if (existing) {
161
+ return;
162
+ }
163
+ }
164
+
165
+ for (const [key, value] of Object.entries(attrs)) {
166
+ if (value === true) {
167
+ el.setAttribute(key, "");
168
+ } else if (value !== undefined && value !== false) {
169
+ el.setAttribute(key, String(value));
170
+ }
171
+ }
172
+
173
+ if (content) {
174
+ el.textContent = content;
175
+ }
176
+
177
+ document.head.appendChild(el);
123
178
  }
124
179
 
125
180
  protected renderMetaTag(document: Document, meta: HeadMeta): void {
@@ -136,7 +136,10 @@ export class HeadProvider {
136
136
  }
137
137
 
138
138
  if (head.script) {
139
- state.head.script = [...(state.head.script ?? []), ...(head.script ?? [])];
139
+ state.head.script = [
140
+ ...(state.head.script ?? []),
141
+ ...(head.script ?? []),
142
+ ];
140
143
  }
141
144
  }
142
145
  }
@@ -1,7 +1,7 @@
1
1
  import { Alepha } from "alepha";
2
2
  import { describe, test } from "vitest";
3
- import { $dictionary } from "../primitives/$dictionary.ts";
4
3
  import { AlephaReactI18n } from "../index.ts";
4
+ import { $dictionary } from "../primitives/$dictionary.ts";
5
5
  import { I18nProvider } from "../providers/I18nProvider.ts";
6
6
 
7
7
  describe("I18n Integration Tests", () => {
@@ -1,10 +1,10 @@
1
1
  import { AlephaContext } from "@alepha/react";
2
- import { I18nProvider, Localize } from "../index.ts";
3
- import { Alepha, t } from "alepha";
2
+ import { Alepha } from "alepha";
4
3
  import { DateTimeProvider } from "alepha/datetime";
5
4
  import type { ReactNode } from "react";
6
5
  import { renderToString } from "react-dom/server";
7
6
  import { describe, expect, it } from "vitest";
7
+ import { I18nProvider, Localize } from "../index.ts";
8
8
 
9
9
  describe("<Localize/>", () => {
10
10
  const setup = () => {
@@ -4,10 +4,10 @@ import { Alepha } from "alepha";
4
4
  import type { ReactNode } from "react";
5
5
  import { act } from "react";
6
6
  import { describe, test } from "vitest";
7
- import { $dictionary } from "../primitives/$dictionary.ts";
8
- import { useI18n } from "./useI18n.ts";
9
7
  import { AlephaReactI18n } from "../index.ts";
8
+ import { $dictionary } from "../primitives/$dictionary.ts";
10
9
  import { I18nProvider } from "../providers/I18nProvider.ts";
10
+ import { useI18n } from "./useI18n.ts";
11
11
 
12
12
  describe("useI18n hook", () => {
13
13
  const renderWithAlepha = (alepha: Alepha, element: ReactNode) => {
package/src/i18n/index.ts CHANGED
@@ -6,8 +6,8 @@ import { I18nProvider } from "./providers/I18nProvider.ts";
6
6
 
7
7
  export type { LocalizeProps } from "./components/Localize.tsx";
8
8
  export { default as Localize } from "./components/Localize.tsx";
9
- export * from "./primitives/$dictionary.ts";
10
9
  export * from "./hooks/useI18n.ts";
10
+ export * from "./primitives/$dictionary.ts";
11
11
  export * from "./providers/I18nProvider.ts";
12
12
 
13
13
  // ---------------------------------------------------------------------------------------------------------------------
@@ -1,4 +1,4 @@
1
- import { $inject, type Async, createPrimitive, Primitive, KIND } from "alepha";
1
+ import { $inject, type Async, createPrimitive, KIND, Primitive } from "alepha";
2
2
  import { I18nProvider } from "../providers/I18nProvider.ts";
3
3
 
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import { Alepha } from "alepha";
2
2
  import { describe, test } from "vitest";
3
- import { $dictionary } from "../primitives/$dictionary.ts";
4
3
  import { AlephaReactI18n } from "../index.ts";
4
+ import { $dictionary } from "../primitives/$dictionary.ts";
5
5
  import { I18nProvider } from "./I18nProvider.ts";
6
6
 
7
7
  describe("I18nProvider", () => {
@@ -15,7 +15,7 @@ export class I18nProvider<
15
15
  protected cookie = $cookie({
16
16
  name: "lang",
17
17
  schema: t.text(),
18
- ttl: [1, "year"]
18
+ ttl: [1, "year"],
19
19
  });
20
20
 
21
21
  public readonly registry: Array<{
@@ -1,4 +1,8 @@
1
- import { AlephaReactHead, BrowserHeadProvider, type Head } from "@alepha/react/head";
1
+ import {
2
+ AlephaReactHead,
3
+ BrowserHeadProvider,
4
+ type Head,
5
+ } from "@alepha/react/head";
2
6
  import { Alepha } from "alepha";
3
7
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
8
  import { $page } from "../index.browser.ts";
@@ -1,6 +1,6 @@
1
+ import { $head, AlephaReactHead } from "@alepha/react/head";
1
2
  import { Alepha } from "alepha";
2
3
  import { describe, it } from "vitest";
3
- import { $head, AlephaReactHead } from "@alepha/react/head";
4
4
  import { $page } from "../index.ts";
5
5
 
6
6
  class App {
@@ -31,14 +31,18 @@ describe("PageHead", () => {
31
31
  const result = await a.hello.render({ html: true, hydration: false });
32
32
 
33
33
  // Check key parts of the HTML output (streaming adds newlines between sections)
34
- expect(result.html).toContain('<!DOCTYPE html>');
34
+ expect(result.html).toContain("<!DOCTYPE html>");
35
35
  expect(result.html).toContain('<html lang="fr" x-data-custom="ok">');
36
- expect(result.html).toContain('<title>Hello World</title>');
37
- expect(result.html).toContain('<meta name="description" content="This is a test page.">');
38
- expect(result.html).toContain('<meta name="keywords" content="test, alepha, react">');
36
+ expect(result.html).toContain("<title>Hello World</title>");
37
+ expect(result.html).toContain(
38
+ '<meta name="description" content="This is a test page.">',
39
+ );
40
+ expect(result.html).toContain(
41
+ '<meta name="keywords" content="test, alepha, react">',
42
+ );
39
43
  expect(result.html).toContain('<body class="hello-world">');
40
44
  expect(result.html).toContain('<div id="root">');
41
- expect(result.html).toContain('</body>');
42
- expect(result.html).toContain('</html>');
45
+ expect(result.html).toContain("</body>");
46
+ expect(result.html).toContain("</html>");
43
47
  });
44
48
  });
@@ -1,6 +1,6 @@
1
+ import { $head, AlephaReactHead } from "@alepha/react/head";
1
2
  import { Alepha } from "alepha";
2
3
  import { describe, it } from "vitest";
3
- import { $head, AlephaReactHead } from "@alepha/react/head";
4
4
  import { $page } from "../index.ts";
5
5
 
6
6
  class App {
@@ -49,7 +49,9 @@ describe("SEO Head", () => {
49
49
  expect(result.html).toContain(
50
50
  '<meta property="og:url" content="https://alepha.dev/">',
51
51
  );
52
- expect(result.html).toContain('<meta property="og:type" content="website">');
52
+ expect(result.html).toContain(
53
+ '<meta property="og:type" content="website">',
54
+ );
53
55
  expect(result.html).toContain(
54
56
  '<meta property="og:site_name" content="Alepha">',
55
57
  );
@@ -113,7 +115,9 @@ describe("SEO Head on Page", () => {
113
115
  expect(result.html).toContain(
114
116
  '<meta name="description" content="Read our latest articles">',
115
117
  );
116
- expect(result.html).toContain('<meta property="og:type" content="article">');
118
+ expect(result.html).toContain(
119
+ '<meta property="og:type" content="article">',
120
+ );
117
121
  expect(result.html).toContain(
118
122
  '<meta property="og:image" content="https://example.com/blog-og.png">',
119
123
  );
@@ -10,26 +10,18 @@ export const ssrManifestAtomSchema = t.object({
10
10
  */
11
11
  preload: t.optional(t.record(t.string(), t.string())),
12
12
 
13
- /**
14
- * SSR manifest mapping source files to their required chunks.
15
- */
16
- ssr: t.optional(t.record(t.string(), t.array(t.string()))),
17
-
18
13
  /**
19
14
  * Client manifest mapping source files to their output information.
15
+ * Only includes fields actually used for preloading.
20
16
  */
21
17
  client: t.optional(
22
18
  t.record(
23
19
  t.string(),
24
20
  t.object({
25
21
  file: t.string(),
26
- src: t.optional(t.string()),
27
22
  isEntry: t.optional(t.boolean()),
28
- isDynamicEntry: t.optional(t.boolean()),
29
23
  imports: t.optional(t.array(t.string())),
30
- dynamicImports: t.optional(t.array(t.string())),
31
24
  css: t.optional(t.array(t.string())),
32
- assets: t.optional(t.array(t.string())),
33
25
  }),
34
26
  ),
35
27
  ),
@@ -49,8 +41,7 @@ export type SsrManifestAtomSchema = typeof ssrManifestAtomSchema;
49
41
  *
50
42
  * The manifest includes:
51
43
  * - preload: Maps short hash keys to source paths (from viteAlephaSsrPreload)
52
- * - ssr: Maps source files to their required chunks
53
- * - client: Maps source files to their output info including imports/css
44
+ * - client: Maps source files to their output info (file, imports, css)
54
45
  */
55
46
  export const ssrManifestAtom = $atom({
56
47
  name: "alepha.react.ssr.manifest",