netjes 0.0.0-stage → 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +54 -2
- package/bin/netjes.mjs +13 -0
- package/cli/agents.mjs +66 -0
- package/cli/commands.mjs +467 -0
- package/cli/help.mjs +89 -0
- package/cli/info.mjs +454 -0
- package/cli/main.mjs +167 -0
- package/cli/packages.mjs +19 -0
- package/cli/process-io.mjs +163 -0
- package/cli/routes.mjs +31 -0
- package/cli/scaffold.mjs +193 -0
- package/cli/suggest.mjs +50 -0
- package/cli/terminal.mjs +40 -0
- package/dist/actions.d.ts +98 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +176 -0
- package/dist/actions.js.map +1 -0
- package/dist/client-entry.d.ts +2 -0
- package/dist/client-entry.d.ts.map +1 -0
- package/dist/client-entry.js +126 -0
- package/dist/client-entry.js.map +1 -0
- package/dist/deferred.d.ts +39 -0
- package/dist/deferred.d.ts.map +1 -0
- package/dist/deferred.js +123 -0
- package/dist/deferred.js.map +1 -0
- package/dist/define-page.d.ts +15 -0
- package/dist/define-page.d.ts.map +1 -0
- package/dist/define-page.js +27 -0
- package/dist/define-page.js.map +1 -0
- package/dist/document.d.ts +19 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +17 -0
- package/dist/document.js.map +1 -0
- package/dist/draft-mode.d.ts +5 -0
- package/dist/draft-mode.d.ts.map +1 -0
- package/dist/draft-mode.js +6 -0
- package/dist/draft-mode.js.map +1 -0
- package/dist/dynamic.d.ts +20 -0
- package/dist/dynamic.d.ts.map +1 -0
- package/dist/dynamic.js +37 -0
- package/dist/dynamic.js.map +1 -0
- package/dist/form-data.d.ts +10 -0
- package/dist/form-data.d.ts.map +1 -0
- package/dist/form-data.js +10 -0
- package/dist/form-data.js.map +1 -0
- package/dist/form.d.ts +35 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +80 -0
- package/dist/form.js.map +1 -0
- package/dist/href.d.ts +25 -0
- package/dist/href.d.ts.map +1 -0
- package/dist/href.js +71 -0
- package/dist/href.js.map +1 -0
- package/dist/image.d.ts +36 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +60 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +17 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +46 -0
- package/dist/link.js.map +1 -0
- package/dist/locale.d.ts +3 -0
- package/dist/locale.d.ts.map +1 -0
- package/dist/locale.js +6 -0
- package/dist/locale.js.map +1 -0
- package/dist/metadata.d.ts +83 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +100 -0
- package/dist/metadata.js.map +1 -0
- package/dist/modules.d.ts +74 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +85 -0
- package/dist/modules.js.map +1 -0
- package/dist/nonce.d.ts +8 -0
- package/dist/nonce.d.ts.map +1 -0
- package/dist/nonce.js +12 -0
- package/dist/nonce.js.map +1 -0
- package/dist/payload.d.ts +171 -0
- package/dist/payload.d.ts.map +1 -0
- package/dist/payload.js +273 -0
- package/dist/payload.js.map +1 -0
- package/dist/public-config.d.ts +11 -0
- package/dist/public-config.d.ts.map +1 -0
- package/dist/public-config.js +10 -0
- package/dist/public-config.js.map +1 -0
- package/dist/query.d.ts +22 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +68 -0
- package/dist/query.js.map +1 -0
- package/dist/render-cleanup.d.ts +6 -0
- package/dist/render-cleanup.d.ts.map +1 -0
- package/dist/render-cleanup.js +7 -0
- package/dist/render-cleanup.js.map +1 -0
- package/dist/root.d.ts +12 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +26 -0
- package/dist/root.js.map +1 -0
- package/dist/route-tree.d.ts +56 -0
- package/dist/route-tree.d.ts.map +1 -0
- package/dist/route-tree.js +86 -0
- package/dist/route-tree.js.map +1 -0
- package/dist/router.d.ts +118 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +938 -0
- package/dist/router.js.map +1 -0
- package/dist/schema.d.ts +35 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +74 -0
- package/dist/schema.js.map +1 -0
- package/dist/script.d.ts +23 -0
- package/dist/script.d.ts.map +1 -0
- package/dist/script.js +89 -0
- package/dist/script.js.map +1 -0
- package/dist/server-entry.d.ts +2 -0
- package/dist/server-entry.d.ts.map +1 -0
- package/dist/server-entry.js +4 -0
- package/dist/server-entry.js.map +1 -0
- package/dist/server-render.d.ts +19 -0
- package/dist/server-render.d.ts.map +1 -0
- package/dist/server-render.js +193 -0
- package/dist/server-render.js.map +1 -0
- package/dist/testing-setup.d.ts +2 -0
- package/dist/testing-setup.d.ts.map +1 -0
- package/dist/testing-setup.js +8 -0
- package/dist/testing-setup.js.map +1 -0
- package/dist/testing.d.ts +76 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +106 -0
- package/dist/testing.js.map +1 -0
- package/dist/vitals-config.d.ts +4 -0
- package/dist/vitals-config.d.ts.map +1 -0
- package/dist/vitals-config.js +5 -0
- package/dist/vitals-config.js.map +1 -0
- package/dist/vitals.d.ts +13 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +60 -0
- package/dist/vitals.js.map +1 -0
- package/dist/zod.d.ts +19 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +65 -0
- package/dist/zod.js.map +1 -0
- package/package.json +147 -4
- package/sidecar/server.mjs +325 -0
- package/skills/netjes/SKILL.md +93 -0
- package/skills/netjes/references/caching-and-streaming.md +68 -0
- package/skills/netjes/references/data-and-actions.md +98 -0
- package/skills/netjes/references/deployment.md +57 -0
- package/skills/netjes/references/routing.md +65 -0
- package/skills/netjes/references/testing.md +57 -0
- package/src/actions.tsx +241 -0
- package/src/client-entry.tsx +126 -0
- package/src/deferred.ts +134 -0
- package/src/define-page.tsx +41 -0
- package/src/document.tsx +49 -0
- package/src/draft-mode.ts +6 -0
- package/src/dynamic.tsx +64 -0
- package/src/form-data.ts +10 -0
- package/src/form.tsx +122 -0
- package/src/href.ts +79 -0
- package/src/image.tsx +91 -0
- package/src/index.ts +40 -0
- package/src/link.tsx +73 -0
- package/src/locale.ts +6 -0
- package/src/metadata.tsx +181 -0
- package/src/modules.ts +133 -0
- package/src/nonce.tsx +14 -0
- package/src/payload.ts +405 -0
- package/src/public-config.tsx +19 -0
- package/src/query.tsx +82 -0
- package/src/render-cleanup.ts +7 -0
- package/src/root.tsx +39 -0
- package/src/route-tree.tsx +139 -0
- package/src/router.tsx +1188 -0
- package/src/schema.ts +98 -0
- package/src/script.tsx +103 -0
- package/src/server-entry.tsx +4 -0
- package/src/server-render.tsx +223 -0
- package/src/testing-setup.ts +8 -0
- package/src/testing.tsx +177 -0
- package/src/virtual.d.ts +12 -0
- package/src/vitals-config.ts +6 -0
- package/src/vitals.tsx +68 -0
- package/src/zod.ts +72 -0
- package/template/.dockerignore +11 -0
- package/template/Dockerfile +58 -0
- package/template/Features/Home/HomePage.cs +32 -0
- package/template/Program.cs +28 -0
- package/template/Properties/launchSettings.json +13 -0
- package/template/README.md +24 -0
- package/template/SiteProps.cs +4 -0
- package/template/_AGENTS.md +64 -0
- package/template/_CLAUDE.md +1 -0
- package/template/__NAME__.csproj +11 -0
- package/template/_gitattributes +2 -0
- package/template/_gitignore +11 -0
- package/template/app/about/page.tsx +17 -0
- package/template/app/error.tsx +15 -0
- package/template/app/layout.tsx +33 -0
- package/template/app/not-found.tsx +14 -0
- package/template/app/page.tsx +14 -0
- package/template/appsettings.Development.json +3 -0
- package/template/appsettings.json +9 -0
- package/template/components/Greeting.test.tsx +25 -0
- package/template/components/Greeting.tsx +26 -0
- package/template/components/HelloForm.test.tsx +27 -0
- package/template/components/HelloForm.tsx +34 -0
- package/template/docker-compose.override.yml +15 -0
- package/template/docker-compose.prod.yml +26 -0
- package/template/docker-compose.yml +16 -0
- package/template/generated/netjes.ts +73 -0
- package/template/package.json +36 -0
- package/template/public/favicon.svg +6 -0
- package/template/styles/global.css +7 -0
- package/template/tsconfig.json +19 -0
- package/template/vite.config.ts +7 -0
- package/vite/plugin.d.ts +29 -0
- package/vite/plugin.mjs +291 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type DefaultOptions, QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import { type ActionState } from './actions.tsx';
|
|
4
|
+
import type { AntiforgeryToken, Params } from './payload.ts';
|
|
5
|
+
import { type NavigationState } from './router.tsx';
|
|
6
|
+
/** What the fake app of `createTestApp` reports to the components under test. */
|
|
7
|
+
export type TestAppOptions = {
|
|
8
|
+
/** The current URL. Default `/`. */
|
|
9
|
+
url?: string;
|
|
10
|
+
/** The page's route pattern, e.g. `/blog/[slug]`. */
|
|
11
|
+
route?: string;
|
|
12
|
+
/** What `useLocale()` returns. */
|
|
13
|
+
locale?: string;
|
|
14
|
+
/** What `useDraftMode()` reports. */
|
|
15
|
+
draftMode?: boolean;
|
|
16
|
+
/** What `usePublicConfig()` returns, like the server's `Netjes:PublicConfig` section. */
|
|
17
|
+
publicConfig?: Record<string, unknown>;
|
|
18
|
+
params?: Params;
|
|
19
|
+
/** What the server would send as `SharedProps`. */
|
|
20
|
+
shared?: unknown;
|
|
21
|
+
/** Seeded into the QueryClient the way `PageResult.WithQuery` seeds it, as of now. */
|
|
22
|
+
queries?: ReadonlyArray<{
|
|
23
|
+
key: readonly unknown[];
|
|
24
|
+
data: unknown;
|
|
25
|
+
}>;
|
|
26
|
+
/** Like `netjes({ query: { defaultOptions } })`; the harness also turns retries off. */
|
|
27
|
+
defaultOptions?: DefaultOptions;
|
|
28
|
+
/** What `useNavigation()` returns. Default idle. */
|
|
29
|
+
navigation?: NavigationState;
|
|
30
|
+
/** The token `<Form method="post">` adds. Default a test token; `false` for none. */
|
|
31
|
+
antiforgery?: AntiforgeryToken | false;
|
|
32
|
+
queryClient?: QueryClient;
|
|
33
|
+
/**
|
|
34
|
+
* Answers server action calls by name; unknown actions succeed with no result. Like `.WithQuery` and
|
|
35
|
+
* `.Invalidate` in C#, an answer can set and invalidate query entries.
|
|
36
|
+
*/
|
|
37
|
+
actions?: Record<string, (input: unknown) => TestActionAnswer | Promise<TestActionAnswer>>;
|
|
38
|
+
};
|
|
39
|
+
/** What a fake server action answers: a success or failure state, plus query entries to set or invalidate. */
|
|
40
|
+
export type TestActionAnswer = ActionState<unknown> & {
|
|
41
|
+
queries?: ReadonlyArray<{
|
|
42
|
+
key: readonly unknown[];
|
|
43
|
+
data: unknown;
|
|
44
|
+
}>;
|
|
45
|
+
invalidate?: ReadonlyArray<readonly unknown[]>;
|
|
46
|
+
/** After a failure: the submitted fields the state's `values` keep, as the server names them (the action's declared, non-private fields). Default: all. */
|
|
47
|
+
fields?: readonly string[];
|
|
48
|
+
};
|
|
49
|
+
/** One thing the component under test did: a navigation, a router call, a form post or a server action call. */
|
|
50
|
+
export type TestNavigation = {
|
|
51
|
+
type: 'push' | 'replace';
|
|
52
|
+
href: string;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'refresh' | 'back' | 'forward';
|
|
55
|
+
} | {
|
|
56
|
+
type: 'submit';
|
|
57
|
+
action: string;
|
|
58
|
+
data: URLSearchParams | FormData;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'action';
|
|
61
|
+
name: string;
|
|
62
|
+
input: unknown;
|
|
63
|
+
};
|
|
64
|
+
/** The fake app `createTestApp` returns: a wrapper for Testing Library and a record of what the components did. */
|
|
65
|
+
export type TestApp = {
|
|
66
|
+
/** Pass as Testing Library's `wrapper`. */
|
|
67
|
+
Wrapper: (props: {
|
|
68
|
+
children: ReactNode;
|
|
69
|
+
}) => ReactNode;
|
|
70
|
+
/** Every navigation, router call and form post, in order. */
|
|
71
|
+
navigations: TestNavigation[];
|
|
72
|
+
queryClient: QueryClient;
|
|
73
|
+
};
|
|
74
|
+
/** Creates a fake app around the components under test: pass its `Wrapper` to Testing Library's `render`. The router loads nothing; it records what was asked of it. */
|
|
75
|
+
export declare function createTestApp(options?: TestAppOptions): TestApp;
|
|
76
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAE,WAAW,EAAgC,MAAM,uBAAuB,CAAC;AACvG,OAAO,EAAE,KAAK,SAAS,EAAsC,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAqB,KAAK,WAAW,EAAmB,MAAM,eAAe,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAe,MAAM,EAAE,MAAM,cAAc,CAAC;AAG1E,OAAO,EAAuB,KAAK,eAAe,EAAmC,MAAM,cAAc,CAAC;AAE1G,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,OAAO,CAAC,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACpE,wFAAwF;IACxF,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oDAAoD;IACpD,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,qFAAqF;IACrF,WAAW,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAC;IACvC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;CAC5F,CAAC;AAEF,8GAA8G;AAC9G,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG;IACpD,OAAO,CAAC,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACpE,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IAC/C,2JAA2J;IAC3J,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,gHAAgH;AAChH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAErD,mHAAmH;AACnH,MAAM,MAAM,OAAO,GAAG;IACpB,2CAA2C;IAC3C,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,KAAK,SAAS,CAAC;IACvD,6DAA6D;IAC7D,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAmBF,wKAAwK;AACxK,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CA6FnE"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Test harness for components and pages that use Netjes: `render(<Page />, { wrapper: createTestApp().Wrapper })`.
|
|
3
|
+
// The router is a fake: it records what the component asked for and moves to the new URL, but loads nothing.
|
|
4
|
+
import { QueryClient, QueryClientProvider, hydrate } from '@tanstack/react-query';
|
|
5
|
+
import { useLayoutEffect, useMemo, useState } from 'react';
|
|
6
|
+
import { setActionClient } from './actions.js';
|
|
7
|
+
import { PublicConfigProvider } from './public-config.js';
|
|
8
|
+
import { dehydratedStateOf, mergeOptions } from './query.js';
|
|
9
|
+
import { RouterContexts } from './router.js';
|
|
10
|
+
/**
|
|
11
|
+
* Registers the stand-in in the commit, and only then: a render React throws away (`renderToString`, children that
|
|
12
|
+
* threw) leaves calls made outside any Wrapper failing at once. React runs the effects of earlier siblings first, and
|
|
13
|
+
* a component's after those of the components inside it, so rendered ahead of the children this runs before any of
|
|
14
|
+
* their effects, layout effects included.
|
|
15
|
+
*/
|
|
16
|
+
function RegisterActionClient({ client }) {
|
|
17
|
+
useLayoutEffect(() => setActionClient(client), [client]);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const TEST_TOKEN = { formFieldName: '__RequestVerificationToken', headerName: 'RequestVerificationToken', requestToken: 'test-token' };
|
|
21
|
+
function dataOf(body) {
|
|
22
|
+
return body.contentType === null ? body.body : new URLSearchParams(body.body);
|
|
23
|
+
}
|
|
24
|
+
/** Creates a fake app around the components under test: pass its `Wrapper` to Testing Library's `render`. The router loads nothing; it records what was asked of it. */
|
|
25
|
+
export function createTestApp(options = {}) {
|
|
26
|
+
const navigations = [];
|
|
27
|
+
const defaults = mergeOptions(options.defaultOptions);
|
|
28
|
+
const queryClient = options.queryClient ??
|
|
29
|
+
new QueryClient({ defaultOptions: { ...defaults, queries: { retry: false, gcTime: Number.POSITIVE_INFINITY, ...defaults.queries } } });
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const seeded = dehydratedStateOf(options.queries?.map((query) => ({ ...query, updatedAt: now })));
|
|
32
|
+
if (seeded)
|
|
33
|
+
hydrate(queryClient, seeded);
|
|
34
|
+
const initialUrl = new URL(options.url ?? '/', window.location.origin);
|
|
35
|
+
// Forms without an action post to the current URL, which they read from the window.
|
|
36
|
+
window.history.replaceState(null, '', initialUrl.href);
|
|
37
|
+
const antiforgery = options.antiforgery === false ? undefined : (options.antiforgery ?? TEST_TOKEN);
|
|
38
|
+
// The router's stand-in for server action calls: records each one and answers it from `options.actions`.
|
|
39
|
+
const actionClient = {
|
|
40
|
+
async invoke(name, _url, input) {
|
|
41
|
+
navigations.push({ type: 'action', name, input });
|
|
42
|
+
const answer = (await options.actions?.[name]?.(input)) ?? { ok: true, result: undefined };
|
|
43
|
+
for (const query of answer.queries ?? [])
|
|
44
|
+
queryClient.setQueryData(query.key, query.data);
|
|
45
|
+
for (const queryKey of answer.invalidate ?? [])
|
|
46
|
+
void queryClient.invalidateQueries({ queryKey: [...queryKey] });
|
|
47
|
+
return answer.ok
|
|
48
|
+
? { kind: 'action', ok: true, status: 200, result: answer.result, revalidate: false }
|
|
49
|
+
: {
|
|
50
|
+
kind: 'action',
|
|
51
|
+
ok: false,
|
|
52
|
+
status: answer.status,
|
|
53
|
+
errors: answer.errors,
|
|
54
|
+
...(answer.fields ? { fields: [...answer.fields] } : {}),
|
|
55
|
+
revalidate: false,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
function Wrapper({ children }) {
|
|
60
|
+
const [url, setUrl] = useState(initialUrl);
|
|
61
|
+
const router = useMemo(() => {
|
|
62
|
+
const go = (type, href) => {
|
|
63
|
+
navigations.push({ type, href });
|
|
64
|
+
const next = new URL(href, window.location.href);
|
|
65
|
+
window.history.replaceState(null, '', next.href);
|
|
66
|
+
setUrl(next);
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
push: (href) => go('push', href),
|
|
71
|
+
replace: (href) => go('replace', href),
|
|
72
|
+
prefetch: () => { },
|
|
73
|
+
refresh: () => {
|
|
74
|
+
navigations.push({ type: 'refresh' });
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
},
|
|
77
|
+
back: () => navigations.push({ type: 'back' }),
|
|
78
|
+
forward: () => navigations.push({ type: 'forward' }),
|
|
79
|
+
submit: (action, body) => {
|
|
80
|
+
navigations.push({ type: 'submit', action: action.href, data: dataOf(body) });
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
},
|
|
83
|
+
onActionResponse: () => () => { },
|
|
84
|
+
onCookiesChanged: () => () => { },
|
|
85
|
+
};
|
|
86
|
+
}, []);
|
|
87
|
+
const payload = useMemo(() => ({
|
|
88
|
+
kind: 'page',
|
|
89
|
+
route: options.route ?? null,
|
|
90
|
+
page: null,
|
|
91
|
+
segments: [],
|
|
92
|
+
props: {},
|
|
93
|
+
params: options.params ?? {},
|
|
94
|
+
shared: options.shared ?? null,
|
|
95
|
+
status: 200,
|
|
96
|
+
url: url.pathname + url.search,
|
|
97
|
+
version: 'test',
|
|
98
|
+
...(antiforgery ? { antiforgery } : {}),
|
|
99
|
+
...(options.draftMode ? { draftMode: true } : {}),
|
|
100
|
+
...(options.locale ? { locale: options.locale } : {}),
|
|
101
|
+
}), [url]);
|
|
102
|
+
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(PublicConfigProvider, { value: options.publicConfig ?? {}, children: _jsxs(RouterContexts, { router: router, payload: payload, url: url, navigation: options.navigation ?? { state: 'idle' }, children: [_jsx(RegisterActionClient, { client: actionClient }), children] }) }) }));
|
|
103
|
+
}
|
|
104
|
+
return { Wrapper, navigations, queryClient };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Where `<WebVitals />` sends reports: set when the server collects them (`NetjesOptions.CollectWebVitals`). */
|
|
2
|
+
export declare const VitalsEndpointContext: import("react").Context<string | undefined>;
|
|
3
|
+
export declare const VitalsEndpointProvider: import("react").Provider<string | undefined>;
|
|
4
|
+
//# sourceMappingURL=vitals-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitals-config.d.ts","sourceRoot":"","sources":["../src/vitals-config.ts"],"names":[],"mappings":"AAEA,iHAAiH;AACjH,eAAO,MAAM,qBAAqB,6CAA+C,CAAC;AAElF,eAAO,MAAM,sBAAsB,8CAAiC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
/** Where `<WebVitals />` sends reports: set when the server collects them (`NetjesOptions.CollectWebVitals`). */
|
|
3
|
+
export const VitalsEndpointContext = createContext(undefined);
|
|
4
|
+
export const VitalsEndpointProvider = VitalsEndpointContext.Provider;
|
|
5
|
+
//# sourceMappingURL=vitals-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitals-config.js","sourceRoot":"","sources":["../src/vitals-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,iHAAiH;AACjH,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAqB,SAAS,CAAC,CAAC;AAElF,MAAM,CAAC,MAAM,sBAAsB,GAAG,qBAAqB,CAAC,QAAQ,CAAC"}
|
package/dist/vitals.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Metric } from 'web-vitals';
|
|
2
|
+
/** One measurement, as `web-vitals` reports it, with the route pattern of the page it was measured on. */
|
|
3
|
+
export type WebVitalsMetric = Pick<Metric, 'name' | 'value' | 'rating' | 'delta' | 'id' | 'navigationType'> & {
|
|
4
|
+
route: string | null;
|
|
5
|
+
};
|
|
6
|
+
/** Like Next.js's `useReportWebVitals`: calls back with each Core Web Vital of this page load (also ones measured earlier). */
|
|
7
|
+
export declare function useReportWebVitals(callback: (metric: WebVitalsMetric) => void): void;
|
|
8
|
+
/**
|
|
9
|
+
* Sends Core Web Vitals to the server, which records them as `netjes.web_vitals.*` histograms (turn on
|
|
10
|
+
* `NetjesOptions.CollectWebVitals`). Render it once, e.g. in the root layout.
|
|
11
|
+
*/
|
|
12
|
+
export declare function WebVitals(): null;
|
|
13
|
+
//# sourceMappingURL=vitals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitals.d.ts","sourceRoot":"","sources":["../src/vitals.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAsC,MAAM,YAAY,CAAC;AAI7E,0GAA0G;AAC1G,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,gBAAgB,CAAC,GAAG;IAC5G,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAwBF,+HAA+H;AAC/H,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,QAe7E;AAKD;;;GAGG;AACH,wBAAgB,SAAS,SASxB"}
|
package/dist/vitals.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// netjes/vitals: Core Web Vitals, measured with the web-vitals package (an optional peer dependency of netjes).
|
|
2
|
+
import { useContext, useEffect, useRef } from 'react';
|
|
3
|
+
import { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals';
|
|
4
|
+
import { usePagePayload } from './router.js';
|
|
5
|
+
import { VitalsEndpointContext } from './vitals-config.js';
|
|
6
|
+
const subscribers = new Set();
|
|
7
|
+
const reported = [];
|
|
8
|
+
let started = false;
|
|
9
|
+
/** Web vitals describe the page load, so they are measured once, for the route the document was loaded on. */
|
|
10
|
+
function start(route) {
|
|
11
|
+
if (started)
|
|
12
|
+
return;
|
|
13
|
+
started = true;
|
|
14
|
+
const report = ({ name, value, rating, delta, id, navigationType }) => {
|
|
15
|
+
const metric = { name, value, rating, delta, id, navigationType, route };
|
|
16
|
+
reported.push(metric);
|
|
17
|
+
for (const subscriber of subscribers)
|
|
18
|
+
subscriber(metric);
|
|
19
|
+
};
|
|
20
|
+
onCLS(report);
|
|
21
|
+
onFCP(report);
|
|
22
|
+
onINP(report);
|
|
23
|
+
onLCP(report);
|
|
24
|
+
onTTFB(report);
|
|
25
|
+
}
|
|
26
|
+
/** Like Next.js's `useReportWebVitals`: calls back with each Core Web Vital of this page load (also ones measured earlier). */
|
|
27
|
+
export function useReportWebVitals(callback) {
|
|
28
|
+
const { route } = usePagePayload();
|
|
29
|
+
const latest = useRef(callback);
|
|
30
|
+
latest.current = callback;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const subscriber = (metric) => latest.current(metric);
|
|
33
|
+
for (const metric of reported)
|
|
34
|
+
subscriber(metric);
|
|
35
|
+
subscribers.add(subscriber);
|
|
36
|
+
start(route);
|
|
37
|
+
return () => {
|
|
38
|
+
subscribers.delete(subscriber);
|
|
39
|
+
};
|
|
40
|
+
// Subscribes once; the route is the one the page was loaded on.
|
|
41
|
+
}, []);
|
|
42
|
+
}
|
|
43
|
+
/** The measurements a `<WebVitals />` has sent, so one that mounts again does not send the replayed ones twice. */
|
|
44
|
+
const sent = new WeakSet();
|
|
45
|
+
/**
|
|
46
|
+
* Sends Core Web Vitals to the server, which records them as `netjes.web_vitals.*` histograms (turn on
|
|
47
|
+
* `NetjesOptions.CollectWebVitals`). Render it once, e.g. in the root layout.
|
|
48
|
+
*/
|
|
49
|
+
export function WebVitals() {
|
|
50
|
+
const endpoint = useContext(VitalsEndpointContext);
|
|
51
|
+
useReportWebVitals((metric) => {
|
|
52
|
+
if (!endpoint || sent.has(metric))
|
|
53
|
+
return;
|
|
54
|
+
sent.add(metric);
|
|
55
|
+
const { name, value, rating, route, navigationType } = metric;
|
|
56
|
+
navigator.sendBeacon(endpoint, JSON.stringify({ name, value, rating, route, navigationType }));
|
|
57
|
+
});
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=vitals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitals.js","sourceRoot":"","sources":["../src/vitals.tsx"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAe,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAS3D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAc,CAAC;AAC1C,MAAM,QAAQ,GAAsB,EAAE,CAAC;AACvC,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB,8GAA8G;AAC9G,SAAS,KAAK,CAAC,KAAoB;IACjC,IAAI,OAAO;QAAE,OAAO;IACpB,OAAO,GAAG,IAAI,CAAC;IACf,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,EAAU,EAAE,EAAE;QAC5E,MAAM,MAAM,GAAoB,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QAC1F,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,KAAK,MAAM,UAAU,IAAI,WAAW;YAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC;IACF,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,MAAM,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAED,+HAA+H;AAC/H,MAAM,UAAU,kBAAkB,CAAC,QAA2C;IAC5E,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAC;IAE1B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,UAAU,GAAe,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClE,KAAK,MAAM,MAAM,IAAI,QAAQ;YAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QAClD,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5B,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,OAAO,GAAG,EAAE;YACV,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC,CAAC;QACF,gEAAgE;IAClE,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAED,mHAAmH;AACnH,MAAM,IAAI,GAAG,IAAI,OAAO,EAAmB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,QAAQ,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC;IACnD,kBAAkB,CAAC,CAAC,MAAM,EAAE,EAAE;QAC5B,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO;QAC1C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;QAC9D,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/zod.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* What `deferred()` parses to: a thenable that React's `use()` reads (it records `status` and `value` as React
|
|
4
|
+
* expects). Not a Promise, because zod would then treat the whole schema as async.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DeferredValue<T> implements PromiseLike<T> {
|
|
7
|
+
status: 'pending' | 'fulfilled' | 'rejected';
|
|
8
|
+
value?: T;
|
|
9
|
+
reason?: unknown;
|
|
10
|
+
private readonly settled;
|
|
11
|
+
constructor(source: PromiseLike<unknown>, parse: (value: unknown) => T);
|
|
12
|
+
then<R1 = T, R2 = never>(onFulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null, onRejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A `Page.Defer` value in page or layout props: a promise of `schema`'s output. Read it with React's `use()` inside
|
|
16
|
+
* a Suspense boundary. The resolved value is validated; a mismatch rejects (and reaches the error boundary).
|
|
17
|
+
*/
|
|
18
|
+
export declare function deferred<Schema extends z.ZodType>(schema: Schema): z.ZodType<PromiseLike<z.output<Schema>>, Promise<unknown>>;
|
|
19
|
+
//# sourceMappingURL=zod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.d.ts","sourceRoot":"","sources":["../src/zod.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,qBAAa,aAAa,CAAC,CAAC,CAAE,YAAW,WAAW,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,CAAa;IACzD,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IAErC,YAAY,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,EAsBrE;IAED,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,KAAK,EACrB,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EACzD,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,GAC9D,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC,CAEtB;CACF;AAID;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAoB7H"}
|
package/dist/zod.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// zod helpers (netjes/zod). Only this module imports zod, so apps on another schema library do not need it.
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* What `deferred()` parses to: a thenable that React's `use()` reads (it records `status` and `value` as React
|
|
5
|
+
* expects). Not a Promise, because zod would then treat the whole schema as async.
|
|
6
|
+
*/
|
|
7
|
+
export class DeferredValue {
|
|
8
|
+
status = 'pending';
|
|
9
|
+
value;
|
|
10
|
+
reason;
|
|
11
|
+
settled;
|
|
12
|
+
constructor(source, parse) {
|
|
13
|
+
this.settled = Promise.resolve(source).then((raw) => {
|
|
14
|
+
try {
|
|
15
|
+
const value = parse(raw);
|
|
16
|
+
this.status = 'fulfilled';
|
|
17
|
+
this.value = value;
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
this.status = 'rejected';
|
|
22
|
+
this.reason = error;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}, (reason) => {
|
|
26
|
+
this.status = 'rejected';
|
|
27
|
+
this.reason = reason;
|
|
28
|
+
throw reason;
|
|
29
|
+
});
|
|
30
|
+
// Values nobody reads must not surface as unhandled rejections.
|
|
31
|
+
this.settled.catch(() => { });
|
|
32
|
+
}
|
|
33
|
+
then(onFulfilled, onRejected) {
|
|
34
|
+
return this.settled.then(onFulfilled, onRejected);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const validated = new WeakMap();
|
|
38
|
+
/**
|
|
39
|
+
* A `Page.Defer` value in page or layout props: a promise of `schema`'s output. Read it with React's `use()` inside
|
|
40
|
+
* a Suspense boundary. The resolved value is validated; a mismatch rejects (and reaches the error boundary).
|
|
41
|
+
*/
|
|
42
|
+
export function deferred(schema) {
|
|
43
|
+
const parsed = z
|
|
44
|
+
.custom((value) => value instanceof Promise, { message: 'Expected a deferred value (Page.Defer)' })
|
|
45
|
+
.transform((promise) => {
|
|
46
|
+
// The same thenable every time, so use() does not suspend again after each render.
|
|
47
|
+
let bySchema = validated.get(promise);
|
|
48
|
+
if (!bySchema)
|
|
49
|
+
validated.set(promise, (bySchema = new Map()));
|
|
50
|
+
let result = bySchema.get(schema);
|
|
51
|
+
if (!result) {
|
|
52
|
+
result = new DeferredValue(promise, (value) => {
|
|
53
|
+
const parsed = schema.safeParse(value);
|
|
54
|
+
if (parsed.success)
|
|
55
|
+
return parsed.data;
|
|
56
|
+
throw new Error(`Invalid deferred value: ${parsed.error.issues.map((i) => `${i.path.join('.') || '(value)'}: ${i.message}`).join('; ')}`);
|
|
57
|
+
});
|
|
58
|
+
bySchema.set(schema, result);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
});
|
|
62
|
+
// zod types a transform returning a thenable as its awaited value; it is the thenable.
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=zod.js.map
|
package/dist/zod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.js","sourceRoot":"","sources":["../src/zod.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,OAAO,aAAa;IACxB,MAAM,GAAyC,SAAS,CAAC;IACzD,KAAK,CAAK;IACV,MAAM,CAAW;IACA,OAAO,CAAa;IAErC,YAAY,MAA4B,EAAE,KAA4B;QACpE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CACzC,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBACzB,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC;gBAC1B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;gBACnB,OAAO,KAAK,CAAC;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC;gBACzB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;gBACpB,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,EACD,CAAC,MAAM,EAAE,EAAE;YACT,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC;YACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,MAAM,MAAM,CAAC;QACf,CAAC,CACF,CAAC;QACF,gEAAgE;QAChE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,CACF,WAAyD,EACzD,UAA+D;QAE/D,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACpD,CAAC;CACF;AAED,MAAM,SAAS,GAAG,IAAI,OAAO,EAAkD,CAAC;AAEhF;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAA2B,MAAc;IAC/D,MAAM,MAAM,GAAG,CAAC;SACb,MAAM,CAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,YAAY,OAAO,EAAE,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAC;SACpH,SAAS,CAAC,CAAC,OAAO,EAAmC,EAAE;QACtD,mFAAmF;QACnF,IAAI,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ;YAAE,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9D,IAAI,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBACvC,IAAI,MAAM,CAAC,OAAO;oBAAE,OAAO,MAAM,CAAC,IAAI,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5I,CAAC,CAAC,CAAC;YACH,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,MAAyC,CAAC;IACnD,CAAC,CAAC,CAAC;IACL,uFAAuF;IACvF,OAAO,MAA+E,CAAC;AACzF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,149 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netjes",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Build React apps and sites on ASP.NET Core: the Next.js App Router conventions, C# loaders and server actions, streaming SSR and types from C# to the browser",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"aspnetcore",
|
|
8
|
+
"dotnet",
|
|
9
|
+
"csharp",
|
|
10
|
+
"ssr",
|
|
11
|
+
"vite",
|
|
12
|
+
"file-based-routing",
|
|
13
|
+
"tailwindcss",
|
|
14
|
+
"tanstack-query"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/DennisVerhoeven/netjes#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/DennisVerhoeven/netjes.git",
|
|
20
|
+
"directory": "packages/netjes"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/DennisVerhoeven/netjes/issues"
|
|
24
|
+
},
|
|
25
|
+
"author": "Dennis Verhoeven",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./vite": {
|
|
40
|
+
"types": "./vite/plugin.d.ts",
|
|
41
|
+
"default": "./vite/plugin.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./testing": {
|
|
44
|
+
"types": "./dist/testing.d.ts",
|
|
45
|
+
"default": "./dist/testing.js"
|
|
46
|
+
},
|
|
47
|
+
"./testing/setup": {
|
|
48
|
+
"types": "./dist/testing-setup.d.ts",
|
|
49
|
+
"default": "./dist/testing-setup.js"
|
|
50
|
+
},
|
|
51
|
+
"./vitals": {
|
|
52
|
+
"types": "./dist/vitals.d.ts",
|
|
53
|
+
"default": "./dist/vitals.js"
|
|
54
|
+
},
|
|
55
|
+
"./zod": {
|
|
56
|
+
"types": "./dist/zod.d.ts",
|
|
57
|
+
"default": "./dist/zod.js"
|
|
58
|
+
},
|
|
59
|
+
"./client-entry": {
|
|
60
|
+
"types": "./dist/client-entry.d.ts",
|
|
61
|
+
"default": "./dist/client-entry.js"
|
|
62
|
+
},
|
|
63
|
+
"./server-entry": {
|
|
64
|
+
"types": "./dist/server-entry.d.ts",
|
|
65
|
+
"default": "./dist/server-entry.js"
|
|
66
|
+
},
|
|
67
|
+
"./sidecar": "./sidecar/server.mjs"
|
|
68
|
+
},
|
|
69
|
+
"bin": {
|
|
70
|
+
"netjes": "./bin/netjes.mjs"
|
|
71
|
+
},
|
|
72
|
+
"files": [
|
|
73
|
+
"dist",
|
|
74
|
+
"src",
|
|
75
|
+
"vite",
|
|
76
|
+
"sidecar",
|
|
77
|
+
"bin",
|
|
78
|
+
"cli",
|
|
79
|
+
"skills",
|
|
80
|
+
"template",
|
|
81
|
+
"README.md",
|
|
82
|
+
"LICENSE"
|
|
83
|
+
],
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "tsc -p tsconfig.build.json",
|
|
86
|
+
"prepack": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && npm run build",
|
|
87
|
+
"test": "vitest run",
|
|
88
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.strict-app.json"
|
|
89
|
+
},
|
|
90
|
+
"dependencies": {
|
|
91
|
+
"@vitejs/plugin-react": "^6.1.2"
|
|
92
|
+
},
|
|
93
|
+
"peerDependencies": {
|
|
94
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
95
|
+
"@tanstack/react-query": "^5.80.0",
|
|
96
|
+
"@testing-library/react": "^16.0.0",
|
|
97
|
+
"jsdom": "*",
|
|
98
|
+
"react": "^19.0.0",
|
|
99
|
+
"react-dom": "^19.0.0",
|
|
100
|
+
"tailwindcss": "^4.0.0",
|
|
101
|
+
"vite": ">=7",
|
|
102
|
+
"vitest": ">=3",
|
|
103
|
+
"web-vitals": ">=4",
|
|
104
|
+
"zod": "^4.0.0"
|
|
105
|
+
},
|
|
106
|
+
"devDependencies": {
|
|
107
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
108
|
+
"@tanstack/react-query": "^5.105.0",
|
|
109
|
+
"@testing-library/dom": "^10.4.2",
|
|
110
|
+
"@testing-library/react": "^16.3.3",
|
|
111
|
+
"@testing-library/user-event": "^14.6.8",
|
|
112
|
+
"@types/node": "^26.6.5",
|
|
113
|
+
"@types/react": "^19.3.0",
|
|
114
|
+
"@types/react-dom": "^19.3.0",
|
|
115
|
+
"jsdom": "^30.1.3",
|
|
116
|
+
"msw": "^3.0.3",
|
|
117
|
+
"react": "^19.3.1",
|
|
118
|
+
"react-dom": "^19.3.1",
|
|
119
|
+
"tailwindcss": "^4.3.3",
|
|
120
|
+
"typescript": "^7.0.2",
|
|
121
|
+
"vite": "^8.3.5",
|
|
122
|
+
"vitest": "^5.0.5",
|
|
123
|
+
"web-vitals": "^6.2.3",
|
|
124
|
+
"zod": "^4.6.7"
|
|
125
|
+
},
|
|
126
|
+
"peerDependenciesMeta": {
|
|
127
|
+
"@tailwindcss/vite": {
|
|
128
|
+
"optional": true
|
|
129
|
+
},
|
|
130
|
+
"@testing-library/react": {
|
|
131
|
+
"optional": true
|
|
132
|
+
},
|
|
133
|
+
"jsdom": {
|
|
134
|
+
"optional": true
|
|
135
|
+
},
|
|
136
|
+
"tailwindcss": {
|
|
137
|
+
"optional": true
|
|
138
|
+
},
|
|
139
|
+
"vitest": {
|
|
140
|
+
"optional": true
|
|
141
|
+
},
|
|
142
|
+
"web-vitals": {
|
|
143
|
+
"optional": true
|
|
144
|
+
},
|
|
145
|
+
"zod": {
|
|
146
|
+
"optional": true
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|