@meekojs/meeko 0.0.4 → 0.1.0
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/README.md +21 -19
- package/dist/autoimports-ts.d.ts +4 -4
- package/dist/autoimports-ts.js +86 -30
- package/dist/autoimports-ts.js.map +1 -1
- package/dist/autoimports.d.ts +2 -2
- package/dist/autoimports.js +23 -7
- package/dist/autoimports.js.map +1 -1
- package/dist/check.js +39 -10
- package/dist/check.js.map +1 -1
- package/dist/cli.js +37 -4
- package/dist/cli.js.map +1 -1
- package/dist/compiler/events.d.ts +2 -0
- package/dist/compiler/events.js +6 -0
- package/dist/compiler/events.js.map +1 -0
- package/dist/compiler/props.d.ts +12 -0
- package/dist/compiler/props.js +32 -0
- package/dist/compiler/props.js.map +1 -0
- package/dist/compiler.js +55 -12
- package/dist/compiler.js.map +1 -1
- package/dist/component-errors.d.ts +18 -0
- package/dist/component-errors.js +65 -0
- package/dist/component-errors.js.map +1 -0
- package/dist/devtools.js +2 -4
- package/dist/devtools.js.map +1 -1
- package/dist/dom.d.ts +11 -5
- package/dist/dom.js +288 -88
- package/dist/dom.js.map +1 -1
- package/dist/error-observer.d.ts +6 -0
- package/dist/error-observer.js +41 -0
- package/dist/error-observer.js.map +1 -0
- package/dist/framework/check-app.js +160 -5
- package/dist/framework/check-app.js.map +1 -1
- package/dist/framework/check-project.d.ts +4 -0
- package/dist/framework/check-project.js +42 -0
- package/dist/framework/check-project.js.map +1 -0
- package/dist/framework/client.d.ts +3 -0
- package/dist/framework/client.js +136 -33
- package/dist/framework/client.js.map +1 -1
- package/dist/framework/clients.js +12 -4
- package/dist/framework/clients.js.map +1 -1
- package/dist/framework/contracts-generate.js +66 -3
- package/dist/framework/contracts-generate.js.map +1 -1
- package/dist/framework/init.js +21 -200
- package/dist/framework/init.js.map +1 -1
- package/dist/framework/middleware.d.ts +14 -0
- package/dist/framework/middleware.js +16 -0
- package/dist/framework/middleware.js.map +1 -0
- package/dist/framework/page-contracts-check.d.ts +8 -0
- package/dist/framework/page-contracts-check.js +110 -0
- package/dist/framework/page-contracts-check.js.map +1 -0
- package/dist/framework/page-contracts-generate.d.ts +2 -0
- package/dist/framework/page-contracts-generate.js +94 -0
- package/dist/framework/page-contracts-generate.js.map +1 -0
- package/dist/framework/page.d.ts +7 -3
- package/dist/framework/page.js +3 -1
- package/dist/framework/page.js.map +1 -1
- package/dist/framework/prerender.js +11 -5
- package/dist/framework/prerender.js.map +1 -1
- package/dist/framework/project-templates.d.ts +3 -0
- package/dist/framework/project-templates.js +119 -0
- package/dist/framework/project-templates.js.map +1 -0
- package/dist/framework/project.d.ts +9 -0
- package/dist/framework/project.js +272 -0
- package/dist/framework/project.js.map +1 -0
- package/dist/framework/router.d.ts +3 -1
- package/dist/framework/router.js +94 -8
- package/dist/framework/router.js.map +1 -1
- package/dist/framework/server.d.ts +17 -3
- package/dist/framework/server.js +71 -24
- package/dist/framework/server.js.map +1 -1
- package/dist/framework/socket-contracts-generate.js +8 -5
- package/dist/framework/socket-contracts-generate.js.map +1 -1
- package/dist/framework/socket.d.ts +2 -0
- package/dist/framework/socket.js +119 -17
- package/dist/framework/socket.js.map +1 -1
- package/dist/framework/standard-schema-types.d.ts +7 -0
- package/dist/framework/standard-schema-types.js +69 -0
- package/dist/framework/standard-schema-types.js.map +1 -0
- package/dist/framework/state.d.ts +7 -0
- package/dist/framework/state.js +78 -14
- package/dist/framework/state.js.map +1 -1
- package/dist/framework/styles.d.ts +2 -0
- package/dist/framework/styles.js +6 -0
- package/dist/framework/styles.js.map +1 -0
- package/dist/framework/type-declarations.d.ts +5 -0
- package/dist/framework/type-declarations.js +84 -0
- package/dist/framework/type-declarations.js.map +1 -0
- package/dist/framework-server/contracts.d.ts +11 -2
- package/dist/framework-server/contracts.js +62 -7
- package/dist/framework-server/contracts.js.map +1 -1
- package/dist/framework-server/discovery.d.ts +4 -1
- package/dist/framework-server/discovery.js +21 -16
- package/dist/framework-server/discovery.js.map +1 -1
- package/dist/framework-server/local.d.ts +2 -0
- package/dist/framework-server/local.js +5 -3
- package/dist/framework-server/local.js.map +1 -1
- package/dist/framework-server/middleware.d.ts +4 -0
- package/dist/framework-server/middleware.js +56 -0
- package/dist/framework-server/middleware.js.map +1 -0
- package/dist/framework-server/node-upgrade.js +27 -9
- package/dist/framework-server/node-upgrade.js.map +1 -1
- package/dist/framework-server/node-websocket.js +4 -2
- package/dist/framework-server/node-websocket.js.map +1 -1
- package/dist/framework-server/session-stores.d.ts +9 -2
- package/dist/framework-server/session-stores.js +37 -11
- package/dist/framework-server/session-stores.js.map +1 -1
- package/dist/framework-server/sessions.d.ts +6 -0
- package/dist/framework-server/sessions.js +41 -10
- package/dist/framework-server/sessions.js.map +1 -1
- package/dist/framework-server/socket.js +62 -27
- package/dist/framework-server/socket.js.map +1 -1
- package/dist/framework-server.d.ts +2 -0
- package/dist/framework-server.js +1 -0
- package/dist/framework-server.js.map +1 -1
- package/dist/framework.d.ts +3 -1
- package/dist/framework.js +1 -0
- package/dist/framework.js.map +1 -1
- package/dist/hmr.js +74 -24
- package/dist/hmr.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.js +27 -1
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -2
- package/dist/internal.js.map +1 -1
- package/dist/node.d.ts +5 -1
- package/dist/node.js +80 -10
- package/dist/node.js.map +1 -1
- package/dist/reactivity.d.ts +12 -3
- package/dist/reactivity.js +94 -17
- package/dist/reactivity.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/vite.js +29 -9
- package/dist/vite.js.map +1 -1
- package/package.json +2 -2
package/dist/framework/client.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createAppErrorChannel, provideAppErrorChannel } from "../component-errors.js";
|
|
2
|
+
import { finishWithCleanup, signal } from "../reactivity.js";
|
|
2
3
|
import { pageSearchObject, validatePageInputs } from "./page.js";
|
|
3
|
-
import {
|
|
4
|
+
import { runRouteMiddleware } from "./middleware.js";
|
|
5
|
+
import { createNavigator, matchRoute, reportNavigationError } from "./router.js";
|
|
4
6
|
import { hydrateWithStateStore, StateStore, withStateStore } from "./state.js";
|
|
5
7
|
function isRecord(value) {
|
|
6
8
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -16,6 +18,33 @@ function readPayload(value) {
|
|
|
16
18
|
throw new TypeError("Invalid Meeko shared state");
|
|
17
19
|
return { route: parsed.route, props: parsed.props, state: parsed.state, sharedState: parsed.sharedState ?? {} };
|
|
18
20
|
}
|
|
21
|
+
function applyCsrfToken(browser, token) {
|
|
22
|
+
const meta = browser.document.querySelector('meta[name="meeko-csrf-token"]');
|
|
23
|
+
const previous = meta?.getAttribute("content") ?? null;
|
|
24
|
+
let created;
|
|
25
|
+
if (token && meta)
|
|
26
|
+
meta.content = token;
|
|
27
|
+
else if (token) {
|
|
28
|
+
created = browser.document.createElement("meta");
|
|
29
|
+
created.name = "meeko-csrf-token";
|
|
30
|
+
created.content = token;
|
|
31
|
+
browser.document.head.append(created);
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
meta?.remove();
|
|
35
|
+
return () => {
|
|
36
|
+
if (meta) {
|
|
37
|
+
if (!meta.isConnected)
|
|
38
|
+
browser.document.head.append(meta);
|
|
39
|
+
if (previous === null)
|
|
40
|
+
meta.removeAttribute("content");
|
|
41
|
+
else
|
|
42
|
+
meta.setAttribute("content", previous);
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
created?.remove();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
19
48
|
function bind(component, props) {
|
|
20
49
|
return {
|
|
21
50
|
renderWithState: () => component.renderWithState(props),
|
|
@@ -51,38 +80,70 @@ export async function mountClientApp(options) {
|
|
|
51
80
|
if (JSON.stringify(initial.params) !== JSON.stringify(payload.props.params))
|
|
52
81
|
throw new Error("Meeko route parameters do not match SSR");
|
|
53
82
|
const stateStore = new StateStore(payload.sharedState);
|
|
83
|
+
const errorChannel = createAppErrorChannel();
|
|
84
|
+
const observeNavigationError = options.onNavigationError ?? ((error) => {
|
|
85
|
+
const handler = errorChannel.current();
|
|
86
|
+
if (handler)
|
|
87
|
+
return handler(error);
|
|
88
|
+
reportNavigationError(browser, error);
|
|
89
|
+
});
|
|
90
|
+
const hydrateApp = (hydrate) => hydrateWithStateStore(stateStore, () => {
|
|
91
|
+
provideAppErrorChannel(errorChannel);
|
|
92
|
+
return hydrate();
|
|
93
|
+
});
|
|
54
94
|
const first = await createTree(initial.route, payload.props, options.loadPage, 0);
|
|
55
95
|
let layouts = first.layouts;
|
|
56
|
-
let release =
|
|
96
|
+
let release = hydrateApp(() => first.component.hydrateScope({ root: options.root }, {}, payload.state));
|
|
57
97
|
let disposed = false;
|
|
98
|
+
let stableUrl = browser.location.href;
|
|
99
|
+
const recoverCommitFailure = (error) => {
|
|
100
|
+
// A failed outlet update or hydration can leave live scopes attached to a
|
|
101
|
+
// partially replaced tree. Recreate the last committed route with SSR.
|
|
102
|
+
try {
|
|
103
|
+
browser.location.replace(stableUrl);
|
|
104
|
+
}
|
|
105
|
+
catch (recoveryError) {
|
|
106
|
+
throw new AggregateError([error, recoveryError], "Meeko navigation failed and could not restore the previous route");
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
};
|
|
58
110
|
const navigator = createNavigator(options.routes, async (context) => {
|
|
111
|
+
if (options.loadMiddleware) {
|
|
112
|
+
const response = await runRouteMiddleware(await options.loadMiddleware(), {
|
|
113
|
+
url: context.url, match: context.match, signal: context.signal, phase: "client",
|
|
114
|
+
});
|
|
115
|
+
if (context.signal.aborted || disposed)
|
|
116
|
+
return false;
|
|
117
|
+
if (response) {
|
|
118
|
+
const location = response.headers.get("location");
|
|
119
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
120
|
+
const target = new URL(location, context.url);
|
|
121
|
+
if (target.href === context.url.href)
|
|
122
|
+
throw new Error("Route middleware redirected to the same URL");
|
|
123
|
+
browser.location.assign(target.href);
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
reportNavigationError(browser, new Error(`Route middleware blocked navigation: HTTP ${response.status}`), observeNavigationError);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
59
130
|
let props;
|
|
131
|
+
let serverSharedState;
|
|
132
|
+
let serverCsrfToken = null;
|
|
133
|
+
let loaderDraft;
|
|
60
134
|
if (options.serverData && !options.loadRoute) {
|
|
61
135
|
const response = await (options.fetch ?? fetch)(context.url.href, {
|
|
62
136
|
headers: { accept: "application/meeko+json" }, credentials: "same-origin", signal: context.signal,
|
|
63
137
|
});
|
|
64
138
|
if (!response.ok)
|
|
65
139
|
throw new Error(`Route data request failed: HTTP ${response.status}`);
|
|
66
|
-
|
|
67
|
-
if (csrf !== null) {
|
|
68
|
-
const meta = browser.document.querySelector('meta[name="meeko-csrf-token"]');
|
|
69
|
-
if (csrf && meta)
|
|
70
|
-
meta.content = csrf;
|
|
71
|
-
else if (csrf) {
|
|
72
|
-
const next = browser.document.createElement("meta");
|
|
73
|
-
next.name = "meeko-csrf-token";
|
|
74
|
-
next.content = csrf;
|
|
75
|
-
browser.document.head.append(next);
|
|
76
|
-
}
|
|
77
|
-
else
|
|
78
|
-
meta?.remove();
|
|
79
|
-
}
|
|
140
|
+
serverCsrfToken = response.headers.get("x-meeko-csrf-token");
|
|
80
141
|
const payload = readPayload(await response.text());
|
|
81
142
|
if (payload.route !== context.match.route.id)
|
|
82
143
|
throw new Error("Meeko route data mismatch");
|
|
83
144
|
if (context.signal.aborted || disposed)
|
|
84
145
|
return;
|
|
85
|
-
|
|
146
|
+
serverSharedState = payload.sharedState;
|
|
86
147
|
props = payload.props;
|
|
87
148
|
}
|
|
88
149
|
else {
|
|
@@ -90,7 +151,8 @@ export async function mountClientApp(options) {
|
|
|
90
151
|
? await options.loadRoute(context.match.route.sidecar)
|
|
91
152
|
: undefined;
|
|
92
153
|
const validated = await validatePageInputs(sidecar, context.match.params, pageSearchObject(context.url.searchParams));
|
|
93
|
-
|
|
154
|
+
loaderDraft = sidecar?.load ? stateStore.navigationDraft() : undefined;
|
|
155
|
+
const data = sidecar?.load ? await sidecar.load({ ...context, ...validated, state: loaderDraft.state }) : undefined;
|
|
94
156
|
props = {
|
|
95
157
|
params: validated.params,
|
|
96
158
|
search: validated.search,
|
|
@@ -103,17 +165,52 @@ export async function mountClientApp(options) {
|
|
|
103
165
|
const next = await createTree(context.match.route, props, options.loadPage, retained);
|
|
104
166
|
if (context.signal.aborted || disposed)
|
|
105
167
|
return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
168
|
+
const commit = () => {
|
|
169
|
+
if (retained) {
|
|
170
|
+
try {
|
|
171
|
+
withStateStore(stateStore, () => { layouts[retained - 1].outlet.value = next.component; });
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
recoverCommitFailure(error);
|
|
175
|
+
}
|
|
176
|
+
layouts = [...layouts.slice(0, retained), ...next.layouts];
|
|
177
|
+
stableUrl = context.url.href;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const rendered = withStateStore(stateStore, () => next.component.renderWithState({}));
|
|
181
|
+
try {
|
|
182
|
+
release();
|
|
183
|
+
options.root.innerHTML = rendered.html;
|
|
184
|
+
release = hydrateApp(() => next.component.hydrateScope({ root: options.root }, {}, rendered.state));
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
recoverCommitFailure(error);
|
|
188
|
+
}
|
|
189
|
+
layouts = next.layouts;
|
|
190
|
+
stableUrl = context.url.href;
|
|
191
|
+
};
|
|
192
|
+
const commitWithToken = () => {
|
|
193
|
+
const restoreToken = serverCsrfToken === null ? undefined : applyCsrfToken(browser, serverCsrfToken);
|
|
194
|
+
try {
|
|
195
|
+
commit();
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
try {
|
|
199
|
+
restoreToken?.();
|
|
200
|
+
}
|
|
201
|
+
catch (restoreError) {
|
|
202
|
+
throw new AggregateError([error, restoreError], "Meeko navigation and CSRF recovery failed");
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
if (serverSharedState)
|
|
208
|
+
stateStore.seedForNavigation(serverSharedState, commitWithToken);
|
|
209
|
+
else if (loaderDraft)
|
|
210
|
+
loaderDraft.commit(commitWithToken);
|
|
211
|
+
else
|
|
212
|
+
commitWithToken();
|
|
213
|
+
}, browser, observeNavigationError);
|
|
117
214
|
const onClick = (event) => {
|
|
118
215
|
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
119
216
|
return;
|
|
@@ -123,11 +220,17 @@ export async function mountClientApp(options) {
|
|
|
123
220
|
const anchor = target.closest("a[href]");
|
|
124
221
|
if (!anchor || anchor.hasAttribute("download") || (anchor.target && anchor.target !== "_self"))
|
|
125
222
|
return;
|
|
126
|
-
|
|
223
|
+
let url;
|
|
224
|
+
try {
|
|
225
|
+
url = new URL(anchor.href, browser.location.href);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
127
230
|
if (url.origin !== browser.location.origin || !matchRoute(options.routes, url.pathname))
|
|
128
231
|
return;
|
|
129
232
|
event.preventDefault();
|
|
130
|
-
void navigator.navigate(url.href).catch((error) =>
|
|
233
|
+
void navigator.navigate(url.href).catch((error) => reportNavigationError(browser, error, observeNavigationError));
|
|
131
234
|
};
|
|
132
235
|
browser.document.addEventListener("click", onClick);
|
|
133
236
|
return {
|
|
@@ -139,7 +242,7 @@ export async function mountClientApp(options) {
|
|
|
139
242
|
disposed = true;
|
|
140
243
|
navigator.dispose();
|
|
141
244
|
browser.document.removeEventListener("click", onClick);
|
|
142
|
-
release();
|
|
245
|
+
finishWithCleanup(release, () => errorChannel.clear());
|
|
143
246
|
},
|
|
144
247
|
};
|
|
145
248
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/framework/client.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component, DomScope } from \"../dom.js\";\nimport { signal } from \"../reactivity.js\";\nimport type { Signal } from \"../reactivity.js\";\nimport { pageSearchObject, validatePageInputs } from \"./page.js\";\nimport type { PageSchema } from \"./page.js\";\nimport { createNavigator, matchRoute } from \"./router.js\";\nimport type { NavigationContext, Navigator, RouteRecord } from \"./router.js\";\nimport { hydrateWithStateStore, StateStore, withStateStore } from \"./state.js\";\n\n/** The public shape emitted by the .meeko compiler. */\nexport interface ClientComponent {\n renderWithState(props: Record<string, unknown>): { html: string; state: Record<string, unknown> };\n renderToString(props: Record<string, unknown>, state?: Record<string, unknown>): string;\n hydrateScope(scope: DomScope, props: Record<string, unknown>, state?: Record<string, unknown>): () => void;\n}\n\nexport interface ClientRoute {\n /** Client-safe loader. Server-only sidecars must not be passed to loadRoute. */\n params?: PageSchema;\n search?: PageSchema;\n load?: (context: NavigationContext & {\n params: Readonly<Record<string, unknown>>;\n search: Readonly<Record<string, unknown>>;\n state: StateStore;\n }) => Promise<unknown> | unknown;\n}\n\nexport interface ClientAppOptions {\n readonly routes: readonly RouteRecord[];\n readonly root: Element;\n readonly payload: string | unknown;\n readonly loadPage: (file: string) => Promise<ClientComponent>;\n readonly loadRoute?: (file: string) => Promise<ClientRoute>;\n /** Fetches public loader data from the SSR handler during browser navigation. */\n readonly serverData?: boolean;\n readonly fetch?: typeof fetch;\n readonly onNavigationError?: (error: unknown) => void;\n readonly browser?: Window;\n}\n\nexport interface ClientApp {\n navigate: Navigator[\"navigate\"];\n readonly state: StateStore;\n dispose(): void;\n}\n\ninterface HydrationPayload {\n route: string;\n props: Record<string, unknown>;\n state: Record<string, unknown>;\n sharedState: Record<string, unknown>;\n}\n\ninterface LayoutInstance {\n file: string;\n outlet: Signal<Component<Record<string, unknown>>>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readPayload(value: unknown): HydrationPayload {\n const parsed: unknown = typeof value === \"string\" ? JSON.parse(value) : value;\n if (!isRecord(parsed) || typeof parsed.route !== \"string\" || !isRecord(parsed.props) || !isRecord(parsed.state)) {\n throw new TypeError(\"Invalid Meeko hydration payload\");\n }\n if (!isRecord(parsed.props.params) || !isRecord(parsed.props.search)) throw new TypeError(\"Invalid Meeko route props\");\n if (parsed.sharedState !== undefined && !isRecord(parsed.sharedState)) throw new TypeError(\"Invalid Meeko shared state\");\n return { route: parsed.route, props: parsed.props, state: parsed.state, sharedState: parsed.sharedState ?? {} };\n}\n\nfunction bind(component: ClientComponent, props: Record<string, unknown>): Component<Record<string, unknown>> {\n return {\n renderWithState: () => component.renderWithState(props),\n renderToString: () => component.renderToString(props),\n hydrateScope: (scope, _unused, state) => component.hydrateScope(scope, props, state),\n };\n}\n\nasync function createTree(\n route: RouteRecord,\n props: Record<string, unknown>,\n loadPage: ClientAppOptions[\"loadPage\"],\n fromLayout: number,\n): Promise<{ component: Component<Record<string, unknown>>; layouts: LayoutInstance[] }> {\n let component = bind(await loadPage(route.file), props);\n const layouts: LayoutInstance[] = [];\n for (let index = route.layouts.length - 1; index >= fromLayout; index--) {\n const file = route.layouts[index]!;\n const outlet = signal(component);\n const layoutProps = Object.defineProperty({}, \"outlet\", {\n enumerable: true,\n get: () => outlet.value,\n }) as Record<string, unknown>;\n component = bind(await loadPage(file), layoutProps);\n layouts.unshift({ file, outlet });\n }\n return { component, layouts };\n}\n\n/**\n * Hydrates the SSR tree and updates only the first changed outlet on navigation.\n * Layout templates render their outlet with <component is=\"props.outlet\"/>.\n */\nexport async function mountClientApp(options: ClientAppOptions): Promise<ClientApp> {\n const browser = options.browser ?? window;\n const payload = readPayload(options.payload);\n const initial = matchRoute(options.routes, browser.location.pathname);\n if (!initial || initial.route.id !== payload.route) throw new Error(\"Meeko route hydration mismatch\");\n if (JSON.stringify(initial.params) !== JSON.stringify(payload.props.params)) throw new Error(\"Meeko route parameters do not match SSR\");\n const stateStore = new StateStore(payload.sharedState);\n const first = await createTree(initial.route, payload.props, options.loadPage, 0);\n let layouts = first.layouts;\n let release = hydrateWithStateStore(stateStore, () => first.component.hydrateScope({ root: options.root }, {}, payload.state));\n let disposed = false;\n const navigator = createNavigator(options.routes, async (context) => {\n let props: Record<string, unknown>;\n if (options.serverData && !options.loadRoute) {\n const response = await (options.fetch ?? fetch)(context.url.href, {\n headers: { accept: \"application/meeko+json\" }, credentials: \"same-origin\", signal: context.signal,\n });\n if (!response.ok) throw new Error(`Route data request failed: HTTP ${response.status}`);\n const csrf = response.headers.get(\"x-meeko-csrf-token\");\n if (csrf !== null) {\n const meta = browser.document.querySelector<HTMLMetaElement>('meta[name=\"meeko-csrf-token\"]');\n if (csrf && meta) meta.content = csrf;\n else if (csrf) {\n const next = browser.document.createElement(\"meta\");\n next.name = \"meeko-csrf-token\";\n next.content = csrf;\n browser.document.head.append(next);\n } else meta?.remove();\n }\n const payload = readPayload(await response.text());\n if (payload.route !== context.match.route.id) throw new Error(\"Meeko route data mismatch\");\n if (context.signal.aborted || disposed) return;\n stateStore.seed(payload.sharedState);\n props = payload.props;\n } else {\n const sidecar = context.match.route.sidecar && options.loadRoute\n ? await options.loadRoute(context.match.route.sidecar)\n : undefined;\n const validated = await validatePageInputs(sidecar, context.match.params, pageSearchObject(context.url.searchParams));\n const data = sidecar?.load ? await sidecar.load({ ...context, ...validated, state: stateStore }) : undefined;\n props = {\n params: validated.params,\n search: validated.search,\n ...(data === undefined ? {} : { data }),\n };\n }\n if (context.signal.aborted || disposed) return;\n const retained = Math.min(context.retainedLayouts, layouts.length);\n const next = await createTree(context.match.route, props, options.loadPage, retained);\n if (context.signal.aborted || disposed) return;\n if (retained) {\n withStateStore(stateStore, () => { layouts[retained - 1]!.outlet.value = next.component; });\n layouts = [...layouts.slice(0, retained), ...next.layouts];\n return;\n }\n const rendered = withStateStore(stateStore, () => next.component.renderWithState({}));\n release();\n options.root.innerHTML = rendered.html;\n release = hydrateWithStateStore(stateStore, () => next.component.hydrateScope({ root: options.root }, {}, rendered.state));\n layouts = next.layouts;\n }, browser);\n const onClick = (event: MouseEvent) => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n const target = event.target;\n if (!target || !(\"closest\" in target) || typeof target.closest !== \"function\") return;\n const anchor = target.closest(\"a[href]\") as HTMLAnchorElement | null;\n if (!anchor || anchor.hasAttribute(\"download\") || (anchor.target && anchor.target !== \"_self\")) return;\n const url = new URL(anchor.href, browser.location.href);\n if (url.origin !== browser.location.origin || !matchRoute(options.routes, url.pathname)) return;\n event.preventDefault();\n void navigator.navigate(url.href).catch((error: unknown) => { options.onNavigationError?.(error); });\n };\n browser.document.addEventListener(\"click\", onClick);\n return {\n navigate: navigator.navigate,\n state: stateStore,\n dispose() {\n if (disposed) return;\n disposed = true;\n navigator.dispose();\n browser.document.removeEventListener(\"click\", onClick);\n release();\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/framework/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEjF,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAqD/E,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,MAAM,MAAM,GAAY,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9E,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAChH,MAAM,IAAI,SAAS,CAAC,iCAAiC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAC;IACvH,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;IACzH,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;AAClH,CAAC;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,KAAa;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkB,+BAA+B,CAAC,CAAC;IAC9F,MAAM,QAAQ,GAAG,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;IACvD,IAAI,OAAoC,CAAC;IACzC,IAAI,KAAK,IAAI,IAAI;QAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;SACnC,IAAI,KAAK,EAAE,CAAC;QACf,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAClC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;QACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;;QAAM,IAAI,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,GAAG,EAAE;QACV,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,WAAW;gBAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1D,IAAI,QAAQ,KAAK,IAAI;gBAAE,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;;gBAClD,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC9C,CAAC;;YAAM,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3B,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,SAA0B,EAAE,KAA8B;IACtE,OAAO;QACL,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC;QACvD,cAAc,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC;QACrD,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC;KACrF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,KAAkB,EAClB,KAA8B,EAC9B,QAAsC,EACtC,UAAkB;IAElB,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;IACxD,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,KAAK,IAAI,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAE,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QACjC,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE;YACtD,UAAU,EAAE,IAAI;YAChB,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK;SACxB,CAA4B,CAAC;QAC9B,SAAS,GAAG,IAAI,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;QACpD,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAyB;IAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC;IAC1C,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACtG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACxI,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,MAAM,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,IAAI,CAAC,CAAC,KAAc,EAAE,EAAE;QAC9E,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;QACvC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;QACnC,qBAAqB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,CAAC,OAAyB,EAAE,EAAE,CAAC,qBAAqB,CAAC,UAAU,EAAE,GAAG,EAAE;QACvF,sBAAsB,CAAC,YAAY,CAAC,CAAC;QACrC,OAAO,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAClF,IAAI,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC5B,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACxG,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtC,MAAM,oBAAoB,GAAG,CAAC,KAAc,EAAS,EAAE;QACrD,0EAA0E;QAC1E,uEAAuE;QACvE,IAAI,CAAC;YACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,aAAa,EAAE,CAAC;YACvB,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,aAAa,CAAC,EAAE,kEAAkE,CAAC,CAAC;QACvH,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAClE,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,MAAM,OAAO,CAAC,cAAc,EAAE,EAAE;gBACxE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ;aAChF,CAAC,CAAC;YACH,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,QAAQ;gBAAE,OAAO,KAAK,CAAC;YACrD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBAClD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;oBAChE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;oBAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,IAAI;wBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;oBACrG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACvC,CAAC;;oBAAM,qBAAqB,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,6CAA6C,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,sBAAsB,CAAC,CAAC;gBACzI,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,IAAI,KAA8B,CAAC;QACnC,IAAI,iBAAsD,CAAC;QAC3D,IAAI,eAAe,GAAkB,IAAI,CAAC;QAC1C,IAAI,WAAkE,CAAC;QACvE,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE;gBAChE,OAAO,EAAE,EAAE,MAAM,EAAE,wBAAwB,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM;aAClG,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACxF,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;YAC7D,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACnD,IAAI,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3F,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,QAAQ;gBAAE,OAAO;YAC/C,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC;YACxC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,OAAO,CAAC,SAAS;gBAC9D,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;gBACtD,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;YACtH,WAAW,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YACvE,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,WAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACrH,KAAK,GAAG;gBACN,MAAM,EAAE,SAAS,CAAC,MAAM;gBACxB,MAAM,EAAE,SAAS,CAAC,MAAM;gBACxB,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;aACxC,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,QAAQ;YAAE,OAAO;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtF,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,QAAQ;YAAE,OAAO;QAC/C,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC;oBACH,cAAc,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAE,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9F,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,oBAAoB,CAAC,KAAK,CAAC,CAAC;gBAC9B,CAAC;gBACD,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC3D,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAC7B,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC;YACtF,IAAI,CAAC;gBACH,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC;gBACvC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YACtG,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;YACD,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAC/B,CAAC,CAAC;QACF,MAAM,eAAe,GAAG,GAAG,EAAE;YAC3B,MAAM,YAAY,GAAG,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;YACrG,IAAI,CAAC;gBAAC,MAAM,EAAE,CAAC;YAAC,CAAC;YACjB,OAAO,KAAK,EAAE,CAAC;gBACb,IAAI,CAAC;oBAAC,YAAY,EAAE,EAAE,CAAC;gBAAC,CAAC;gBACzB,OAAO,YAAY,EAAE,CAAC;oBAAC,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,2CAA2C,CAAC,CAAC;gBAAC,CAAC;gBACtH,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC;QACF,IAAI,iBAAiB;YAAE,UAAU,CAAC,iBAAiB,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;aACnF,IAAI,WAAW;YAAE,WAAW,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;;YACrD,eAAe,EAAE,CAAC;IACzB,CAAC,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,CAAC,KAAiB,EAAE,EAAE;QACpC,IAAI,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO;QAC7H,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU;YAAE,OAAO;QACtF,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAA6B,CAAC;QACrE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,CAAC;YAAE,OAAO;QACvG,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YAAC,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;QAC1D,MAAM,CAAC;YAAC,OAAO;QAAC,CAAC;QACjB,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO;QAChG,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,qBAAqB,CAAC,OAAO,EAAE,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAC7H,CAAC,CAAC;IACF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO;QACL,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,KAAK,EAAE,UAAU;QACjB,OAAO;YACL,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACvD,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { Component, DomScope } from \"../dom.js\";\nimport { createAppErrorChannel, provideAppErrorChannel } from \"../component-errors.js\";\nimport { finishWithCleanup, signal } from \"../reactivity.js\";\nimport type { Signal } from \"../reactivity.js\";\nimport { pageSearchObject, validatePageInputs } from \"./page.js\";\nimport type { PageSchema } from \"./page.js\";\nimport { runRouteMiddleware } from \"./middleware.js\";\nimport type { RouteMiddleware } from \"./middleware.js\";\nimport { createNavigator, matchRoute, reportNavigationError } from \"./router.js\";\nimport type { NavigationContext, Navigator, RouteRecord } from \"./router.js\";\nimport { hydrateWithStateStore, StateStore, withStateStore } from \"./state.js\";\n\n/** The public shape emitted by the .meeko compiler. */\nexport interface ClientComponent {\n renderWithState(props: Record<string, unknown>): { html: string; state: Record<string, unknown> };\n renderToString(props: Record<string, unknown>, state?: Record<string, unknown>): string;\n hydrateScope(scope: DomScope, props: Record<string, unknown>, state?: Record<string, unknown>): () => void;\n}\n\nexport interface ClientRoute {\n /** Client-safe loader. Server-only sidecars must not be passed to loadRoute. */\n params?: PageSchema;\n search?: PageSchema;\n load?: (context: NavigationContext & {\n params: Readonly<Record<string, unknown>>;\n search: Readonly<Record<string, unknown>>;\n state: StateStore;\n }) => Promise<unknown> | unknown;\n}\n\nexport interface ClientAppOptions {\n readonly routes: readonly RouteRecord[];\n readonly root: Element;\n readonly payload: string | unknown;\n readonly loadPage: (file: string) => Promise<ClientComponent>;\n readonly loadRoute?: (file: string) => Promise<ClientRoute>;\n /** Runs the same route guards as SSR before a browser navigation fetch. */\n readonly loadMiddleware?: () => Promise<readonly RouteMiddleware[]>;\n /** Fetches public loader data from the SSR handler during browser navigation. */\n readonly serverData?: boolean;\n readonly fetch?: typeof fetch;\n readonly onNavigationError?: (error: unknown) => void;\n readonly browser?: Window;\n}\n\nexport interface ClientApp {\n navigate: Navigator[\"navigate\"];\n readonly state: StateStore;\n dispose(): void;\n}\n\ninterface HydrationPayload {\n route: string;\n props: Record<string, unknown>;\n state: Record<string, unknown>;\n sharedState: Record<string, unknown>;\n}\n\ninterface LayoutInstance {\n file: string;\n outlet: Signal<Component<Record<string, unknown>>>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readPayload(value: unknown): HydrationPayload {\n const parsed: unknown = typeof value === \"string\" ? JSON.parse(value) : value;\n if (!isRecord(parsed) || typeof parsed.route !== \"string\" || !isRecord(parsed.props) || !isRecord(parsed.state)) {\n throw new TypeError(\"Invalid Meeko hydration payload\");\n }\n if (!isRecord(parsed.props.params) || !isRecord(parsed.props.search)) throw new TypeError(\"Invalid Meeko route props\");\n if (parsed.sharedState !== undefined && !isRecord(parsed.sharedState)) throw new TypeError(\"Invalid Meeko shared state\");\n return { route: parsed.route, props: parsed.props, state: parsed.state, sharedState: parsed.sharedState ?? {} };\n}\n\nfunction applyCsrfToken(browser: Window, token: string): () => void {\n const meta = browser.document.querySelector<HTMLMetaElement>('meta[name=\"meeko-csrf-token\"]');\n const previous = meta?.getAttribute(\"content\") ?? null;\n let created: HTMLMetaElement | undefined;\n if (token && meta) meta.content = token;\n else if (token) {\n created = browser.document.createElement(\"meta\");\n created.name = \"meeko-csrf-token\";\n created.content = token;\n browser.document.head.append(created);\n } else meta?.remove();\n return () => {\n if (meta) {\n if (!meta.isConnected) browser.document.head.append(meta);\n if (previous === null) meta.removeAttribute(\"content\");\n else meta.setAttribute(\"content\", previous);\n } else created?.remove();\n };\n}\n\nfunction bind(component: ClientComponent, props: Record<string, unknown>): Component<Record<string, unknown>> {\n return {\n renderWithState: () => component.renderWithState(props),\n renderToString: () => component.renderToString(props),\n hydrateScope: (scope, _unused, state) => component.hydrateScope(scope, props, state),\n };\n}\n\nasync function createTree(\n route: RouteRecord,\n props: Record<string, unknown>,\n loadPage: ClientAppOptions[\"loadPage\"],\n fromLayout: number,\n): Promise<{ component: Component<Record<string, unknown>>; layouts: LayoutInstance[] }> {\n let component = bind(await loadPage(route.file), props);\n const layouts: LayoutInstance[] = [];\n for (let index = route.layouts.length - 1; index >= fromLayout; index--) {\n const file = route.layouts[index]!;\n const outlet = signal(component);\n const layoutProps = Object.defineProperty({}, \"outlet\", {\n enumerable: true,\n get: () => outlet.value,\n }) as Record<string, unknown>;\n component = bind(await loadPage(file), layoutProps);\n layouts.unshift({ file, outlet });\n }\n return { component, layouts };\n}\n\n/**\n * Hydrates the SSR tree and updates only the first changed outlet on navigation.\n * Layout templates render their outlet with <component is=\"props.outlet\"/>.\n */\nexport async function mountClientApp(options: ClientAppOptions): Promise<ClientApp> {\n const browser = options.browser ?? window;\n const payload = readPayload(options.payload);\n const initial = matchRoute(options.routes, browser.location.pathname);\n if (!initial || initial.route.id !== payload.route) throw new Error(\"Meeko route hydration mismatch\");\n if (JSON.stringify(initial.params) !== JSON.stringify(payload.props.params)) throw new Error(\"Meeko route parameters do not match SSR\");\n const stateStore = new StateStore(payload.sharedState);\n const errorChannel = createAppErrorChannel();\n const observeNavigationError = options.onNavigationError ?? ((error: unknown) => {\n const handler = errorChannel.current();\n if (handler) return handler(error);\n reportNavigationError(browser, error);\n });\n const hydrateApp = (hydrate: () => () => void) => hydrateWithStateStore(stateStore, () => {\n provideAppErrorChannel(errorChannel);\n return hydrate();\n });\n const first = await createTree(initial.route, payload.props, options.loadPage, 0);\n let layouts = first.layouts;\n let release = hydrateApp(() => first.component.hydrateScope({ root: options.root }, {}, payload.state));\n let disposed = false;\n let stableUrl = browser.location.href;\n const recoverCommitFailure = (error: unknown): never => {\n // A failed outlet update or hydration can leave live scopes attached to a\n // partially replaced tree. Recreate the last committed route with SSR.\n try {\n browser.location.replace(stableUrl);\n } catch (recoveryError) {\n throw new AggregateError([error, recoveryError], \"Meeko navigation failed and could not restore the previous route\");\n }\n throw error;\n };\n const navigator = createNavigator(options.routes, async (context) => {\n if (options.loadMiddleware) {\n const response = await runRouteMiddleware(await options.loadMiddleware(), {\n url: context.url, match: context.match, signal: context.signal, phase: \"client\",\n });\n if (context.signal.aborted || disposed) return false;\n if (response) {\n const location = response.headers.get(\"location\");\n if (response.status >= 300 && response.status < 400 && location) {\n const target = new URL(location, context.url);\n if (target.href === context.url.href) throw new Error(\"Route middleware redirected to the same URL\");\n browser.location.assign(target.href);\n } else reportNavigationError(browser, new Error(`Route middleware blocked navigation: HTTP ${response.status}`), observeNavigationError);\n return false;\n }\n }\n let props: Record<string, unknown>;\n let serverSharedState: Record<string, unknown> | undefined;\n let serverCsrfToken: string | null = null;\n let loaderDraft: ReturnType<StateStore[\"navigationDraft\"]> | undefined;\n if (options.serverData && !options.loadRoute) {\n const response = await (options.fetch ?? fetch)(context.url.href, {\n headers: { accept: \"application/meeko+json\" }, credentials: \"same-origin\", signal: context.signal,\n });\n if (!response.ok) throw new Error(`Route data request failed: HTTP ${response.status}`);\n serverCsrfToken = response.headers.get(\"x-meeko-csrf-token\");\n const payload = readPayload(await response.text());\n if (payload.route !== context.match.route.id) throw new Error(\"Meeko route data mismatch\");\n if (context.signal.aborted || disposed) return;\n serverSharedState = payload.sharedState;\n props = payload.props;\n } else {\n const sidecar = context.match.route.sidecar && options.loadRoute\n ? await options.loadRoute(context.match.route.sidecar)\n : undefined;\n const validated = await validatePageInputs(sidecar, context.match.params, pageSearchObject(context.url.searchParams));\n loaderDraft = sidecar?.load ? stateStore.navigationDraft() : undefined;\n const data = sidecar?.load ? await sidecar.load({ ...context, ...validated, state: loaderDraft!.state }) : undefined;\n props = {\n params: validated.params,\n search: validated.search,\n ...(data === undefined ? {} : { data }),\n };\n }\n if (context.signal.aborted || disposed) return;\n const retained = Math.min(context.retainedLayouts, layouts.length);\n const next = await createTree(context.match.route, props, options.loadPage, retained);\n if (context.signal.aborted || disposed) return;\n const commit = () => {\n if (retained) {\n try {\n withStateStore(stateStore, () => { layouts[retained - 1]!.outlet.value = next.component; });\n } catch (error) {\n recoverCommitFailure(error);\n }\n layouts = [...layouts.slice(0, retained), ...next.layouts];\n stableUrl = context.url.href;\n return;\n }\n const rendered = withStateStore(stateStore, () => next.component.renderWithState({}));\n try {\n release();\n options.root.innerHTML = rendered.html;\n release = hydrateApp(() => next.component.hydrateScope({ root: options.root }, {}, rendered.state));\n } catch (error) {\n recoverCommitFailure(error);\n }\n layouts = next.layouts;\n stableUrl = context.url.href;\n };\n const commitWithToken = () => {\n const restoreToken = serverCsrfToken === null ? undefined : applyCsrfToken(browser, serverCsrfToken);\n try { commit(); }\n catch (error) {\n try { restoreToken?.(); }\n catch (restoreError) { throw new AggregateError([error, restoreError], \"Meeko navigation and CSRF recovery failed\"); }\n throw error;\n }\n };\n if (serverSharedState) stateStore.seedForNavigation(serverSharedState, commitWithToken);\n else if (loaderDraft) loaderDraft.commit(commitWithToken);\n else commitWithToken();\n }, browser, observeNavigationError);\n const onClick = (event: MouseEvent) => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n const target = event.target;\n if (!target || !(\"closest\" in target) || typeof target.closest !== \"function\") return;\n const anchor = target.closest(\"a[href]\") as HTMLAnchorElement | null;\n if (!anchor || anchor.hasAttribute(\"download\") || (anchor.target && anchor.target !== \"_self\")) return;\n let url: URL;\n try { url = new URL(anchor.href, browser.location.href); }\n catch { return; }\n if (url.origin !== browser.location.origin || !matchRoute(options.routes, url.pathname)) return;\n event.preventDefault();\n void navigator.navigate(url.href).catch((error: unknown) => reportNavigationError(browser, error, observeNavigationError));\n };\n browser.document.addEventListener(\"click\", onClick);\n return {\n navigate: navigator.navigate,\n state: stateStore,\n dispose() {\n if (disposed) return;\n disposed = true;\n navigator.dispose();\n browser.document.removeEventListener(\"click\", onClick);\n finishWithCleanup(release, () => errorChannel.clear());\n },\n };\n}\n"]}
|
|
@@ -21,15 +21,23 @@ export class ClientError extends Error {
|
|
|
21
21
|
}
|
|
22
22
|
/** Reads the current document token at call time, including after client navigation. */
|
|
23
23
|
export function createBrowserClientOptions() {
|
|
24
|
+
const currentCsrfToken = () => typeof document === "undefined"
|
|
25
|
+
? undefined
|
|
26
|
+
: document.querySelector('meta[name="meeko-csrf-token"]')?.getAttribute("content") ?? undefined;
|
|
24
27
|
return {
|
|
25
|
-
csrfToken:
|
|
26
|
-
? undefined
|
|
27
|
-
: document.querySelector('meta[name="meeko-csrf-token"]')?.getAttribute("content") ?? undefined,
|
|
28
|
+
csrfToken: currentCsrfToken,
|
|
28
29
|
fetch: async (input, init) => {
|
|
30
|
+
const sourceDocument = typeof document === "undefined" ? undefined : document;
|
|
31
|
+
const csrfTokenAtStart = currentCsrfToken();
|
|
29
32
|
const response = await fetch(input, init);
|
|
30
33
|
const token = response.headers.get("x-meeko-csrf-token");
|
|
31
|
-
|
|
34
|
+
// Navigation or a newer response may have rotated or cleared the token.
|
|
35
|
+
if (token !== null && sourceDocument && document === sourceDocument && currentCsrfToken() === csrfTokenAtStart) {
|
|
32
36
|
let meta = document.querySelector('meta[name="meeko-csrf-token"]');
|
|
37
|
+
if (!token) {
|
|
38
|
+
meta?.remove();
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
33
41
|
if (!meta) {
|
|
34
42
|
meta = document.createElement("meta");
|
|
35
43
|
meta.setAttribute("name", "meeko-csrf-token");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clients.js","sourceRoot":"","sources":["../../src/framework/clients.ts"],"names":[],"mappings":"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","sourcesContent":["/** Public descriptors intentionally omit handler files and server schemas. */\nexport interface PublicApiEndpoint {\n readonly clientKey: string;\n readonly pathname: string;\n readonly method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n readonly contentType?: \"json\" | \"response\";\n}\n\nexport interface PublicRpcEndpoint {\n readonly id: string;\n readonly pathname: string;\n}\n\nexport interface PublicServerManifest {\n readonly api: readonly PublicApiEndpoint[];\n readonly rpc: readonly PublicRpcEndpoint[];\n}\n\n/** Strips private handler paths before serializing a discovered server manifest. */\nexport function createPublicServerManifest(manifest: PublicServerManifest): PublicServerManifest {\n return {\n api: manifest.api.map(({ clientKey, pathname, method, contentType }) => ({\n clientKey, pathname, method, ...(contentType ? { contentType } : {}),\n })),\n rpc: manifest.rpc.map(({ id, pathname }) => ({ id, pathname })),\n };\n}\n\n/** Type-only operation declarations can be inferred from public shared schemas. */\nexport interface ApiOperation<Params = undefined, Query = undefined, Body = undefined, Output = unknown> {\n readonly params: Params;\n readonly query: Query;\n readonly body: Body;\n readonly output: Output;\n}\n\nexport interface RpcOperation<Input = unknown, Output = unknown> {\n readonly input: Input;\n readonly output: Output;\n}\n\ntype AnyApiOperation = ApiOperation<unknown, unknown, unknown, unknown>;\n\ntype SearchValue = string | number | boolean | null | undefined;\ntype QueryValue = SearchValue | readonly SearchValue[];\ntype RequestOptions = { readonly signal?: AbortSignal };\ntype ParamOption<T> = T extends undefined ? { readonly params?: never } : { readonly params: T };\ntype QueryOption<T> = T extends undefined ? { readonly query?: never } : { readonly query: T };\ntype BodyOption<T> = T extends undefined ? { readonly body?: never } : { readonly body: T };\ntype ApiArguments<O extends AnyApiOperation> = O[\"params\"] extends undefined\n ? O[\"query\"] extends undefined\n ? O[\"body\"] extends undefined ? [options?: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions] : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions]\n : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions]\n : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions];\ntype Nest<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? { [K in Head]: Nest<Tail, Value> } : { [K in Path]: Value };\ntype Intersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Result) => void ? Result : never;\ntype ApiMethods<Operations> = { [Method in keyof Operations]: Operations[Method] extends AnyApiOperation\n ? (...args: ApiArguments<Operations[Method]>) => Promise<Operations[Method][\"output\"]>\n : never };\nexport type ApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>> = Intersection<{\n [Key in keyof Contract & string]: Nest<Key, ApiMethods<Contract[Key]>>\n}[keyof Contract & string]>;\nexport type RpcClient<Contract extends Record<string, RpcOperation>> = Intersection<{\n [Key in keyof Contract & string]: Nest<Key, (input: Contract[Key][\"input\"], options?: RequestOptions) => Promise<Contract[Key][\"output\"]>>\n}[keyof Contract & string]>;\n\nexport class ClientError extends Error {\n constructor(public readonly code: string, public readonly status: number, public readonly requestId?: string) {\n super(code);\n this.name = \"ClientError\";\n }\n}\n\nexport interface ClientOptions {\n readonly baseUrl?: string | (() => string);\n readonly fetch?: typeof fetch;\n readonly csrfToken?: string | (() => string | undefined);\n readonly headers?: HeadersInit | (() => HeadersInit);\n}\n\n/** Reads the current document token at call time, including after client navigation. */\nexport function createBrowserClientOptions(): ClientOptions {\n return {\n csrfToken: () => typeof document === \"undefined\"\n ? undefined\n : document.querySelector('meta[name=\"meeko-csrf-token\"]')?.getAttribute(\"content\") ?? undefined,\n fetch: async (input, init) => {\n const response = await fetch(input, init);\n const token = response.headers.get(\"x-meeko-csrf-token\");\n if (token && typeof document !== \"undefined\") {\n let meta = document.querySelector('meta[name=\"meeko-csrf-token\"]');\n if (!meta) {\n meta = document.createElement(\"meta\");\n meta.setAttribute(\"name\", \"meeko-csrf-token\");\n document.head.append(meta);\n }\n meta.setAttribute(\"content\", token);\n }\n return response;\n },\n };\n}\n\nfunction pathWithParams(pathname: string, params: Readonly<Record<string, string>> = {}): string {\n const used = new Set<string>();\n const path = pathname.replace(/\\[(\\.\\.\\.)?([A-Za-z_$][\\w$]*)\\]/g, (_match, rest: string | undefined, name: string) => {\n const value = params[name];\n if (!value) throw new TypeError(`Missing route parameter: ${name}`);\n used.add(name);\n const parts = rest ? value.split(\"/\") : [value];\n if (parts.some((part) => !part || part === \".\" || part === \"..\" || part.includes(\"/\") || part.includes(\"\\\\\"))) throw new TypeError(`Invalid route parameter: ${name}`);\n return parts.map(encodeURIComponent).join(\"/\");\n });\n for (const key of Object.keys(params)) if (!used.has(key)) throw new TypeError(`Unknown route parameter: ${key}`);\n return path;\n}\n\nfunction queryString(query: Readonly<Record<string, QueryValue>> | undefined): string {\n if (!query) return \"\";\n const search = new URLSearchParams();\n for (const [key, raw] of Object.entries(query)) {\n for (const value of Array.isArray(raw) ? raw : [raw]) {\n if (value !== null && value !== undefined) search.append(key, String(value));\n }\n }\n const result = search.toString();\n return result ? `?${result}` : \"\";\n}\n\nasync function responseValue(response: Response, contentType: \"json\" | \"response\" = \"json\"): Promise<unknown> {\n if (!response.ok) {\n let payload: unknown;\n try { payload = await response.json(); } catch { /* Bad error bodies still produce a normalized error. */ }\n const record = payload && typeof payload === \"object\" ? payload as Record<string, unknown> : {};\n const code = typeof record.code === \"string\" ? record.code : \"HTTP_ERROR\";\n const requestId = typeof record.requestId === \"string\" ? record.requestId : response.headers.get(\"x-request-id\") ?? undefined;\n throw new ClientError(code, response.status, requestId);\n }\n if (contentType === \"response\") return response;\n if (response.status === 204) return undefined;\n try { return await response.json(); }\n catch { throw new ClientError(\"INVALID_RESPONSE\", response.status, response.headers.get(\"x-request-id\") ?? undefined); }\n}\n\nfunction endpointUrl(pathname: string, options: ClientOptions): string {\n const baseUrl = typeof options.baseUrl === \"function\" ? options.baseUrl() : options.baseUrl;\n return baseUrl ? new URL(pathname, baseUrl).toString() : pathname;\n}\n\nfunction headersFor(options: ClientOptions, method: string, json: boolean): Headers {\n const headers = new Headers(typeof options.headers === \"function\" ? options.headers() : options.headers);\n if (json) headers.set(\"content-type\", \"application/json\");\n if (method !== \"GET\" && method !== \"HEAD\") {\n const csrf = typeof options.csrfToken === \"function\" ? options.csrfToken() : options.csrfToken;\n if (csrf) headers.set(\"x-meeko-csrf\", csrf);\n }\n return headers;\n}\n\nfunction insert(root: Record<string, unknown>, key: string, value: unknown): void {\n const parts = key.split(\".\");\n if (parts.some((part) => !/^[A-Za-z_$][\\w$]*$/.test(part))) throw new TypeError(`Invalid client key: ${key}`);\n let node = root;\n for (const part of parts.slice(0, -1)) {\n const existing = node[part];\n if (existing === undefined) node[part] = Object.create(null) as Record<string, unknown>;\n else if (!existing || typeof existing !== \"object\") throw new TypeError(`Client key collision: ${key}`);\n node = node[part] as Record<string, unknown>;\n }\n const last = parts.at(-1)!;\n if (node[last] !== undefined) throw new TypeError(`Client key collision: ${key}`);\n node[last] = value;\n}\n\nfunction ensureObject(root: Record<string, unknown>, key: string): Record<string, unknown> {\n const parts = key.split(\".\");\n if (parts.some((part) => !/^[A-Za-z_$][\\w$]*$/.test(part))) throw new TypeError(`Invalid client key: ${key}`);\n let node = root;\n for (const part of parts) {\n const existing = node[part];\n if (existing === undefined) node[part] = Object.create(null) as Record<string, unknown>;\n else if (!existing || typeof existing !== \"object\") throw new TypeError(`Client key collision: ${key}`);\n node = node[part] as Record<string, unknown>;\n }\n return node;\n}\n\n/** Creates browser API methods from public endpoint paths, without server imports. */\nexport function createApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>>(manifest: Pick<PublicServerManifest, \"api\">, options: ClientOptions = {}): ApiClient<Contract> {\n const root: Record<string, unknown> = Object.create(null);\n for (const endpoint of manifest.api) {\n const methods = ensureObject(root, endpoint.clientKey);\n const method = endpoint.method.toLowerCase();\n if (methods[method]) throw new TypeError(`Duplicate API method: ${endpoint.clientKey}.${method}`);\n methods[method] = async (input: { params?: Record<string, string>; query?: Record<string, QueryValue>; body?: unknown; signal?: AbortSignal } = {}) => {\n const path = pathWithParams(endpoint.pathname, input.params) + queryString(input.query);\n const url = endpointUrl(path, options);\n const fetcher = options.fetch ?? fetch;\n const response = await fetcher(url, {\n method: endpoint.method, headers: headersFor(options, endpoint.method, input.body !== undefined),\n credentials: \"same-origin\", ...(input.body === undefined ? {} : { body: JSON.stringify(input.body) }),\n ...(input.signal ? { signal: input.signal } : {}),\n });\n return responseValue(response, endpoint.contentType);\n };\n }\n return root as ApiClient<Contract>;\n}\n\n/** Creates browser RPC procedures from public endpoint paths. */\nexport function createRpcClient<Contract extends Record<string, RpcOperation>>(manifest: Pick<PublicServerManifest, \"rpc\">, options: ClientOptions = {}): RpcClient<Contract> {\n const root: Record<string, unknown> = Object.create(null);\n for (const endpoint of manifest.rpc) {\n insert(root, endpoint.id, async (input: unknown, request: RequestOptions = {}) => {\n const fetcher = options.fetch ?? fetch;\n const response = await fetcher(endpointUrl(endpoint.pathname, options), {\n method: \"POST\", headers: headersFor(options, \"POST\", true), credentials: \"same-origin\",\n body: JSON.stringify(input), ...(request.signal ? { signal: request.signal } : {}),\n });\n return responseValue(response);\n });\n }\n return root as RpcClient<Contract>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clients.js","sourceRoot":"","sources":["../../src/framework/clients.ts"],"names":[],"mappings":"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","sourcesContent":["/** Public descriptors intentionally omit handler files and server schemas. */\nexport interface PublicApiEndpoint {\n readonly clientKey: string;\n readonly pathname: string;\n readonly method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n readonly contentType?: \"json\" | \"response\";\n}\n\nexport interface PublicRpcEndpoint {\n readonly id: string;\n readonly pathname: string;\n}\n\nexport interface PublicServerManifest {\n readonly api: readonly PublicApiEndpoint[];\n readonly rpc: readonly PublicRpcEndpoint[];\n}\n\n/** Strips private handler paths before serializing a discovered server manifest. */\nexport function createPublicServerManifest(manifest: PublicServerManifest): PublicServerManifest {\n return {\n api: manifest.api.map(({ clientKey, pathname, method, contentType }) => ({\n clientKey, pathname, method, ...(contentType ? { contentType } : {}),\n })),\n rpc: manifest.rpc.map(({ id, pathname }) => ({ id, pathname })),\n };\n}\n\n/** Type-only operation declarations can be inferred from public shared schemas. */\nexport interface ApiOperation<Params = undefined, Query = undefined, Body = undefined, Output = unknown> {\n readonly params: Params;\n readonly query: Query;\n readonly body: Body;\n readonly output: Output;\n}\n\nexport interface RpcOperation<Input = unknown, Output = unknown> {\n readonly input: Input;\n readonly output: Output;\n}\n\ntype AnyApiOperation = ApiOperation<unknown, unknown, unknown, unknown>;\n\ntype SearchValue = string | number | boolean | null | undefined;\ntype QueryValue = SearchValue | readonly SearchValue[];\ntype RequestOptions = { readonly signal?: AbortSignal };\ntype ParamOption<T> = T extends undefined ? { readonly params?: never } : { readonly params: T };\ntype QueryOption<T> = T extends undefined ? { readonly query?: never } : { readonly query: T };\ntype BodyOption<T> = T extends undefined ? { readonly body?: never } : { readonly body: T };\ntype ApiArguments<O extends AnyApiOperation> = O[\"params\"] extends undefined\n ? O[\"query\"] extends undefined\n ? O[\"body\"] extends undefined ? [options?: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions] : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions]\n : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions]\n : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions];\ntype Nest<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? { [K in Head]: Nest<Tail, Value> } : { [K in Path]: Value };\ntype Intersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Result) => void ? Result : never;\ntype ApiMethods<Operations> = { [Method in keyof Operations]: Operations[Method] extends AnyApiOperation\n ? (...args: ApiArguments<Operations[Method]>) => Promise<Operations[Method][\"output\"]>\n : never };\nexport type ApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>> = Intersection<{\n [Key in keyof Contract & string]: Nest<Key, ApiMethods<Contract[Key]>>\n}[keyof Contract & string]>;\nexport type RpcClient<Contract extends Record<string, RpcOperation>> = Intersection<{\n [Key in keyof Contract & string]: Nest<Key, (input: Contract[Key][\"input\"], options?: RequestOptions) => Promise<Contract[Key][\"output\"]>>\n}[keyof Contract & string]>;\n\nexport class ClientError extends Error {\n constructor(public readonly code: string, public readonly status: number, public readonly requestId?: string) {\n super(code);\n this.name = \"ClientError\";\n }\n}\n\nexport interface ClientOptions {\n readonly baseUrl?: string | (() => string);\n readonly fetch?: typeof fetch;\n readonly csrfToken?: string | (() => string | undefined);\n readonly headers?: HeadersInit | (() => HeadersInit);\n}\n\n/** Reads the current document token at call time, including after client navigation. */\nexport function createBrowserClientOptions(): ClientOptions {\n const currentCsrfToken = (): string | undefined => typeof document === \"undefined\"\n ? undefined\n : document.querySelector('meta[name=\"meeko-csrf-token\"]')?.getAttribute(\"content\") ?? undefined;\n return {\n csrfToken: currentCsrfToken,\n fetch: async (input, init) => {\n const sourceDocument = typeof document === \"undefined\" ? undefined : document;\n const csrfTokenAtStart = currentCsrfToken();\n const response = await fetch(input, init);\n const token = response.headers.get(\"x-meeko-csrf-token\");\n // Navigation or a newer response may have rotated or cleared the token.\n if (token !== null && sourceDocument && document === sourceDocument && currentCsrfToken() === csrfTokenAtStart) {\n let meta = document.querySelector('meta[name=\"meeko-csrf-token\"]');\n if (!token) {\n meta?.remove();\n return response;\n }\n if (!meta) {\n meta = document.createElement(\"meta\");\n meta.setAttribute(\"name\", \"meeko-csrf-token\");\n document.head.append(meta);\n }\n meta.setAttribute(\"content\", token);\n }\n return response;\n },\n };\n}\n\nfunction pathWithParams(pathname: string, params: Readonly<Record<string, string>> = {}): string {\n const used = new Set<string>();\n const path = pathname.replace(/\\[(\\.\\.\\.)?([A-Za-z_$][\\w$]*)\\]/g, (_match, rest: string | undefined, name: string) => {\n const value = params[name];\n if (!value) throw new TypeError(`Missing route parameter: ${name}`);\n used.add(name);\n const parts = rest ? value.split(\"/\") : [value];\n if (parts.some((part) => !part || part === \".\" || part === \"..\" || part.includes(\"/\") || part.includes(\"\\\\\"))) throw new TypeError(`Invalid route parameter: ${name}`);\n return parts.map(encodeURIComponent).join(\"/\");\n });\n for (const key of Object.keys(params)) if (!used.has(key)) throw new TypeError(`Unknown route parameter: ${key}`);\n return path;\n}\n\nfunction queryString(query: Readonly<Record<string, QueryValue>> | undefined): string {\n if (!query) return \"\";\n const search = new URLSearchParams();\n for (const [key, raw] of Object.entries(query)) {\n for (const value of Array.isArray(raw) ? raw : [raw]) {\n if (value !== null && value !== undefined) search.append(key, String(value));\n }\n }\n const result = search.toString();\n return result ? `?${result}` : \"\";\n}\n\nasync function responseValue(response: Response, contentType: \"json\" | \"response\" = \"json\"): Promise<unknown> {\n if (!response.ok) {\n let payload: unknown;\n try { payload = await response.json(); } catch { /* Bad error bodies still produce a normalized error. */ }\n const record = payload && typeof payload === \"object\" ? payload as Record<string, unknown> : {};\n const code = typeof record.code === \"string\" ? record.code : \"HTTP_ERROR\";\n const requestId = typeof record.requestId === \"string\" ? record.requestId : response.headers.get(\"x-request-id\") ?? undefined;\n throw new ClientError(code, response.status, requestId);\n }\n if (contentType === \"response\") return response;\n if (response.status === 204) return undefined;\n try { return await response.json(); }\n catch { throw new ClientError(\"INVALID_RESPONSE\", response.status, response.headers.get(\"x-request-id\") ?? undefined); }\n}\n\nfunction endpointUrl(pathname: string, options: ClientOptions): string {\n const baseUrl = typeof options.baseUrl === \"function\" ? options.baseUrl() : options.baseUrl;\n return baseUrl ? new URL(pathname, baseUrl).toString() : pathname;\n}\n\nfunction headersFor(options: ClientOptions, method: string, json: boolean): Headers {\n const headers = new Headers(typeof options.headers === \"function\" ? options.headers() : options.headers);\n if (json) headers.set(\"content-type\", \"application/json\");\n if (method !== \"GET\" && method !== \"HEAD\") {\n const csrf = typeof options.csrfToken === \"function\" ? options.csrfToken() : options.csrfToken;\n if (csrf) headers.set(\"x-meeko-csrf\", csrf);\n }\n return headers;\n}\n\nfunction insert(root: Record<string, unknown>, key: string, value: unknown): void {\n const parts = key.split(\".\");\n if (parts.some((part) => !/^[A-Za-z_$][\\w$]*$/.test(part))) throw new TypeError(`Invalid client key: ${key}`);\n let node = root;\n for (const part of parts.slice(0, -1)) {\n const existing = node[part];\n if (existing === undefined) node[part] = Object.create(null) as Record<string, unknown>;\n else if (!existing || typeof existing !== \"object\") throw new TypeError(`Client key collision: ${key}`);\n node = node[part] as Record<string, unknown>;\n }\n const last = parts.at(-1)!;\n if (node[last] !== undefined) throw new TypeError(`Client key collision: ${key}`);\n node[last] = value;\n}\n\nfunction ensureObject(root: Record<string, unknown>, key: string): Record<string, unknown> {\n const parts = key.split(\".\");\n if (parts.some((part) => !/^[A-Za-z_$][\\w$]*$/.test(part))) throw new TypeError(`Invalid client key: ${key}`);\n let node = root;\n for (const part of parts) {\n const existing = node[part];\n if (existing === undefined) node[part] = Object.create(null) as Record<string, unknown>;\n else if (!existing || typeof existing !== \"object\") throw new TypeError(`Client key collision: ${key}`);\n node = node[part] as Record<string, unknown>;\n }\n return node;\n}\n\n/** Creates browser API methods from public endpoint paths, without server imports. */\nexport function createApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>>(manifest: Pick<PublicServerManifest, \"api\">, options: ClientOptions = {}): ApiClient<Contract> {\n const root: Record<string, unknown> = Object.create(null);\n for (const endpoint of manifest.api) {\n const methods = ensureObject(root, endpoint.clientKey);\n const method = endpoint.method.toLowerCase();\n if (methods[method]) throw new TypeError(`Duplicate API method: ${endpoint.clientKey}.${method}`);\n methods[method] = async (input: { params?: Record<string, string>; query?: Record<string, QueryValue>; body?: unknown; signal?: AbortSignal } = {}) => {\n const path = pathWithParams(endpoint.pathname, input.params) + queryString(input.query);\n const url = endpointUrl(path, options);\n const fetcher = options.fetch ?? fetch;\n const response = await fetcher(url, {\n method: endpoint.method, headers: headersFor(options, endpoint.method, input.body !== undefined),\n credentials: \"same-origin\", ...(input.body === undefined ? {} : { body: JSON.stringify(input.body) }),\n ...(input.signal ? { signal: input.signal } : {}),\n });\n return responseValue(response, endpoint.contentType);\n };\n }\n return root as ApiClient<Contract>;\n}\n\n/** Creates browser RPC procedures from public endpoint paths. */\nexport function createRpcClient<Contract extends Record<string, RpcOperation>>(manifest: Pick<PublicServerManifest, \"rpc\">, options: ClientOptions = {}): RpcClient<Contract> {\n const root: Record<string, unknown> = Object.create(null);\n for (const endpoint of manifest.rpc) {\n insert(root, endpoint.id, async (input: unknown, request: RequestOptions = {}) => {\n const fetcher = options.fetch ?? fetch;\n const response = await fetcher(endpointUrl(endpoint.pathname, options), {\n method: \"POST\", headers: headersFor(options, \"POST\", true), credentials: \"same-origin\",\n body: JSON.stringify(input), ...(request.signal ? { signal: request.signal } : {}),\n });\n return responseValue(response);\n });\n }\n return root as RpcClient<Contract>;\n}\n"]}
|
|
@@ -3,6 +3,7 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
|
3
3
|
import ts from "@typescript/typescript6";
|
|
4
4
|
import { discoverServerManifest } from "../framework-server/discovery.js";
|
|
5
5
|
import { generateSocketContracts } from "./socket-contracts-generate.js";
|
|
6
|
+
import { standardSchemaTypes } from "./standard-schema-types.js";
|
|
6
7
|
async function writeIfChanged(file, source) {
|
|
7
8
|
try {
|
|
8
9
|
if (await readFile(file, "utf8") === source)
|
|
@@ -93,14 +94,70 @@ function publicSchema(expression, label, file, imports, checker, root, sharedRoo
|
|
|
93
94
|
const exported = imported.exported === "*" ? member : imported.exported;
|
|
94
95
|
if (!exported || (imported.exported !== "*" && member))
|
|
95
96
|
throw new Error(`${file.fileName}: ${label} must reference one shared export`);
|
|
96
|
-
|
|
97
|
-
if (type.flags & ts.TypeFlags.Any)
|
|
98
|
-
throw new Error(`${file.fileName}: ${label} has type any; public schema cannot be inferred`);
|
|
97
|
+
standardSchemaTypes(expression, checker, () => new Error(`${file.fileName}: ${label} must expose concrete Standard Schema input and output types`));
|
|
99
98
|
validateSharedGraph(imported.file, root, sharedRoot, options, new Set());
|
|
100
99
|
const target = relative(outputDir, imported.file).replaceAll("\\", "/").replace(/\.(?:ts|mts|cts)$/, (suffix) => suffix === ".mts" ? ".mjs" : suffix === ".cts" ? ".cjs" : ".js");
|
|
101
100
|
const specifier = target.startsWith(".") ? target : `./${target}`;
|
|
102
101
|
return `typeof import(${JSON.stringify(specifier)})[${JSON.stringify(exported)}]`;
|
|
103
102
|
}
|
|
103
|
+
function schemaInputType(expression, checker, invalid) {
|
|
104
|
+
return standardSchemaTypes(expression, checker, invalid).input;
|
|
105
|
+
}
|
|
106
|
+
function validatePathParams(expression, pathname, checker, file) {
|
|
107
|
+
const expected = [...pathname.matchAll(/\[(?:\.\.\.)?([A-Za-z_$][\w$]*)\]/g)].map((match) => match[1]);
|
|
108
|
+
const invalid = () => new Error(`${file.fileName}: params must contain exactly the route parameters as required URL strings`);
|
|
109
|
+
const inputType = schemaInputType(expression, checker, invalid);
|
|
110
|
+
const stringType = checker.getStringType();
|
|
111
|
+
for (const variant of inputType.isUnion() ? inputType.types : [inputType]) {
|
|
112
|
+
if (variant.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)
|
|
113
|
+
|| checker.getIndexTypeOfType(variant, ts.IndexKind.String))
|
|
114
|
+
throw invalid();
|
|
115
|
+
const fields = checker.getPropertiesOfType(variant);
|
|
116
|
+
if (fields.length !== expected.length || fields.some((field) => !expected.includes(field.getName())))
|
|
117
|
+
throw invalid();
|
|
118
|
+
for (const name of expected) {
|
|
119
|
+
const field = checker.getPropertyOfType(variant, name);
|
|
120
|
+
if (!field || field.flags & ts.SymbolFlags.Optional)
|
|
121
|
+
throw invalid();
|
|
122
|
+
const value = checker.getTypeOfSymbolAtLocation(field, expression);
|
|
123
|
+
if (value.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)
|
|
124
|
+
|| !checker.isTypeAssignableTo(value, stringType))
|
|
125
|
+
throw invalid();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function validateQuery(expression, checker, file) {
|
|
130
|
+
const invalid = () => new Error(`${file.fileName}: query values must be URL strings or arrays of URL strings`);
|
|
131
|
+
const inputType = schemaInputType(expression, checker, invalid);
|
|
132
|
+
const stringType = checker.getStringType();
|
|
133
|
+
const validValue = (value, arrayAllowed) => {
|
|
134
|
+
if (value.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never))
|
|
135
|
+
return false;
|
|
136
|
+
if (value.isUnion())
|
|
137
|
+
return value.types.every((part) => validValue(part, arrayAllowed));
|
|
138
|
+
if (value.flags & ts.TypeFlags.Undefined)
|
|
139
|
+
return true;
|
|
140
|
+
if (checker.isTypeAssignableTo(value, stringType))
|
|
141
|
+
return true;
|
|
142
|
+
if (arrayAllowed && (checker.isArrayType(value) || checker.isTupleType(value))) {
|
|
143
|
+
const item = checker.getIndexTypeOfType(value, ts.IndexKind.Number);
|
|
144
|
+
return !!item && validValue(item, false);
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
for (const variant of inputType.isUnion() ? inputType.types : [inputType]) {
|
|
149
|
+
if (!(variant.flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection))
|
|
150
|
+
|| checker.isArrayType(variant) || checker.isTupleType(variant))
|
|
151
|
+
throw invalid();
|
|
152
|
+
for (const field of checker.getPropertiesOfType(variant)) {
|
|
153
|
+
if (!validValue(checker.getTypeOfSymbolAtLocation(field, expression), true))
|
|
154
|
+
throw invalid();
|
|
155
|
+
}
|
|
156
|
+
const indexValue = checker.getIndexTypeOfType(variant, ts.IndexKind.String);
|
|
157
|
+
if (indexValue && !validValue(indexValue, true))
|
|
158
|
+
throw invalid();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
104
161
|
function validateSharedGraph(file, root, sharedRoot, options, seen) {
|
|
105
162
|
if (seen.has(file))
|
|
106
163
|
return;
|
|
@@ -150,7 +207,13 @@ function shapeFor(endpoint, checker, program, root, options, outputDir) {
|
|
|
150
207
|
return { type: `import("@meekojs/meeko/framework").RpcOperation<__Input<${input}>, __Output<${output}>>` };
|
|
151
208
|
}
|
|
152
209
|
const params = schema("params");
|
|
210
|
+
if (params && !endpoint.pathname.includes("["))
|
|
211
|
+
throw new Error(`${file.fileName}: static API path cannot declare params`);
|
|
212
|
+
if (params)
|
|
213
|
+
validatePathParams(objectProperty(contract, "params"), endpoint.pathname, checker, file);
|
|
153
214
|
const query = schema("query");
|
|
215
|
+
if (query)
|
|
216
|
+
validateQuery(objectProperty(contract, "query"), checker, file);
|
|
154
217
|
const body = schema("body");
|
|
155
218
|
const response = schema("response");
|
|
156
219
|
const content = objectProperty(contract, "contentType");
|