netjes 0.0.0-stage → 0.0.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 (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -2
  3. package/bin/netjes.mjs +13 -0
  4. package/cli/agents.mjs +66 -0
  5. package/cli/commands.mjs +467 -0
  6. package/cli/help.mjs +89 -0
  7. package/cli/info.mjs +454 -0
  8. package/cli/main.mjs +167 -0
  9. package/cli/packages.mjs +19 -0
  10. package/cli/process-io.mjs +163 -0
  11. package/cli/routes.mjs +31 -0
  12. package/cli/scaffold.mjs +193 -0
  13. package/cli/suggest.mjs +50 -0
  14. package/cli/terminal.mjs +40 -0
  15. package/dist/actions.d.ts +98 -0
  16. package/dist/actions.d.ts.map +1 -0
  17. package/dist/actions.js +176 -0
  18. package/dist/actions.js.map +1 -0
  19. package/dist/client-entry.d.ts +2 -0
  20. package/dist/client-entry.d.ts.map +1 -0
  21. package/dist/client-entry.js +102 -0
  22. package/dist/client-entry.js.map +1 -0
  23. package/dist/deferred.d.ts +39 -0
  24. package/dist/deferred.d.ts.map +1 -0
  25. package/dist/deferred.js +123 -0
  26. package/dist/deferred.js.map +1 -0
  27. package/dist/define-page.d.ts +15 -0
  28. package/dist/define-page.d.ts.map +1 -0
  29. package/dist/define-page.js +27 -0
  30. package/dist/define-page.js.map +1 -0
  31. package/dist/document.d.ts +18 -0
  32. package/dist/document.d.ts.map +1 -0
  33. package/dist/document.js +16 -0
  34. package/dist/document.js.map +1 -0
  35. package/dist/draft-mode.d.ts +5 -0
  36. package/dist/draft-mode.d.ts.map +1 -0
  37. package/dist/draft-mode.js +6 -0
  38. package/dist/draft-mode.js.map +1 -0
  39. package/dist/dynamic.d.ts +20 -0
  40. package/dist/dynamic.d.ts.map +1 -0
  41. package/dist/dynamic.js +37 -0
  42. package/dist/dynamic.js.map +1 -0
  43. package/dist/form-data.d.ts +10 -0
  44. package/dist/form-data.d.ts.map +1 -0
  45. package/dist/form-data.js +10 -0
  46. package/dist/form-data.js.map +1 -0
  47. package/dist/form.d.ts +35 -0
  48. package/dist/form.d.ts.map +1 -0
  49. package/dist/form.js +80 -0
  50. package/dist/form.js.map +1 -0
  51. package/dist/href.d.ts +25 -0
  52. package/dist/href.d.ts.map +1 -0
  53. package/dist/href.js +71 -0
  54. package/dist/href.js.map +1 -0
  55. package/dist/image.d.ts +36 -0
  56. package/dist/image.d.ts.map +1 -0
  57. package/dist/image.js +60 -0
  58. package/dist/image.js.map +1 -0
  59. package/dist/index.d.ts +18 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +14 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/link.d.ts +17 -0
  64. package/dist/link.d.ts.map +1 -0
  65. package/dist/link.js +46 -0
  66. package/dist/link.js.map +1 -0
  67. package/dist/locale.d.ts +3 -0
  68. package/dist/locale.d.ts.map +1 -0
  69. package/dist/locale.js +6 -0
  70. package/dist/locale.js.map +1 -0
  71. package/dist/metadata.d.ts +83 -0
  72. package/dist/metadata.d.ts.map +1 -0
  73. package/dist/metadata.js +100 -0
  74. package/dist/metadata.js.map +1 -0
  75. package/dist/modules.d.ts +74 -0
  76. package/dist/modules.d.ts.map +1 -0
  77. package/dist/modules.js +85 -0
  78. package/dist/modules.js.map +1 -0
  79. package/dist/nonce.d.ts +8 -0
  80. package/dist/nonce.d.ts.map +1 -0
  81. package/dist/nonce.js +12 -0
  82. package/dist/nonce.js.map +1 -0
  83. package/dist/payload.d.ts +169 -0
  84. package/dist/payload.d.ts.map +1 -0
  85. package/dist/payload.js +271 -0
  86. package/dist/payload.js.map +1 -0
  87. package/dist/public-config.d.ts +11 -0
  88. package/dist/public-config.d.ts.map +1 -0
  89. package/dist/public-config.js +10 -0
  90. package/dist/public-config.js.map +1 -0
  91. package/dist/query.d.ts +22 -0
  92. package/dist/query.d.ts.map +1 -0
  93. package/dist/query.js +68 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/render-cleanup.d.ts +6 -0
  96. package/dist/render-cleanup.d.ts.map +1 -0
  97. package/dist/render-cleanup.js +7 -0
  98. package/dist/render-cleanup.js.map +1 -0
  99. package/dist/root.d.ts +12 -0
  100. package/dist/root.d.ts.map +1 -0
  101. package/dist/root.js +26 -0
  102. package/dist/root.js.map +1 -0
  103. package/dist/route-tree.d.ts +56 -0
  104. package/dist/route-tree.d.ts.map +1 -0
  105. package/dist/route-tree.js +86 -0
  106. package/dist/route-tree.js.map +1 -0
  107. package/dist/router.d.ts +118 -0
  108. package/dist/router.d.ts.map +1 -0
  109. package/dist/router.js +938 -0
  110. package/dist/router.js.map +1 -0
  111. package/dist/schema.d.ts +35 -0
  112. package/dist/schema.d.ts.map +1 -0
  113. package/dist/schema.js +74 -0
  114. package/dist/schema.js.map +1 -0
  115. package/dist/script.d.ts +23 -0
  116. package/dist/script.d.ts.map +1 -0
  117. package/dist/script.js +89 -0
  118. package/dist/script.js.map +1 -0
  119. package/dist/server-entry.d.ts +2 -0
  120. package/dist/server-entry.d.ts.map +1 -0
  121. package/dist/server-entry.js +4 -0
  122. package/dist/server-entry.js.map +1 -0
  123. package/dist/server-render.d.ts +19 -0
  124. package/dist/server-render.d.ts.map +1 -0
  125. package/dist/server-render.js +193 -0
  126. package/dist/server-render.js.map +1 -0
  127. package/dist/testing-setup.d.ts +2 -0
  128. package/dist/testing-setup.d.ts.map +1 -0
  129. package/dist/testing-setup.js +8 -0
  130. package/dist/testing-setup.js.map +1 -0
  131. package/dist/testing.d.ts +76 -0
  132. package/dist/testing.d.ts.map +1 -0
  133. package/dist/testing.js +106 -0
  134. package/dist/testing.js.map +1 -0
  135. package/dist/vitals-config.d.ts +4 -0
  136. package/dist/vitals-config.d.ts.map +1 -0
  137. package/dist/vitals-config.js +5 -0
  138. package/dist/vitals-config.js.map +1 -0
  139. package/dist/vitals.d.ts +13 -0
  140. package/dist/vitals.d.ts.map +1 -0
  141. package/dist/vitals.js +60 -0
  142. package/dist/vitals.js.map +1 -0
  143. package/dist/zod.d.ts +19 -0
  144. package/dist/zod.d.ts.map +1 -0
  145. package/dist/zod.js +65 -0
  146. package/dist/zod.js.map +1 -0
  147. package/package.json +147 -4
  148. package/sidecar/server.mjs +325 -0
  149. package/skills/netjes/SKILL.md +93 -0
  150. package/skills/netjes/references/caching-and-streaming.md +68 -0
  151. package/skills/netjes/references/data-and-actions.md +98 -0
  152. package/skills/netjes/references/deployment.md +57 -0
  153. package/skills/netjes/references/routing.md +65 -0
  154. package/skills/netjes/references/testing.md +57 -0
  155. package/src/actions.tsx +241 -0
  156. package/src/client-entry.tsx +103 -0
  157. package/src/deferred.ts +134 -0
  158. package/src/define-page.tsx +41 -0
  159. package/src/document.tsx +47 -0
  160. package/src/draft-mode.ts +6 -0
  161. package/src/dynamic.tsx +64 -0
  162. package/src/form-data.ts +10 -0
  163. package/src/form.tsx +122 -0
  164. package/src/href.ts +79 -0
  165. package/src/image.tsx +91 -0
  166. package/src/index.ts +40 -0
  167. package/src/link.tsx +73 -0
  168. package/src/locale.ts +6 -0
  169. package/src/metadata.tsx +181 -0
  170. package/src/modules.ts +133 -0
  171. package/src/nonce.tsx +14 -0
  172. package/src/payload.ts +403 -0
  173. package/src/public-config.tsx +19 -0
  174. package/src/query.tsx +82 -0
  175. package/src/render-cleanup.ts +7 -0
  176. package/src/root.tsx +39 -0
  177. package/src/route-tree.tsx +139 -0
  178. package/src/router.tsx +1188 -0
  179. package/src/schema.ts +98 -0
  180. package/src/script.tsx +103 -0
  181. package/src/server-entry.tsx +4 -0
  182. package/src/server-render.tsx +223 -0
  183. package/src/testing-setup.ts +8 -0
  184. package/src/testing.tsx +177 -0
  185. package/src/virtual.d.ts +12 -0
  186. package/src/vitals-config.ts +6 -0
  187. package/src/vitals.tsx +68 -0
  188. package/src/zod.ts +72 -0
  189. package/template/.dockerignore +11 -0
  190. package/template/Dockerfile +58 -0
  191. package/template/Features/Home/HomePage.cs +32 -0
  192. package/template/Program.cs +28 -0
  193. package/template/Properties/launchSettings.json +13 -0
  194. package/template/README.md +24 -0
  195. package/template/SiteProps.cs +4 -0
  196. package/template/_AGENTS.md +64 -0
  197. package/template/_CLAUDE.md +1 -0
  198. package/template/__NAME__.csproj +11 -0
  199. package/template/_gitattributes +2 -0
  200. package/template/_gitignore +11 -0
  201. package/template/app/about/page.tsx +17 -0
  202. package/template/app/error.tsx +15 -0
  203. package/template/app/layout.tsx +33 -0
  204. package/template/app/not-found.tsx +14 -0
  205. package/template/app/page.tsx +14 -0
  206. package/template/appsettings.Development.json +3 -0
  207. package/template/appsettings.json +9 -0
  208. package/template/components/Greeting.test.tsx +25 -0
  209. package/template/components/Greeting.tsx +26 -0
  210. package/template/components/HelloForm.test.tsx +27 -0
  211. package/template/components/HelloForm.tsx +34 -0
  212. package/template/docker-compose.override.yml +15 -0
  213. package/template/docker-compose.prod.yml +26 -0
  214. package/template/docker-compose.yml +16 -0
  215. package/template/generated/netjes.ts +73 -0
  216. package/template/package.json +36 -0
  217. package/template/public/favicon.svg +6 -0
  218. package/template/styles/global.css +7 -0
  219. package/template/tsconfig.json +19 -0
  220. package/template/vite.config.ts +7 -0
  221. package/vite/plugin.d.ts +29 -0
  222. package/vite/plugin.mjs +291 -0
@@ -0,0 +1,98 @@
1
+ import { type ActionResponse, type AntiforgeryToken } from './payload.ts';
2
+ import { type StandardSchemaV1 } from './schema.ts';
3
+ /**
4
+ * What a server action resolves to: its result, or the validation errors (status 422) per field. An action typed with
5
+ * a result can still answer `ServerAction.Redirect(...)` (then `result` is undefined and the router has already moved
6
+ * on), so code that runs after `await action(...)` in a component that may stay mounted should not assume one.
7
+ */
8
+ export type ActionState<TResult> = {
9
+ ok: true;
10
+ result: TResult;
11
+ } | {
12
+ ok: false;
13
+ status: number;
14
+ errors: Record<string, string[]>;
15
+ values?: Record<string, string | string[]>;
16
+ };
17
+ /**
18
+ * A reference to a C# server action (`app.MapAction(name, ...)`). Call it with a plain object (sent as JSON) or
19
+ * FormData (sent as a form post); use it in a form through {@link useAction}.
20
+ */
21
+ export interface ServerAction<TInput, TResult> {
22
+ (input: TInput | FormData): Promise<ActionState<TResult>>;
23
+ readonly actionName: string;
24
+ readonly url: string;
25
+ }
26
+ /** Thrown into the nearest error boundary when the server could not run the action (not for validation errors). */
27
+ export declare class ServerActionError extends Error {
28
+ name: string;
29
+ readonly status: number;
30
+ constructor(message: string, status: number);
31
+ }
32
+ /** Runs action calls for the page: the router in the browser (fetch, then redirect/refresh/query updates), a fake in tests. */
33
+ export type ActionClient = {
34
+ invoke(name: string, url: string, input: unknown): Promise<ActionResponse>;
35
+ /** The antiforgery form field, left out of the values a failed call hands back. */
36
+ tokenField?(): string | undefined;
37
+ };
38
+ /** A router is mounting (called as it first renders, in the browser): calls from now on wait until it registers. */
39
+ export declare function expectActionClient(): void;
40
+ /** The router registers itself; there is one per page in the browser. */
41
+ export declare function setActionClient(next: ActionClient | null): () => void;
42
+ export declare const RETURN_FIELD = "__netjes_return";
43
+ type ActionMeta = {
44
+ url: string;
45
+ name: string;
46
+ };
47
+ /** The action URL and name a form action carries, so `<Form>` can render a real form post on the server. */
48
+ export declare function actionMetaOf(action: unknown): ActionMeta | undefined;
49
+ export declare function actionRequest(input: unknown, token: AntiforgeryToken | undefined, returnUrl: string): RequestInit;
50
+ /**
51
+ * The fetch half of a call, for the router's action client. `tokenRefused` says the server refused the call's
52
+ * antiforgery token, so the action did not run; no other answer says that, whatever its status. `onCookiesChanged`
53
+ * runs as soon as the server says the call set or deleted a cookie (signed someone in or out), which script cannot
54
+ * see in the response itself, whatever the rest of the answer turns out to be.
55
+ */
56
+ export declare function fetchAction(name: string, url: string, init: RequestInit, onCookiesChanged?: () => void): Promise<{
57
+ response: ActionResponse;
58
+ tokenRefused: boolean;
59
+ }>;
60
+ /**
61
+ * Declares a server action by name. The result is parsed with `options.result` when given (zod, valibot, ...).
62
+ * `npx netjes generate` writes these for every `MapAction`, with types from the C# signature.
63
+ */
64
+ export declare function serverAction<TInput = FormData, TResult = unknown>(name: string, options?: {
65
+ result?: StandardSchemaV1<unknown, TResult>;
66
+ }): ServerAction<TInput, TResult>;
67
+ /** Options for `useAction`. */
68
+ export type UseActionOptions<TResult> = {
69
+ /** The state before the first submission. Default null. */
70
+ initialState?: ActionState<TResult> | null;
71
+ /**
72
+ * Called with the form's data inside the action's transition, just before the call to the server: the place for
73
+ * `useOptimistic` updates, which React keeps until the action (and the page refresh after it) finishes.
74
+ */
75
+ onSubmit?: (formData: FormData) => void;
76
+ };
77
+ /**
78
+ * `useActionState` for a server action: `[state, formAction, isPending]`. Pass `formAction` to `<Form action>`;
79
+ * `useFormStatus` works as usual, and `onSubmit` is where optimistic updates go. After a failed post without
80
+ * JavaScript, the state starts as that failure, with the submitted values.
81
+ */
82
+ export declare function useAction<TInput, TResult>(action: ServerAction<TInput, TResult>, options?: UseActionOptions<TResult>): [state: ActionState<TResult> | null, formAction: (formData: FormData) => void, isPending: boolean];
83
+ /** A page action's failure, in the shape of `useAction`'s failure state. */
84
+ export type PageActionFailure = {
85
+ ok: false;
86
+ status: number;
87
+ errors: Record<string, string[]>;
88
+ values: Record<string, string | string[]>;
89
+ };
90
+ /**
91
+ * After the input of the page's action (`MapPageAction`) failed its checks, its DataAnnotations or
92
+ * `IValidatableObject`: the errors per field and the submitted values of the fields the action declares, as the page
93
+ * is rendered again from its loader, with and without JavaScript. Else null, also after a server action's failure,
94
+ * which `useAction` reads.
95
+ */
96
+ export declare function usePageActionState(): PageActionFailure | null;
97
+ export {};
98
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ import { useActionState, useMemo } from 'react';
2
+ import { encodeForm } from './form-data.js';
3
+ import { ANTIFORGERY_HEADER, COOKIES_HEADER, DATA_HEADER, parseActionResponse } from './payload.js';
4
+ import { usePagePayload } from './router.js';
5
+ import { parseWith } from './schema.js';
6
+ /** Thrown into the nearest error boundary when the server could not run the action (not for validation errors). */
7
+ export class ServerActionError extends Error {
8
+ name = 'ServerActionError';
9
+ status;
10
+ constructor(message, status) {
11
+ super(message);
12
+ this.status = status;
13
+ }
14
+ }
15
+ let client = null;
16
+ /**
17
+ * Set from the moment a router renders until it registers: React dispatches the clicks that came in while it hydrated
18
+ * the page before it runs the router's effects, and runs a page's own effects before its router's. Calls made then
19
+ * wait here.
20
+ */
21
+ let arriving = null;
22
+ /** A router is mounting (called as it first renders, in the browser): calls from now on wait until it registers. */
23
+ export function expectActionClient() {
24
+ if (arriving)
25
+ return;
26
+ let resolve = () => { };
27
+ const ready = new Promise((done) => {
28
+ resolve = done;
29
+ });
30
+ arriving = { ready, resolve };
31
+ }
32
+ /** The router registers itself; there is one per page in the browser. */
33
+ export function setActionClient(next) {
34
+ client = next;
35
+ if (next && arriving) {
36
+ arriving.resolve();
37
+ arriving = null;
38
+ }
39
+ return () => {
40
+ if (client === next)
41
+ client = null;
42
+ };
43
+ }
44
+ /** How long a call waits for the router on its way; one whose render React threw away never registers. */
45
+ const READY_TIMEOUT_MS = 10_000;
46
+ /**
47
+ * The registered client, once the router on its way has registered; the error without a router on its way, or when it
48
+ * has not registered in time.
49
+ */
50
+ async function readyClient(name) {
51
+ const waitingFor = arriving;
52
+ if (!client && waitingFor) {
53
+ let timer;
54
+ const timedOut = new Promise((resolve) => {
55
+ timer = setTimeout(() => resolve('timeout'), READY_TIMEOUT_MS);
56
+ });
57
+ // Given up on, so that later calls fail at once.
58
+ if ((await Promise.race([waitingFor.ready, timedOut])) === 'timeout' && arriving === waitingFor)
59
+ arriving = null;
60
+ clearTimeout(timer);
61
+ }
62
+ if (!client)
63
+ throw new Error(`netjes: server action ${name} was called before the page was ready`);
64
+ return client;
65
+ }
66
+ export const RETURN_FIELD = '__netjes_return';
67
+ const ACTION_META = Symbol.for('netjes.action');
68
+ /** The action URL and name a form action carries, so `<Form>` can render a real form post on the server. */
69
+ export function actionMetaOf(action) {
70
+ return typeof action === 'function' ? action[ACTION_META] : undefined;
71
+ }
72
+ /**
73
+ * The fields of a rejected form, as the failure state's `values`: React resets a form after its action runs, so a
74
+ * `defaultValue={state.values.name}` is what keeps what the user typed. Files, the token and Netjes's own fields are
75
+ * left out, and so is anything outside `fields`: the server names the fields a post without JavaScript would echo
76
+ * (those the action declares, none private).
77
+ */
78
+ function submittedValues(input, tokenField, fields) {
79
+ if (!(input instanceof FormData))
80
+ return undefined;
81
+ const values = {};
82
+ for (const [key, value] of input.entries()) {
83
+ if (typeof value !== 'string' || key === tokenField || key.startsWith('__netjes_'))
84
+ continue;
85
+ if (fields && !fields.includes(key))
86
+ continue;
87
+ const existing = values[key];
88
+ values[key] = existing === undefined ? value : Array.isArray(existing) ? [...existing, value] : [existing, value];
89
+ }
90
+ return values;
91
+ }
92
+ export function actionRequest(input, token, returnUrl) {
93
+ const headers = { [DATA_HEADER]: '1', Accept: 'application/json' };
94
+ if (token?.headerName && token.requestToken)
95
+ headers[token.headerName] = token.requestToken;
96
+ if (input instanceof FormData) {
97
+ if (!input.has(RETURN_FIELD))
98
+ input.append(RETURN_FIELD, returnUrl);
99
+ const encoded = encodeForm(input);
100
+ if (encoded.contentType)
101
+ headers['Content-Type'] = encoded.contentType;
102
+ return { method: 'POST', headers, body: encoded.body, credentials: 'same-origin' };
103
+ }
104
+ headers['Content-Type'] = 'application/json';
105
+ return { method: 'POST', headers, body: JSON.stringify(input ?? {}), credentials: 'same-origin' };
106
+ }
107
+ /**
108
+ * The fetch half of a call, for the router's action client. `tokenRefused` says the server refused the call's
109
+ * antiforgery token, so the action did not run; no other answer says that, whatever its status. `onCookiesChanged`
110
+ * runs as soon as the server says the call set or deleted a cookie (signed someone in or out), which script cannot
111
+ * see in the response itself, whatever the rest of the answer turns out to be.
112
+ */
113
+ export async function fetchAction(name, url, init, onCookiesChanged) {
114
+ const response = await fetch(url, init);
115
+ if (response.headers.get(COOKIES_HEADER) === 'changed')
116
+ onCookiesChanged?.();
117
+ const type = response.headers.get('content-type') ?? '';
118
+ if (!type.includes('application/json'))
119
+ throw new ServerActionError(`Server action ${name} failed with status ${response.status}`, response.status);
120
+ return { response: parseActionResponse(await response.json()), tokenRefused: response.headers.get(ANTIFORGERY_HEADER) === 'invalid' };
121
+ }
122
+ /**
123
+ * Declares a server action by name. The result is parsed with `options.result` when given (zod, valibot, ...).
124
+ * `npx netjes generate` writes these for every `MapAction`, with types from the C# signature.
125
+ */
126
+ export function serverAction(name, options = {}) {
127
+ const url = `/_netjes/actions/${name}`;
128
+ async function call(input) {
129
+ const active = client ?? (await readyClient(name));
130
+ const answer = await active.invoke(name, url, input);
131
+ if (!answer.ok && answer.status !== 422) {
132
+ throw new ServerActionError(answer.message ?? `Server action ${name} failed with status ${answer.status}`, answer.status);
133
+ }
134
+ if (!answer.ok) {
135
+ const values = submittedValues(input, active.tokenField?.(), answer.fields);
136
+ return { ok: false, status: answer.status, errors: answer.errors ?? {}, ...(values ? { values } : {}) };
137
+ }
138
+ // `ServerAction.Redirect(...)` and `Ok()` send no result, even from an action whose C# type promises one.
139
+ const result = options.result && answer.result !== undefined ? parseWith(options.result, answer.result, `result of ${name}`) : answer.result;
140
+ return { ok: true, result };
141
+ }
142
+ return Object.assign(call, { actionName: name, url });
143
+ }
144
+ /**
145
+ * `useActionState` for a server action: `[state, formAction, isPending]`. Pass `formAction` to `<Form action>`;
146
+ * `useFormStatus` works as usual, and `onSubmit` is where optimistic updates go. After a failed post without
147
+ * JavaScript, the state starts as that failure, with the submitted values.
148
+ */
149
+ export function useAction(action, options = {}) {
150
+ const { actionState } = usePagePayload();
151
+ const initial = actionState?.action === action.actionName
152
+ ? { ok: false, status: 422, errors: actionState.errors, values: actionState.values }
153
+ : (options.initialState ?? null);
154
+ const { onSubmit } = options;
155
+ const [state, dispatch, pending] = useActionState((_previous, formData) => {
156
+ onSubmit?.(formData);
157
+ return action(formData);
158
+ }, initial);
159
+ if (!(ACTION_META in dispatch)) {
160
+ Object.defineProperty(dispatch, ACTION_META, { value: { url: action.url, name: action.actionName } });
161
+ }
162
+ return [state, dispatch, pending];
163
+ }
164
+ /**
165
+ * After the input of the page's action (`MapPageAction`) failed its checks, its DataAnnotations or
166
+ * `IValidatableObject`: the errors per field and the submitted values of the fields the action declares, as the page
167
+ * is rendered again from its loader, with and without JavaScript. Else null, also after a server action's failure,
168
+ * which `useAction` reads.
169
+ */
170
+ export function usePageActionState() {
171
+ const { actionState, route } = usePagePayload();
172
+ return useMemo(() => actionState && route !== null && actionState.action === route
173
+ ? { ok: false, status: 422, errors: actionState.errors, values: actionState.values }
174
+ : null, [actionState, route]);
175
+ }
176
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import '@vitejs/plugin-react/preamble';
2
+ //# sourceMappingURL=client-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-entry.d.ts","sourceRoot":"","sources":["../src/client-entry.tsx"],"names":[],"mappings":"AACA,OAAO,+BAA+B,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Must come first: installs the React Fast Refresh runtime before any component module runs (empty in builds).
3
+ import '@vitejs/plugin-react/preamble';
4
+ import { createRoot, hydrateRoot } from 'react-dom/client';
5
+ import { pages } from 'virtual:netjes/pages';
6
+ import { deferredRegistry, revivePayload } from './deferred.js';
7
+ import { createModuleLoader } from './modules.js';
8
+ import { DATA_ELEMENT_ID, parseInitialPayload } from './payload.js';
9
+ import { Root } from './root.js';
10
+ import { withClockOffset } from './router.js';
11
+ /**
12
+ * The payload is the body's first element, so it is normally parsed by the time this async script runs. If the
13
+ * script won the race, wait for the element rather than for the whole (streaming) document.
14
+ */
15
+ function payloadElement() {
16
+ // Complete once the parser has moved past it (a large payload can arrive in several chunks).
17
+ const complete = () => {
18
+ const element = document.getElementById(DATA_ELEMENT_ID);
19
+ return element && (element.nextSibling || document.readyState !== 'loading') ? element : null;
20
+ };
21
+ const found = complete();
22
+ if (found || document.readyState !== 'loading')
23
+ return Promise.resolve(found ?? document.getElementById(DATA_ELEMENT_ID));
24
+ return new Promise((resolve) => {
25
+ const observer = new MutationObserver(() => {
26
+ const element = complete();
27
+ if (element) {
28
+ observer.disconnect();
29
+ resolve(element);
30
+ }
31
+ });
32
+ observer.observe(document, { childList: true, subtree: true });
33
+ document.addEventListener('DOMContentLoaded', () => {
34
+ observer.disconnect();
35
+ resolve(document.getElementById(DATA_ELEMENT_ID));
36
+ });
37
+ });
38
+ }
39
+ const dataJson = (await payloadElement())?.textContent;
40
+ if (!dataJson)
41
+ throw new Error(`netjes: #${DATA_ELEMENT_ID} is missing; was this page rendered by Netjes?`);
42
+ const globals = self;
43
+ const deferred = deferredRegistry();
44
+ const settle = (id, value, error) => (error === undefined || error === null ? deferred.resolve(id, value) : deferred.reject(id, error));
45
+ /**
46
+ * The values the head bootstrap collected: its own plain object, `{ id: [value, error] }`. Anything else is page
47
+ * markup standing in for the global (an element with `id="__netjes_v"`), and must not stop the page from hydrating.
48
+ */
49
+ function collectedValues(collected) {
50
+ if (typeof collected !== 'object' || collected === null)
51
+ return [];
52
+ const prototype = Object.getPrototypeOf(collected);
53
+ return prototype === Object.prototype || prototype === null ? Object.entries(collected) : [];
54
+ }
55
+ for (const [id, entry] of collectedValues(globals.__netjes_v)) {
56
+ if (Array.isArray(entry))
57
+ settle(id, entry[0], typeof entry[1] === 'string' ? entry[1] : undefined);
58
+ }
59
+ globals.__netjes_d = settle;
60
+ const parsed = revivePayload(parseInitialPayload(JSON.parse(dataJson)), deferred.source);
61
+ // Judge the server's seeded data as fresh as the server did when it rendered, so hydration matches.
62
+ const payload = withClockOffset(parsed, parsed.renderedAt);
63
+ const loader = createModuleLoader(pages, { nameFailedImports: true });
64
+ /** How long the first page waits before it tries a module that failed to load once more. */
65
+ const RETRY_DELAY_MS = 500;
66
+ /**
67
+ * The first page's modules. One that fails to load (a dropped request, a deploy that removed its chunk) gets one more
68
+ * try, which the browser fetches again. If that fails too, the page stays as the server sent it, without the client
69
+ * router: its links and forms are plain HTML, and the browser loads each in full.
70
+ */
71
+ async function firstPageModules() {
72
+ try {
73
+ return await loader.loadRoute(payload);
74
+ }
75
+ catch {
76
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
77
+ }
78
+ try {
79
+ return await loader.loadRoute(payload);
80
+ }
81
+ catch (error) {
82
+ const reason = error instanceof Error ? error.message : `netjes: the page's modules could not be loaded (${String(error)})`;
83
+ const outcome = payload.document.clientRender
84
+ ? 'the page cannot render without it'
85
+ : 'the page stays as the server rendered it, without the client router, and its links and forms load pages in full';
86
+ console.error(`${reason}: ${outcome}`, error);
87
+ return null;
88
+ }
89
+ }
90
+ const modules = await firstPageModules();
91
+ if (modules) {
92
+ const root = _jsx(Root, { payload: payload, modules: modules, loader: loader, dataJson: dataJson });
93
+ if (payload.document.clientRender) {
94
+ // No sidecar was ready, so the server sent the document without the page: there is nothing to hydrate. React keeps
95
+ // the scripts and stylesheets already in it and replaces the rest.
96
+ createRoot(document).render(root);
97
+ }
98
+ else {
99
+ hydrateRoot(document, root);
100
+ }
101
+ }
102
+ //# sourceMappingURL=client-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-entry.js","sourceRoot":"","sources":["../src/client-entry.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { PagePayload } from './payload.ts';
2
+ /** A promise that records its outcome the way React's `use()` reads it, so a settled one renders without suspending. */
3
+ export type Thenable<T> = Promise<T> & {
4
+ status?: 'pending' | 'fulfilled' | 'rejected';
5
+ value?: T;
6
+ reason?: unknown;
7
+ };
8
+ export type Deferred<T> = {
9
+ promise: Thenable<T>;
10
+ resolve(value: T): void;
11
+ reject(reason: unknown): void;
12
+ };
13
+ export declare function createDeferred<T>(): Deferred<T>;
14
+ /** Where revived placeholders get their promise from, by id. */
15
+ export type DeferredSource = (id: string) => Thenable<unknown>;
16
+ /** Replaces every `{ $netjes: 'deferred', id }` in a JSON value; returns the same object when there are none. */
17
+ export declare function reviveDeferred(value: unknown, source: DeferredSource): unknown;
18
+ /** Revives the placeholders in a page's props and its segments' props. */
19
+ export declare function revivePayload<T extends PagePayload>(payload: T, source: DeferredSource): T;
20
+ /** A source that hands out one deferred per id and settles it when the value arrives. */
21
+ export declare function deferredRegistry(): {
22
+ source: DeferredSource;
23
+ resolve(id: string, value: unknown): void;
24
+ reject(id: string, message: string): void;
25
+ /** Rejects every value still pending, e.g. when the stream that was to deliver them ended. */
26
+ rejectPending(message: (id: string) => string): void;
27
+ };
28
+ /**
29
+ * Inline in every document: collects deferred values streamed as `<script>self.__netjes_d(id, value, error)</script>`
30
+ * until the client entry takes over (it replaces `__netjes_d`). The async client entry can run first, so this never
31
+ * replaces a handler that is already there. It creates `__netjes_v` up front, as a window property of its own: until
32
+ * then, page markup with `id="__netjes_v"` would be what that name reads (DOM clobbering).
33
+ */
34
+ export declare const DEFERRED_BOOTSTRAP = "self.__netjes_d=self.__netjes_d||function(i,v,e){self.__netjes_v[i]=[v,e]};Object.prototype.hasOwnProperty.call(self,'__netjes_v')||(self.__netjes_v={})";
35
+ /** JSON that can sit inside a <script> element. */
36
+ export declare function htmlSafeJson(value: unknown): string;
37
+ /** The script that hands one deferred value (or its error) to the browser. */
38
+ export declare function deferredScript(id: string, value: unknown, error?: string, nonce?: string): string;
39
+ //# sourceMappingURL=deferred.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deferred.d.ts","sourceRoot":"","sources":["../src/deferred.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,cAAc,CAAC;AAE5D,wHAAwH;AACxH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;IAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAE3G,wBAAgB,cAAc,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAuB/C;AAED,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC;AAM/D,iHAAiH;AACjH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAoB9E;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,GAAG,CAAC,CAW1F;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB;IAa5B,MAAM,EAAiC,cAAc;IACrD,OAAO,KAAK,MAAM,SAAS,OAAO;IAGlC,MAAM,KAAK,MAAM,WAAW,MAAM;IAGlC,8FAA8F;IAC9F,aAAa,UAAU,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM;EAMhD;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,6JAC6H,CAAC;AAI7J,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAQjG"}
@@ -0,0 +1,123 @@
1
+ export function createDeferred() {
2
+ let resolve;
3
+ let reject;
4
+ const promise = new Promise((res, rej) => {
5
+ resolve = res;
6
+ reject = rej;
7
+ });
8
+ promise.status = 'pending';
9
+ return {
10
+ promise,
11
+ resolve(value) {
12
+ if (promise.status !== 'pending')
13
+ return;
14
+ promise.status = 'fulfilled';
15
+ promise.value = value;
16
+ resolve(value);
17
+ },
18
+ reject(reason) {
19
+ if (promise.status !== 'pending')
20
+ return;
21
+ promise.status = 'rejected';
22
+ promise.reason = reason;
23
+ reject(reason);
24
+ },
25
+ };
26
+ }
27
+ function isPlaceholder(value) {
28
+ return value.$netjes === 'deferred' && typeof value.id === 'string';
29
+ }
30
+ /** Replaces every `{ $netjes: 'deferred', id }` in a JSON value; returns the same object when there are none. */
31
+ export function reviveDeferred(value, source) {
32
+ if (typeof value !== 'object' || value === null)
33
+ return value;
34
+ if (Array.isArray(value)) {
35
+ let changed = false;
36
+ const items = value.map((item) => {
37
+ const revived = reviveDeferred(item, source);
38
+ if (revived !== item)
39
+ changed = true;
40
+ return revived;
41
+ });
42
+ return changed ? items : value;
43
+ }
44
+ if (isPlaceholder(value))
45
+ return source(value.id);
46
+ let changed = false;
47
+ const entries = Object.entries(value).map(([key, item]) => {
48
+ const revived = reviveDeferred(item, source);
49
+ if (revived !== item)
50
+ changed = true;
51
+ return [key, revived];
52
+ });
53
+ // Keys are defined, not assigned: JSON.parse keeps "__proto__" as a key, and assigning it would set the prototype.
54
+ return changed ? Object.fromEntries(entries) : value;
55
+ }
56
+ /** Revives the placeholders in a page's props and its segments' props. */
57
+ export function revivePayload(payload, source) {
58
+ const props = reviveDeferred(payload.props, source);
59
+ let segmentsChanged = false;
60
+ const segments = payload.segments.map((segment) => {
61
+ if (!segment.props)
62
+ return segment;
63
+ const revived = reviveDeferred(segment.props, source);
64
+ if (revived === segment.props)
65
+ return segment;
66
+ segmentsChanged = true;
67
+ return { ...segment, props: revived };
68
+ });
69
+ return props === payload.props && !segmentsChanged ? payload : { ...payload, props, segments };
70
+ }
71
+ /** A source that hands out one deferred per id and settles it when the value arrives. */
72
+ export function deferredRegistry() {
73
+ const entries = new Map();
74
+ const entry = (id) => {
75
+ let found = entries.get(id);
76
+ if (!found) {
77
+ found = createDeferred();
78
+ // Unread values must not surface as unhandled rejections.
79
+ found.promise.catch(() => { });
80
+ entries.set(id, found);
81
+ }
82
+ return found;
83
+ };
84
+ return {
85
+ source: ((id) => entry(id).promise),
86
+ resolve(id, value) {
87
+ entry(id).resolve(value);
88
+ },
89
+ reject(id, message) {
90
+ entry(id).reject(new Error(message));
91
+ },
92
+ /** Rejects every value still pending, e.g. when the stream that was to deliver them ended. */
93
+ rejectPending(message) {
94
+ for (const [id, deferred] of entries) {
95
+ if (deferred.promise.status === 'pending')
96
+ deferred.reject(new Error(message(id)));
97
+ }
98
+ },
99
+ };
100
+ }
101
+ /**
102
+ * Inline in every document: collects deferred values streamed as `<script>self.__netjes_d(id, value, error)</script>`
103
+ * until the client entry takes over (it replaces `__netjes_d`). The async client entry can run first, so this never
104
+ * replaces a handler that is already there. It creates `__netjes_v` up front, as a window property of its own: until
105
+ * then, page markup with `id="__netjes_v"` would be what that name reads (DOM clobbering).
106
+ */
107
+ export const DEFERRED_BOOTSTRAP = "self.__netjes_d=self.__netjes_d||function(i,v,e){self.__netjes_v[i]=[v,e]};Object.prototype.hasOwnProperty.call(self,'__netjes_v')||(self.__netjes_v={})";
108
+ const UNSAFE = /[<>&\u2028\u2029]/g;
109
+ /** JSON that can sit inside a <script> element. */
110
+ export function htmlSafeJson(value) {
111
+ return (JSON.stringify(value) ?? 'null').replace(UNSAFE, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
112
+ }
113
+ /** The script that hands one deferred value (or its error) to the browser. */
114
+ export function deferredScript(id, value, error, nonce) {
115
+ // The value goes as JSON text for JSON.parse, which reads it as the other paths do: as an object literal, a
116
+ // "__proto__" key would set the object's prototype instead.
117
+ const json = htmlSafeJson(JSON.stringify(value) ?? 'null');
118
+ const args = error === undefined ? `${htmlSafeJson(id)},JSON.parse(${json})` : `${htmlSafeJson(id)},null,${htmlSafeJson(error)}`;
119
+ // Under a CSP with nonces, inline scripts run only with the page's nonce (base64, but escaped all the same).
120
+ const attribute = nonce ? ` nonce="${nonce.replace(/[^A-Za-z0-9+/=_-]/g, '')}"` : '';
121
+ return `<script${attribute}>self.__netjes_d(${args})</script>`;
122
+ }
123
+ //# sourceMappingURL=deferred.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deferred.js","sourceRoot":"","sources":["../src/deferred.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ComponentType, FunctionComponent } from 'react';
2
+ import type { LayoutProps } from './modules.ts';
3
+ import { type StandardSchemaV1 } from './schema.ts';
4
+ export type { StandardSchemaV1 } from './schema.ts';
5
+ /**
6
+ * Declares a page whose props (sent by the C# loader) are validated with a Standard Schema before rendering,
7
+ * so the component receives parsed, typed props instead of trusting the server's JSON.
8
+ */
9
+ export declare function definePage<Schema extends StandardSchemaV1>(schema: Schema, Page: ComponentType<StandardSchemaV1.InferOutput<Schema>>): FunctionComponent<Record<string, unknown>>;
10
+ /**
11
+ * Declares a layout whose loader props (from `MapLayoutLoader` in C#) are validated with a Standard Schema. The
12
+ * component gets the parsed props next to `children` and `params`.
13
+ */
14
+ export declare function defineLayout<Schema extends StandardSchemaV1>(schema: Schema, Layout: ComponentType<StandardSchemaV1.InferOutput<Schema> & LayoutProps>): FunctionComponent<LayoutProps & Record<string, unknown>>;
15
+ //# sourceMappingURL=define-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-page.d.ts","sourceRoot":"","sources":["../src/define-page.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAa,MAAM,OAAO,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,aAAa,CAAC;AAE/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAS,gBAAgB,EACxD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,CAAC,gBAAgB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,GACxD,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAO5C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,SAAS,gBAAgB,EAC1D,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,GACxE,iBAAiB,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAW1D"}
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { parseWith } from './schema.js';
3
+ /**
4
+ * Declares a page whose props (sent by the C# loader) are validated with a Standard Schema before rendering,
5
+ * so the component receives parsed, typed props instead of trusting the server's JSON.
6
+ */
7
+ export function definePage(schema, Page) {
8
+ function ValidatedPage(props) {
9
+ const parsed = parseWith(schema, props, 'props');
10
+ return _jsx(Page, { ...parsed });
11
+ }
12
+ ValidatedPage.displayName = `definePage(${Page.displayName ?? Page.name ?? 'Page'})`;
13
+ return ValidatedPage;
14
+ }
15
+ /**
16
+ * Declares a layout whose loader props (from `MapLayoutLoader` in C#) are validated with a Standard Schema. The
17
+ * component gets the parsed props next to `children` and `params`.
18
+ */
19
+ export function defineLayout(schema, Layout) {
20
+ function ValidatedLayout({ children, params, ...props }) {
21
+ const parsed = parseWith(schema, props, 'layout props');
22
+ return (_jsx(Layout, { ...parsed, params: params, children: children }));
23
+ }
24
+ ValidatedLayout.displayName = `defineLayout(${Layout.displayName ?? Layout.name ?? 'Layout'})`;
25
+ return ValidatedLayout;
26
+ }
27
+ //# sourceMappingURL=define-page.js.map