@parlor/client 0.1.0 → 0.2.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 +2 -0
- package/dist/action-client.d.ts +2 -0
- package/dist/action-client.d.ts.map +1 -1
- package/dist/action-client.js +13 -4
- package/dist/action-client.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-client.js.map +1 -1
- package/dist/query-keys.js.map +1 -1
- package/dist/react/action-query.d.ts +26 -0
- package/dist/react/action-query.d.ts.map +1 -0
- package/dist/react/action-query.js +33 -0
- package/dist/react/action-query.js.map +1 -0
- package/dist/react/agent-ui/activity-state.js.map +1 -1
- package/dist/react/agent-ui/chat-activity.js.map +1 -1
- package/dist/react/agent-ui/chat-confirm.js.map +1 -1
- package/dist/react/agent-ui/chat.js.map +1 -1
- package/dist/react/agent-ui/dev.js.map +1 -1
- package/dist/react/agent-ui/runtime.js.map +1 -1
- package/dist/react/agent-ui/use-agent-activity.js.map +1 -1
- package/dist/react/agent-ui/use-agent-confirmations.js.map +1 -1
- package/dist/react/agent-ui/use-agent-view.js.map +1 -1
- package/dist/react/chat/ask-fields.js.map +1 -1
- package/dist/react/chat/chat-ask.js.map +1 -1
- package/dist/react/chat/chat-composer.d.ts.map +1 -1
- package/dist/react/chat/chat-composer.js +1 -1
- package/dist/react/chat/chat-composer.js.map +1 -1
- package/dist/react/chat/chat-root.js.map +1 -1
- package/dist/react/chat/chat-thread.js.map +1 -1
- package/dist/react/chat/context.js.map +1 -1
- package/dist/react/chat/create.js.map +1 -1
- package/dist/react/chat/open-turns.js.map +1 -1
- package/dist/react/chat/runtime.d.ts +12 -1
- package/dist/react/chat/runtime.d.ts.map +1 -1
- package/dist/react/chat/runtime.js +12 -1
- package/dist/react/chat/runtime.js.map +1 -1
- package/dist/react/chat/use-chat.js.map +1 -1
- package/dist/react/chat/use-device-setup.js.map +1 -1
- package/dist/react/hooks.d.ts +7 -1
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +17 -13
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.d.ts +6 -3
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js +61 -11
- package/dist/react/provider.js.map +1 -1
- package/dist/react/retry.js.map +1 -1
- package/dist/react/sync-bridge.d.ts +4 -1
- package/dist/react/sync-bridge.d.ts.map +1 -1
- package/dist/react/sync-bridge.js.map +1 -1
- package/dist/react/sync.d.ts +41 -2
- package/dist/react/sync.d.ts.map +1 -1
- package/dist/react/sync.js +127 -9
- package/dist/react/sync.js.map +1 -1
- package/dist/react/use-capability.js.map +1 -1
- package/dist/react/use-devices.js.map +1 -1
- package/dist/react/use-link-device.js.map +1 -1
- package/dist/register.js.map +1 -1
- package/dist/router/errors.d.ts +13 -0
- package/dist/router/errors.d.ts.map +1 -0
- package/dist/router/errors.js +16 -0
- package/dist/router/errors.js.map +1 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +4 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/mount.d.ts +22 -0
- package/dist/router/mount.d.ts.map +1 -0
- package/dist/router/mount.js +27 -0
- package/dist/router/mount.js.map +1 -0
- package/dist/router/router.d.ts +29 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/router.js +44 -0
- package/dist/router/router.js.map +1 -0
- package/dist/session.js.map +1 -1
- package/dist/ssr/index.d.ts +2 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +2 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/ssr/render.d.ts +18 -0
- package/dist/ssr/render.d.ts.map +1 -0
- package/dist/ssr/render.js +96 -0
- package/dist/ssr/render.js.map +1 -0
- package/package.json +15 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/router/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAwC,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAOxF,MAAM,cAAc,GAAsB,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;AAE1E;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAM,GAAsB,EAAE;IAE9B,MAAM,QAAQ,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IAClD,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,MAAM,KAAK,GAAG,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAoB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { ActionError, type ActionErrorCode } from \"@parlor/core\";\nimport { type AnyRedirect, type NotFoundError, notFound } from \"@tanstack/react-router\";\n\n/** For each `ActionError` code, the `redirect(...)` or `notFound(...)` a route throws instead. */\nexport type ActionErrorRoutes = {\n readonly [C in ActionErrorCode]?: (error: ActionError) => AnyRedirect | NotFoundError;\n};\n\nconst DEFAULT_ROUTES: ActionErrorRoutes = { not_found: () => notFound() };\n\n/**\n * Rethrows an `ActionError` as what `routes` names for its code, `not_found` as `notFound()`\n * unless `routes` says otherwise, and anything else unchanged for the route's `errorComponent`.\n * Pass it as a route's `onError`, or to `.catch` in a loader.\n */\nexport function routeActionErrors(\n routes: ActionErrorRoutes = {},\n): (error: unknown) => never {\n const handlers = { ...DEFAULT_ROUTES, ...routes };\n return (error) => {\n const route = error instanceof ActionError ? handlers[error.code] : undefined;\n throw route ? route(error as ActionError) : error;\n };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { type ActionErrorRoutes, routeActionErrors } from "./errors.js";
|
|
2
|
+
export { type CreateParlorApp, mount, type ParlorApp } from "./mount.js";
|
|
3
|
+
export { createParlorRouter, type ParlorRouterContext, type ParlorRouterOptions, } from "./router.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAwB,KAAK,EAAkB,MAAM,YAAY,CAAC;AACzE,OAAO,EACL,kBAAkB,GAGnB,MAAM,aAAa,CAAC","sourcesContent":["export { type ActionErrorRoutes, routeActionErrors } from \"./errors.js\";\nexport { type CreateParlorApp, mount, type ParlorApp } from \"./mount.js\";\nexport {\n createParlorRouter,\n type ParlorRouterContext,\n type ParlorRouterOptions,\n} from \"./router.js\";\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AnyRouter } from "@tanstack/react-router";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { ActionClient } from "../action-client.js";
|
|
4
|
+
import type { RegisteredActions } from "../register.js";
|
|
5
|
+
/** Set on the page's container when the server rendered the app into it. */
|
|
6
|
+
export declare const SERVER_RENDERED = "data-parlor-ssr";
|
|
7
|
+
/** One instance of the app: its router and the element that renders it. */
|
|
8
|
+
export interface ParlorApp<TRouter extends AnyRouter = AnyRouter> {
|
|
9
|
+
readonly router: TRouter;
|
|
10
|
+
readonly element: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builds the app around an action client. The browser calls it once; with `web.ssr`, the
|
|
14
|
+
* server calls it for every page it renders, with a client that reaches the app in process.
|
|
15
|
+
*/
|
|
16
|
+
export type CreateParlorApp<TRouter extends AnyRouter = AnyRouter> = (client: ActionClient<RegisteredActions>) => ParlorApp<TRouter>;
|
|
17
|
+
/**
|
|
18
|
+
* Starts the app in `container`: it hydrates what the server rendered there, and renders from
|
|
19
|
+
* scratch otherwise, as on a page the server did not render and in the native shells.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mount(container: Element, { router, element }: ParlorApp): void;
|
|
22
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/router/mount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,4EAA4E;AAC5E,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,2EAA2E;AAC3E,MAAM,WAAW,SAAS,CAAC,OAAO,SAAS,SAAS,GAAG,SAAS;IAC9D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,SAAS,GAAG,SAAS,IAAI,CACnE,MAAM,EAAE,YAAY,CAAC,iBAAiB,CAAC,KACpC,SAAS,CAAC,OAAO,CAAC,CAAC;AAMxB;;;GAGG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,SAAS,GAAG,IAAI,CAiB9E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { hydrate } from "@tanstack/react-router/ssr/client";
|
|
2
|
+
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
3
|
+
/** Set on the page's container when the server rendered the app into it. */
|
|
4
|
+
export const SERVER_RENDERED = "data-parlor-ssr";
|
|
5
|
+
function render(container, element) {
|
|
6
|
+
createRoot(container).render(element);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Starts the app in `container`: it hydrates what the server rendered there, and renders from
|
|
10
|
+
* scratch otherwise, as on a page the server did not render and in the native shells.
|
|
11
|
+
*/
|
|
12
|
+
export function mount(container, { router, element }) {
|
|
13
|
+
if (!container.hasAttribute(SERVER_RENDERED)) {
|
|
14
|
+
render(container, element);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
hydrate(router)
|
|
18
|
+
.finally(() => globalThis.window?.$_TSR?.h())
|
|
19
|
+
.then(() => {
|
|
20
|
+
hydrateRoot(container, element);
|
|
21
|
+
}, (error) => {
|
|
22
|
+
console.error("[parlor] Could not hydrate the server-rendered page:", error);
|
|
23
|
+
container.replaceChildren();
|
|
24
|
+
render(container, element);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=mount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.js","sourceRoot":"","sources":["../../src/router/mount.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAK3D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAgBjD,SAAS,MAAM,CAAC,SAAkB,EAAE,OAAkB;IACpD,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,SAAkB,EAAE,EAAE,MAAM,EAAE,OAAO,EAAa;IACtE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,OAAO,CAAC,MAAM,CAAC;SACZ,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;SAC5C,IAAI,CACH,GAAG,EAAE;QACH,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,sDAAsD,EAAE,KAAK,CAAC,CAAC;QAC7E,SAAS,CAAC,eAAe,EAAE,CAAC;QAC5B,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC,CACF,CAAC;AACN,CAAC","sourcesContent":["import type { AnyRouter } from \"@tanstack/react-router\";\nimport { hydrate } from \"@tanstack/react-router/ssr/client\";\nimport type { ReactNode } from \"react\";\nimport { createRoot, hydrateRoot } from \"react-dom/client\";\n\nimport type { ActionClient } from \"../action-client.js\";\nimport type { RegisteredActions } from \"../register.js\";\n\n/** Set on the page's container when the server rendered the app into it. */\nexport const SERVER_RENDERED = \"data-parlor-ssr\";\n\n/** One instance of the app: its router and the element that renders it. */\nexport interface ParlorApp<TRouter extends AnyRouter = AnyRouter> {\n readonly router: TRouter;\n readonly element: ReactNode;\n}\n\n/**\n * Builds the app around an action client. The browser calls it once; with `web.ssr`, the\n * server calls it for every page it renders, with a client that reaches the app in process.\n */\nexport type CreateParlorApp<TRouter extends AnyRouter = AnyRouter> = (\n client: ActionClient<RegisteredActions>,\n) => ParlorApp<TRouter>;\n\nfunction render(container: Element, element: ReactNode): void {\n createRoot(container).render(element);\n}\n\n/**\n * Starts the app in `container`: it hydrates what the server rendered there, and renders from\n * scratch otherwise, as on a page the server did not render and in the native shells.\n */\nexport function mount(container: Element, { router, element }: ParlorApp): void {\n if (!container.hasAttribute(SERVER_RENDERED)) {\n render(container, element);\n return;\n }\n hydrate(router)\n .finally(() => globalThis.window?.$_TSR?.h())\n .then(\n () => {\n hydrateRoot(container, element);\n },\n (error: unknown) => {\n console.error(\"[parlor] Could not hydrate the server-rendered page:\", error);\n container.replaceChildren();\n render(container, element);\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { type AnyRoute, type RouterConstructorOptions, type RouterHistory, type TrailingSlashOption } from "@tanstack/react-router";
|
|
3
|
+
import type { ActionClient } from "../action-client.js";
|
|
4
|
+
import type { ActionQueryContext } from "../react/action-query.js";
|
|
5
|
+
import type { RegisteredActions } from "../register.js";
|
|
6
|
+
/** The router context Parlor's loaders read: `createRootRouteWithContext<ParlorRouterContext>()`. */
|
|
7
|
+
export type ParlorRouterContext = ActionQueryContext;
|
|
8
|
+
type Dehydrated = Record<string, unknown>;
|
|
9
|
+
type RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean, TRouterHistory extends RouterHistory> = RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, Dehydrated>;
|
|
10
|
+
type AppContext<TRouteTree extends AnyRoute> = Omit<NonNullable<RouterOptions<TRouteTree, "never", false, RouterHistory>["context"]>, keyof ParlorRouterContext>;
|
|
11
|
+
export type ParlorRouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = "never", TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory> = Omit<RouterOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory>, "context"> & {
|
|
12
|
+
readonly client: ActionClient<RegisteredActions>;
|
|
13
|
+
readonly queryClient?: QueryClient | undefined;
|
|
14
|
+
} & ({} extends AppContext<TRouteTree> ? {
|
|
15
|
+
readonly context?: AppContext<TRouteTree>;
|
|
16
|
+
} : {
|
|
17
|
+
readonly context: AppContext<TRouteTree>;
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* `createRouter` with `client` and `queryClient` (a new one unless given) in every loader's
|
|
21
|
+
* `context`, links that preload on hover, and TanStack Query deciding when data is stale
|
|
22
|
+
* (`defaultPreloadStaleTime: 0`). `FrameworkProvider` given this router uses its `queryClient`.
|
|
23
|
+
*
|
|
24
|
+
* A route renders on the server only when it sets `ssr: true` (`defaultSsr: false`). The query
|
|
25
|
+
* cache a server render filled, with its read tags, comes along to the browser, so sync keeps it fresh.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createParlorRouter<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = "never", TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory>(options: ParlorRouterOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory>): import("@tanstack/router-core").RouterCore<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, Dehydrated>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,WAAW,EACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,qGAAqG;AACrG,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAErD,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,KAAK,aAAa,CAChB,UAAU,SAAS,QAAQ,EAC3B,oBAAoB,SAAS,mBAAmB,EAChD,+BAA+B,SAAS,OAAO,EAC/C,cAAc,SAAS,aAAa,IAClC,wBAAwB,CAC1B,UAAU,EACV,oBAAoB,EACpB,+BAA+B,EAC/B,cAAc,EACd,UAAU,CACX,CAAC;AAEF,KAAK,UAAU,CAAC,UAAU,SAAS,QAAQ,IAAI,IAAI,CACjD,WAAW,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,EAChF,MAAM,mBAAmB,CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAC7B,UAAU,SAAS,QAAQ,EAC3B,oBAAoB,SAAS,mBAAmB,GAAG,OAAO,EAC1D,+BAA+B,SAAS,OAAO,GAAG,KAAK,EACvD,cAAc,SAAS,aAAa,GAAG,aAAa,IAClD,IAAI,CACN,aAAa,CACX,UAAU,EACV,oBAAoB,EACpB,+BAA+B,EAC/B,cAAc,CACf,EACD,SAAS,CACV,GAAG;IACF,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAChD,GAAG,CAAC,EAAE,SAAS,UAAU,CAAC,UAAU,CAAC,GAChC;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAA;CAAE,GAC7C;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC,CAAC;AAqBpD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,SAAS,QAAQ,EAC3B,oBAAoB,SAAS,mBAAmB,GAAG,OAAO,EAC1D,+BAA+B,SAAS,OAAO,GAAG,KAAK,EACvD,cAAc,SAAS,aAAa,GAAG,aAAa,EAEpD,OAAO,EAAE,mBAAmB,CAC1B,UAAU,EACV,oBAAoB,EACpB,+BAA+B,EAC/B,cAAc,CACf,6IA+BF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { dehydrate, hydrate, QueryClient, } from "@tanstack/react-query";
|
|
2
|
+
import { createRouter, } from "@tanstack/react-router";
|
|
3
|
+
import { readTagsOf } from "../react/sync.js";
|
|
4
|
+
const QUERY_CACHE = "parlorQueryCache";
|
|
5
|
+
function dehydrateQueryCache(queryClient) {
|
|
6
|
+
return { state: dehydrate(queryClient), readTags: readTagsOf(queryClient).entries() };
|
|
7
|
+
}
|
|
8
|
+
function hydrateQueryCache(queryClient, cache) {
|
|
9
|
+
if (!cache)
|
|
10
|
+
return;
|
|
11
|
+
const { state, readTags } = cache;
|
|
12
|
+
hydrate(queryClient, state);
|
|
13
|
+
const registry = readTagsOf(queryClient);
|
|
14
|
+
for (const [hash, tags, cursor] of readTags)
|
|
15
|
+
registry.record(hash, tags, cursor);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `createRouter` with `client` and `queryClient` (a new one unless given) in every loader's
|
|
19
|
+
* `context`, links that preload on hover, and TanStack Query deciding when data is stale
|
|
20
|
+
* (`defaultPreloadStaleTime: 0`). `FrameworkProvider` given this router uses its `queryClient`.
|
|
21
|
+
*
|
|
22
|
+
* A route renders on the server only when it sets `ssr: true` (`defaultSsr: false`). The query
|
|
23
|
+
* cache a server render filled, with its read tags, comes along to the browser, so sync keeps it fresh.
|
|
24
|
+
*/
|
|
25
|
+
export function createParlorRouter(options) {
|
|
26
|
+
const { client, queryClient = new QueryClient(), context, dehydrate: dehydrateApp, hydrate: hydrateApp, ...rest } = options;
|
|
27
|
+
const routerOptions = {
|
|
28
|
+
defaultPreload: "intent",
|
|
29
|
+
defaultPreloadStaleTime: 0,
|
|
30
|
+
defaultSsr: false,
|
|
31
|
+
...rest,
|
|
32
|
+
context: { ...context, client, queryClient },
|
|
33
|
+
dehydrate: async () => ({
|
|
34
|
+
...(await dehydrateApp?.()),
|
|
35
|
+
[QUERY_CACHE]: dehydrateQueryCache(queryClient),
|
|
36
|
+
}),
|
|
37
|
+
hydrate: async (dehydrated) => {
|
|
38
|
+
hydrateQueryCache(queryClient, dehydrated[QUERY_CACHE]);
|
|
39
|
+
await hydrateApp?.(dehydrated);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
return createRouter(routerOptions);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,OAAO,EACP,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,YAAY,GAIb,MAAM,wBAAwB,CAAC;AAIhC,OAAO,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AA8C9D,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAOvC,SAAS,mBAAmB,CAAC,WAAwB;IACnD,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;AACxF,CAAC;AAED,SAAS,iBAAiB,CAAC,WAAwB,EAAE,KAAc;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAA6B,CAAC;IAC1D,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,QAAQ;QAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAMhC,OAKC;IAED,MAAM,EACJ,MAAM,EACN,WAAW,GAAG,IAAI,WAAW,EAAE,EAC/B,OAAO,EACP,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,UAAU,EACnB,GAAG,IAAI,EACR,GAAG,OAAO,CAAC;IACZ,MAAM,aAAa,GAAG;QACpB,cAAc,EAAE,QAAQ;QACxB,uBAAuB,EAAE,CAAC;QAC1B,UAAU,EAAE,KAAK;QACjB,GAAG,IAAI;QACP,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE;QAC5C,SAAS,EAAE,KAAK,IAAyB,EAAE,CAAC,CAAC;YAC3C,GAAG,CAAC,MAAM,YAAY,EAAE,EAAE,CAAC;YAC3B,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC;SAChD,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,UAAsB,EAAE,EAAE;YACxC,iBAAiB,CAAC,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;YACxD,MAAM,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;KAMF,CAAC;IACF,OAAO,YAAY,CAAC,aAAa,CAAC,CAAC;AACrC,CAAC","sourcesContent":["import {\n type DehydratedState,\n dehydrate,\n hydrate,\n QueryClient,\n} from \"@tanstack/react-query\";\nimport {\n type AnyRoute,\n createRouter,\n type RouterConstructorOptions,\n type RouterHistory,\n type TrailingSlashOption,\n} from \"@tanstack/react-router\";\n\nimport type { ActionClient } from \"../action-client.js\";\nimport type { ActionQueryContext } from \"../react/action-query.js\";\nimport { type ReadEntry, readTagsOf } from \"../react/sync.js\";\nimport type { RegisteredActions } from \"../register.js\";\n\n/** The router context Parlor's loaders read: `createRootRouteWithContext<ParlorRouterContext>()`. */\nexport type ParlorRouterContext = ActionQueryContext;\n\ntype Dehydrated = Record<string, unknown>;\n\ntype RouterOptions<\n TRouteTree extends AnyRoute,\n TTrailingSlashOption extends TrailingSlashOption,\n TDefaultStructuralSharingOption extends boolean,\n TRouterHistory extends RouterHistory,\n> = RouterConstructorOptions<\n TRouteTree,\n TTrailingSlashOption,\n TDefaultStructuralSharingOption,\n TRouterHistory,\n Dehydrated\n>;\n\ntype AppContext<TRouteTree extends AnyRoute> = Omit<\n NonNullable<RouterOptions<TRouteTree, \"never\", false, RouterHistory>[\"context\"]>,\n keyof ParlorRouterContext\n>;\n\nexport type ParlorRouterOptions<\n TRouteTree extends AnyRoute,\n TTrailingSlashOption extends TrailingSlashOption = \"never\",\n TDefaultStructuralSharingOption extends boolean = false,\n TRouterHistory extends RouterHistory = RouterHistory,\n> = Omit<\n RouterOptions<\n TRouteTree,\n TTrailingSlashOption,\n TDefaultStructuralSharingOption,\n TRouterHistory\n >,\n \"context\"\n> & {\n readonly client: ActionClient<RegisteredActions>;\n readonly queryClient?: QueryClient | undefined;\n} & ({} extends AppContext<TRouteTree>\n ? { readonly context?: AppContext<TRouteTree> }\n : { readonly context: AppContext<TRouteTree> });\n\nconst QUERY_CACHE = \"parlorQueryCache\";\n\ninterface DehydratedQueryCache {\n readonly state: DehydratedState;\n readonly readTags: readonly ReadEntry[];\n}\n\nfunction dehydrateQueryCache(queryClient: QueryClient): DehydratedQueryCache {\n return { state: dehydrate(queryClient), readTags: readTagsOf(queryClient).entries() };\n}\n\nfunction hydrateQueryCache(queryClient: QueryClient, cache: unknown): void {\n if (!cache) return;\n const { state, readTags } = cache as DehydratedQueryCache;\n hydrate(queryClient, state);\n const registry = readTagsOf(queryClient);\n for (const [hash, tags, cursor] of readTags) registry.record(hash, tags, cursor);\n}\n\n/**\n * `createRouter` with `client` and `queryClient` (a new one unless given) in every loader's\n * `context`, links that preload on hover, and TanStack Query deciding when data is stale\n * (`defaultPreloadStaleTime: 0`). `FrameworkProvider` given this router uses its `queryClient`.\n *\n * A route renders on the server only when it sets `ssr: true` (`defaultSsr: false`). The query\n * cache a server render filled, with its read tags, comes along to the browser, so sync keeps it fresh.\n */\nexport function createParlorRouter<\n TRouteTree extends AnyRoute,\n TTrailingSlashOption extends TrailingSlashOption = \"never\",\n TDefaultStructuralSharingOption extends boolean = false,\n TRouterHistory extends RouterHistory = RouterHistory,\n>(\n options: ParlorRouterOptions<\n TRouteTree,\n TTrailingSlashOption,\n TDefaultStructuralSharingOption,\n TRouterHistory\n >,\n) {\n const {\n client,\n queryClient = new QueryClient(),\n context,\n dehydrate: dehydrateApp,\n hydrate: hydrateApp,\n ...rest\n } = options;\n const routerOptions = {\n defaultPreload: \"intent\",\n defaultPreloadStaleTime: 0,\n defaultSsr: false,\n ...rest,\n context: { ...context, client, queryClient },\n dehydrate: async (): Promise<Dehydrated> => ({\n ...(await dehydrateApp?.()),\n [QUERY_CACHE]: dehydrateQueryCache(queryClient),\n }),\n hydrate: async (dehydrated: Dehydrated) => {\n hydrateQueryCache(queryClient, dehydrated[QUERY_CACHE]);\n await hydrateApp?.(dehydrated);\n },\n } as RouterOptions<\n TRouteTree,\n TTrailingSlashOption,\n TDefaultStructuralSharingOption,\n TRouterHistory\n >;\n return createRouter(routerOptions);\n}\n"]}
|
package/dist/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAmFA,6FAA6F;AAC7F,MAAM,OAAO,cAAe,SAAQ,KAAK;IACrB,IAAI,GAAG,gBAAgB,CAAC;CAC3C"}
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAmFA,6FAA6F;AAC7F,MAAM,OAAO,cAAe,SAAQ,KAAK;IACrB,IAAI,GAAG,gBAAgB,CAAC;CAC3C","sourcesContent":["import type { HostPairing, PairingDevice } from \"@parlor/capabilities\";\nimport type { AnswerToSign, ItemSignature, PromptToSign } from \"@parlor/chat\";\nimport type { DeviceId } from \"@parlor/core\";\n\nexport interface SessionUser {\n readonly id: string;\n readonly name: string;\n readonly email: string;\n}\n\n/** `anonymous`: the server signs nobody in, so everyone uses the app without an account. */\nexport type SessionSnapshot =\n | { readonly status: \"loading\" }\n | { readonly status: \"anonymous\" }\n | { readonly status: \"signed-out\" }\n | { readonly status: \"signed-in\"; readonly user: SessionUser };\n\n/**\n * Who is signed in on this device, as the app's auth client sees it. `current` returns the\n * same object until the session changes.\n */\nexport interface SessionSource {\n current(): SessionSnapshot;\n subscribe(listener: () => void): () => void;\n}\n\n/** This device's signing identity, as the chat needs it. */\nexport interface SessionDevice {\n readonly id: DeviceId;\n /** The public half of its keys, as the account registered them. */\n readonly keys: PairingDevice;\n /** With `presence`, the platform asks for Touch ID, Face ID or a passkey first. */\n signPrompt(\n prompt: PromptToSign,\n options?: { readonly presence?: boolean },\n ): Promise<ItemSignature>;\n /** With `presence`, the platform asks for Touch ID, Face ID or a passkey first. */\n signAnswer(\n answer: AnswerToSign,\n options: { readonly presence: boolean },\n ): Promise<ItemSignature>;\n}\n\nexport interface DeviceSource {\n /**\n * Enrolls this device with the account on first use; undefined where it cannot sign.\n * `presence: false` enrolls it without a presence key. Rejects with the capability's\n * `PresenceSetupCancelled` when the user dismisses the passkey prompt.\n */\n device(options?: { readonly presence?: boolean }): Promise<SessionDevice | undefined>;\n /** `needed` while the first `device()` would ask the user to create a passkey. */\n deviceSetup?(): Promise<\"needed\" | \"ready\">;\n}\n\nexport interface HostAuthorizer {\n /** For pairing the host on this computer: a code for it, approved by this session. */\n authorizeHost(pairing: HostPairing): Promise<string>;\n}\n\n/** A device waiting to be linked, its keys checked against the code it shows. */\nexport interface PendingLink {\n readonly id: string;\n readonly name: string;\n readonly platform: string;\n readonly commitment: string;\n /** The short code the device shows, for `parlor-host link` on the computer. */\n readonly code: string;\n}\n\n/** One of the account's devices, as the devices list shows it. */\nexport interface AccountDevice {\n readonly id: string;\n readonly name: string;\n readonly platform: string;\n /** This device. */\n readonly current: boolean;\n readonly createdAt: string;\n readonly vouched: boolean;\n readonly linking: boolean;\n /** The device whose vouch linked it last. */\n readonly linkedBy?: string | undefined;\n}\n\n/** Thrown by `vouch` on a device with no presence key: the computer itself links it then. */\nexport class LinkOnComputer extends Error {\n override readonly name = \"LinkOnComputer\";\n}\n\n/** Linking devices: a new one shows a code, a linked one checks it and vouches for it. */\nexport interface DeviceLinker {\n /** Never prompts: a device not created yet reads as none. */\n linkState(): Promise<{\n readonly deviceId?: string | undefined;\n readonly vouched: boolean;\n /** It approved `connect` on the computer, so its first prompt links it. */\n readonly pairing: boolean;\n }>;\n /** Enrolls this device, with Face ID or Touch ID unless `presence` is false, and shows its code. */\n startLink(options?: { readonly presence?: boolean }): Promise<{\n readonly deviceId: string;\n readonly code: string;\n /** The whole hash the code is the start of; the QR carries it. */\n readonly commitment: string;\n readonly expiresAt: string;\n }>;\n /**\n * The device showing `code`, or the one whose QR named its id and whole commitment; throws\n * when the keys the app returns do not match.\n */\n resolveLink(\n from: string | { readonly deviceId: string; readonly commitment: string },\n ): Promise<PendingLink>;\n /** Vouches with this device's presence key; throws `LinkOnComputer` when it has none. */\n vouch(link: PendingLink): Promise<void>;\n /** Whether this device has a presence key to vouch with; never prompts. */\n canVouch(): Promise<boolean>;\n listDevices(): Promise<readonly AccountDevice[]>;\n /** Removes a device, confirmed with Face ID or Touch ID; throws `LinkOnComputer` without them. */\n removeDevice(id: string): Promise<void>;\n}\n\n/** What `FrameworkProvider` takes as `auth`; signing, host pairing and linking are optional. */\nexport type FrameworkAuth = SessionSource &\n Partial<DeviceSource & HostAuthorizer & DeviceLinker>;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ssr/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ssr/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAuC,MAAM,aAAa,CAAC","sourcesContent":["export { createRenderer, type RenderOptions, type RenderPage } from \"./render.js\";\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CreateParlorApp } from "../router/mount.js";
|
|
2
|
+
export interface RenderOptions {
|
|
3
|
+
/** The app's `index.html` as the browser gets it; the page renders into its `#root`. */
|
|
4
|
+
readonly template: string;
|
|
5
|
+
/** Reaches the app's server: in process, through its Durable Object, or over the network in dev. */
|
|
6
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
7
|
+
/** Goes on every inline script; the response's policy allows it. */
|
|
8
|
+
readonly nonce: string;
|
|
9
|
+
}
|
|
10
|
+
/** The page, or undefined when a route it matches does not render on the server. */
|
|
11
|
+
export type RenderPage = (request: Request, options: RenderOptions) => Promise<Response | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* Renders one document request with TanStack Router's SSR handler: the loaders run against
|
|
14
|
+
* the app's server with the request's cookie and authorization, then React renders the
|
|
15
|
+
* matched routes and the router's state, query cache included, goes along for hydration.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createRenderer(createApp: CreateParlorApp): RenderPage;
|
|
18
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/ssr/render.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,eAAe,EAAmB,MAAM,oBAAoB,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oGAAoG;IACpG,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxD,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,oFAAoF;AACpF,MAAM,MAAM,UAAU,GAAG,CACvB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,aAAa,KACnB,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;AAkFnC;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,eAAe,GAAG,UAAU,CAoCrE"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { RouterContextProvider, Scripts } from "@tanstack/react-router";
|
|
3
|
+
import { createRequestHandler, getSsrStatus, transformHtmlStringWithRouter, } from "@tanstack/react-router/ssr/server";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
import { renderToReadableStream, renderToString } from "react-dom/server";
|
|
6
|
+
import { createActionClient } from "../action-client.js";
|
|
7
|
+
import { SERVER_RENDERED } from "../router/mount.js";
|
|
8
|
+
const FORWARDED_HEADERS = ["cookie", "authorization"];
|
|
9
|
+
const APP_TAG = "parlor-app";
|
|
10
|
+
const CONTAINER = /<([a-z][\w-]*)(\s[^>]*?\bid=(["']?)root\3(?:\s[^>]*)?)>\s*<\/\1>/i;
|
|
11
|
+
const TITLE = /<title[\s>]/i;
|
|
12
|
+
const TEMPLATE_TITLE = /\s*<title[^>]*>[\s\S]*?<\/title>/i;
|
|
13
|
+
let warnedNoContainer = false;
|
|
14
|
+
function forwarded(headers) {
|
|
15
|
+
const kept = {};
|
|
16
|
+
for (const name of FORWARDED_HEADERS) {
|
|
17
|
+
const value = headers.get(name);
|
|
18
|
+
if (value !== null)
|
|
19
|
+
kept[name] = value;
|
|
20
|
+
}
|
|
21
|
+
return kept;
|
|
22
|
+
}
|
|
23
|
+
/** A route that says it never renders on the server settles it before any loader runs. */
|
|
24
|
+
function declined(router, pathname) {
|
|
25
|
+
const [routes] = router.getMatchedRoutes(pathname);
|
|
26
|
+
return routes.some((route) => {
|
|
27
|
+
const ssr = route.options.ssr ?? router.options.defaultSsr ?? true;
|
|
28
|
+
return ssr === false || ssr === "data-only";
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Every element above the app has one child, so `useId` counts as it does from the browser's
|
|
33
|
+
* root. The router's scripts render after the app, once it has taken what loaders dehydrated.
|
|
34
|
+
*/
|
|
35
|
+
async function renderDocument(router, element, nonce) {
|
|
36
|
+
const stream = await renderToReadableStream(_jsx("html", { children: _jsx("body", { children: createElement(APP_TAG, null, element) }) }), {
|
|
37
|
+
nonce,
|
|
38
|
+
progressiveChunkSize: Number.POSITIVE_INFINITY,
|
|
39
|
+
onError: (error) => console.error("[parlor] Server render:", error),
|
|
40
|
+
});
|
|
41
|
+
await stream.allReady;
|
|
42
|
+
const app = await new Response(stream).text();
|
|
43
|
+
const scripts = renderToString(_jsx(RouterContextProvider, { router: router, children: _jsx(Scripts, {}) }));
|
|
44
|
+
const html = app
|
|
45
|
+
.replace(/^<!DOCTYPE html>/i, "")
|
|
46
|
+
.replace("</body>", () => `${scripts}</body>`);
|
|
47
|
+
return transformHtmlStringWithRouter(router, html);
|
|
48
|
+
}
|
|
49
|
+
/** React renders the app and hoists its head tags; the template keeps everything else. */
|
|
50
|
+
function intoTemplate(template, rendered) {
|
|
51
|
+
const head = rendered.slice(rendered.indexOf("<head>") + "<head>".length, rendered.indexOf("</head>"));
|
|
52
|
+
const appStart = rendered.indexOf(`<${APP_TAG}>`) + `<${APP_TAG}>`.length;
|
|
53
|
+
const appEnd = rendered.lastIndexOf(`</${APP_TAG}>`);
|
|
54
|
+
const scripts = rendered.slice(appEnd + `</${APP_TAG}>`.length, rendered.lastIndexOf("</body>"));
|
|
55
|
+
const headEnd = template.indexOf("</head>");
|
|
56
|
+
const templateHead = template.slice(0, headEnd);
|
|
57
|
+
const shell = `${TITLE.test(head) ? templateHead.replace(TEMPLATE_TITLE, "") : templateHead}${head}${template.slice(headEnd)}`;
|
|
58
|
+
return shell.replace(CONTAINER, (_match, tag, attributes) => `<${tag}${attributes} ${SERVER_RENDERED}>${rendered.slice(appStart, appEnd)}</${tag}>${scripts}`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Renders one document request with TanStack Router's SSR handler: the loaders run against
|
|
62
|
+
* the app's server with the request's cookie and authorization, then React renders the
|
|
63
|
+
* matched routes and the router's state, query cache included, goes along for hydration.
|
|
64
|
+
*/
|
|
65
|
+
export function createRenderer(createApp) {
|
|
66
|
+
return async (request, { template, fetch, nonce }) => {
|
|
67
|
+
if (!CONTAINER.test(template)) {
|
|
68
|
+
if (!warnedNoContainer) {
|
|
69
|
+
warnedNoContainer = true;
|
|
70
|
+
console.warn('[parlor] index.html has no empty element with id="root", so pages are not rendered on the server.');
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const url = new URL(request.url);
|
|
75
|
+
const { router, element } = createApp(createActionClient({
|
|
76
|
+
baseUrl: url.origin,
|
|
77
|
+
fetch: (input, init) => fetch(new Request(input, init)),
|
|
78
|
+
headers: forwarded(request.headers),
|
|
79
|
+
}));
|
|
80
|
+
if (declined(router, url.pathname))
|
|
81
|
+
return undefined;
|
|
82
|
+
router.update({ ...router.options, ssr: { nonce } });
|
|
83
|
+
const notRendered = new Response();
|
|
84
|
+
const response = await createRequestHandler({ request, createRouter: () => router })(async ({ responseHeaders }) => {
|
|
85
|
+
if (!router.state.matches.every((match) => match.ssr === true))
|
|
86
|
+
return notRendered;
|
|
87
|
+
const page = intoTemplate(template, await renderDocument(router, element, nonce));
|
|
88
|
+
return new Response(page, {
|
|
89
|
+
status: getSsrStatus(router),
|
|
90
|
+
headers: responseHeaders,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return response === notRendered ? undefined : response;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/ssr/render.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type AnyRouter, RouterContextProvider, Scripts } from \"@tanstack/react-router\";\nimport {\n createRequestHandler,\n getSsrStatus,\n transformHtmlStringWithRouter,\n} from \"@tanstack/react-router/ssr/server\";\nimport { createElement, type ReactNode } from \"react\";\nimport { renderToReadableStream, renderToString } from \"react-dom/server\";\n\nimport { createActionClient } from \"../action-client.js\";\nimport { type CreateParlorApp, SERVER_RENDERED } from \"../router/mount.js\";\n\nexport interface RenderOptions {\n /** The app's `index.html` as the browser gets it; the page renders into its `#root`. */\n readonly template: string;\n /** Reaches the app's server: in process, through its Durable Object, or over the network in dev. */\n readonly fetch: (request: Request) => Promise<Response>;\n /** Goes on every inline script; the response's policy allows it. */\n readonly nonce: string;\n}\n\n/** The page, or undefined when a route it matches does not render on the server. */\nexport type RenderPage = (\n request: Request,\n options: RenderOptions,\n) => Promise<Response | undefined>;\n\nconst FORWARDED_HEADERS = [\"cookie\", \"authorization\"] as const;\nconst APP_TAG = \"parlor-app\";\nconst CONTAINER = /<([a-z][\\w-]*)(\\s[^>]*?\\bid=([\"']?)root\\3(?:\\s[^>]*)?)>\\s*<\\/\\1>/i;\nconst TITLE = /<title[\\s>]/i;\nconst TEMPLATE_TITLE = /\\s*<title[^>]*>[\\s\\S]*?<\\/title>/i;\n\nlet warnedNoContainer = false;\n\nfunction forwarded(headers: Headers): Record<string, string> {\n const kept: Record<string, string> = {};\n for (const name of FORWARDED_HEADERS) {\n const value = headers.get(name);\n if (value !== null) kept[name] = value;\n }\n return kept;\n}\n\n/** A route that says it never renders on the server settles it before any loader runs. */\nfunction declined(router: AnyRouter, pathname: string): boolean {\n const [routes] = router.getMatchedRoutes(pathname);\n return routes.some((route) => {\n const ssr = route.options.ssr ?? router.options.defaultSsr ?? true;\n return ssr === false || ssr === \"data-only\";\n });\n}\n\n/**\n * Every element above the app has one child, so `useId` counts as it does from the browser's\n * root. The router's scripts render after the app, once it has taken what loaders dehydrated.\n */\nasync function renderDocument(\n router: AnyRouter,\n element: ReactNode,\n nonce: string,\n): Promise<string> {\n const stream = await renderToReadableStream(\n <html>\n <body>{createElement(APP_TAG, null, element)}</body>\n </html>,\n {\n nonce,\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n onError: (error) => console.error(\"[parlor] Server render:\", error),\n },\n );\n await stream.allReady;\n const app = await new Response(stream).text();\n const scripts = renderToString(\n <RouterContextProvider router={router}>\n <Scripts />\n </RouterContextProvider>,\n );\n const html = app\n .replace(/^<!DOCTYPE html>/i, \"\")\n .replace(\"</body>\", () => `${scripts}</body>`);\n return transformHtmlStringWithRouter(router, html);\n}\n\n/** React renders the app and hoists its head tags; the template keeps everything else. */\nfunction intoTemplate(template: string, rendered: string): string {\n const head = rendered.slice(\n rendered.indexOf(\"<head>\") + \"<head>\".length,\n rendered.indexOf(\"</head>\"),\n );\n const appStart = rendered.indexOf(`<${APP_TAG}>`) + `<${APP_TAG}>`.length;\n const appEnd = rendered.lastIndexOf(`</${APP_TAG}>`);\n const scripts = rendered.slice(\n appEnd + `</${APP_TAG}>`.length,\n rendered.lastIndexOf(\"</body>\"),\n );\n const headEnd = template.indexOf(\"</head>\");\n const templateHead = template.slice(0, headEnd);\n const shell = `${TITLE.test(head) ? templateHead.replace(TEMPLATE_TITLE, \"\") : templateHead}${head}${template.slice(headEnd)}`;\n return shell.replace(\n CONTAINER,\n (_match, tag: string, attributes: string) =>\n `<${tag}${attributes} ${SERVER_RENDERED}>${rendered.slice(appStart, appEnd)}</${tag}>${scripts}`,\n );\n}\n\n/**\n * Renders one document request with TanStack Router's SSR handler: the loaders run against\n * the app's server with the request's cookie and authorization, then React renders the\n * matched routes and the router's state, query cache included, goes along for hydration.\n */\nexport function createRenderer(createApp: CreateParlorApp): RenderPage {\n return async (request, { template, fetch, nonce }) => {\n if (!CONTAINER.test(template)) {\n if (!warnedNoContainer) {\n warnedNoContainer = true;\n console.warn(\n '[parlor] index.html has no empty element with id=\"root\", so pages are not rendered on the server.',\n );\n }\n return undefined;\n }\n const url = new URL(request.url);\n const { router, element } = createApp(\n createActionClient({\n baseUrl: url.origin,\n fetch: (input, init) => fetch(new Request(input, init)),\n headers: forwarded(request.headers),\n }),\n );\n if (declined(router, url.pathname)) return undefined;\n router.update({ ...router.options, ssr: { nonce } });\n\n const notRendered = new Response();\n const response = await createRequestHandler({ request, createRouter: () => router })(\n async ({ responseHeaders }) => {\n if (!router.state.matches.every((match) => match.ssr === true))\n return notRendered;\n const page = intoTemplate(template, await renderDocument(router, element, nonce));\n return new Response(page, {\n status: getSsrStatus(router),\n headers: responseHeaders,\n });\n },\n );\n return response === notRendered ? undefined : response;\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parlor/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"homepage": "https://cychien.github.io/parlor",
|
|
5
5
|
"bugs": "https://github.com/cychien/parlor/issues",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,14 +22,22 @@
|
|
|
22
22
|
"./react": {
|
|
23
23
|
"types": "./dist/react/index.d.ts",
|
|
24
24
|
"import": "./dist/react/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./router": {
|
|
27
|
+
"types": "./dist/router/index.d.ts",
|
|
28
|
+
"import": "./dist/router/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./ssr": {
|
|
31
|
+
"types": "./dist/ssr/index.d.ts",
|
|
32
|
+
"import": "./dist/ssr/index.js"
|
|
25
33
|
}
|
|
26
34
|
},
|
|
27
35
|
"dependencies": {
|
|
28
|
-
"@parlor/
|
|
29
|
-
"@parlor/
|
|
30
|
-
"@parlor/
|
|
31
|
-
"@parlor/core": "0.
|
|
32
|
-
"@parlor/sync": "0.
|
|
36
|
+
"@parlor/agent-ui": "0.2.0",
|
|
37
|
+
"@parlor/chat": "0.2.0",
|
|
38
|
+
"@parlor/capabilities": "0.2.0",
|
|
39
|
+
"@parlor/core": "0.2.0",
|
|
40
|
+
"@parlor/sync": "0.2.0"
|
|
33
41
|
},
|
|
34
42
|
"devDependencies": {
|
|
35
43
|
"@tanstack/react-query": "^5.104.1",
|
|
@@ -47,7 +55,7 @@
|
|
|
47
55
|
"zod": "^4.6.5"
|
|
48
56
|
},
|
|
49
57
|
"peerDependencies": {
|
|
50
|
-
"@tanstack/react-query": ">=5",
|
|
58
|
+
"@tanstack/react-query": ">=5.102",
|
|
51
59
|
"@tanstack/react-router": ">=1.170",
|
|
52
60
|
"react": ">=19",
|
|
53
61
|
"react-dom": ">=19"
|