pulse-framework 0.1.38a4__tar.gz → 0.1.38a6__tar.gz
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.
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/PKG-INFO +1 -1
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/pyproject.toml +1 -1
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/__init__.py +4 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/app.py +59 -21
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/cmd.py +9 -2
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/templates/layout.py +20 -3
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/messages.py +22 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/middleware.py +64 -3
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/plugin.py +1 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/README.md +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/channel.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/dependencies.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/folder_lock.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/helpers.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/models.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/packages.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/processes.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/secrets.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cli/uvicorn_log_config.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/codegen.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/imports.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/js.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/templates/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/templates/route.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/templates/routes_ts.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/utils.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/components/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/components/for_.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/components/if_.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/components/react_router.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/context.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/cookies.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/css.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/decorators.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/env.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/form.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/helpers.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/core.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/effects.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/runtime.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/setup.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/stable.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/hooks/states.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/elements.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/events.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/props.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/svg.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/tags.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/html/tags.pyi +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/proxy.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/py.typed +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/query.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/react_component.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/reactive.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/reactive_extensions.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/render_session.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/renderer.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/request.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/routing.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/serializer.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/state.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/types/__init__.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/types/event_handler.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/user_session.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/vdom.py +0 -0
- {pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/version.py +0 -0
|
@@ -1300,6 +1300,10 @@ from pulse.html.tags import (
|
|
|
1300
1300
|
from pulse.html.tags import (
|
|
1301
1301
|
wbr as wbr,
|
|
1302
1302
|
)
|
|
1303
|
+
from pulse.messages import Directives as Directives
|
|
1304
|
+
from pulse.messages import PrerenderPayload as PrerenderPayload
|
|
1305
|
+
from pulse.messages import PrerenderResult as PrerenderResult
|
|
1306
|
+
from pulse.messages import SocketIODirectives as SocketIODirectives
|
|
1303
1307
|
|
|
1304
1308
|
# Middleware
|
|
1305
1309
|
from pulse.middleware import (
|
|
@@ -11,7 +11,7 @@ from collections import defaultdict
|
|
|
11
11
|
from collections.abc import Awaitable, Sequence
|
|
12
12
|
from contextlib import asynccontextmanager
|
|
13
13
|
from enum import IntEnum
|
|
14
|
-
from typing import Any, Callable, Literal,
|
|
14
|
+
from typing import Any, Callable, Literal, TypeVar, cast
|
|
15
15
|
|
|
16
16
|
import socketio
|
|
17
17
|
import uvicorn
|
|
@@ -56,7 +56,8 @@ from pulse.messages import (
|
|
|
56
56
|
ClientChannelResponseMessage,
|
|
57
57
|
ClientMessage,
|
|
58
58
|
ClientPulseMessage,
|
|
59
|
-
|
|
59
|
+
PrerenderPayload,
|
|
60
|
+
PrerenderResult,
|
|
60
61
|
ServerMessage,
|
|
61
62
|
)
|
|
62
63
|
from pulse.middleware import (
|
|
@@ -73,7 +74,7 @@ from pulse.proxy import PulseProxy
|
|
|
73
74
|
from pulse.react_component import ReactComponent, registered_react_components
|
|
74
75
|
from pulse.render_session import RenderSession
|
|
75
76
|
from pulse.request import PulseRequest
|
|
76
|
-
from pulse.routing import Layout, Route,
|
|
77
|
+
from pulse.routing import Layout, Route, RouteTree
|
|
77
78
|
from pulse.serializer import Serialized, deserialize, serialize
|
|
78
79
|
from pulse.user_session import (
|
|
79
80
|
CookieSessionStore,
|
|
@@ -91,24 +92,13 @@ class AppStatus(IntEnum):
|
|
|
91
92
|
created = 0
|
|
92
93
|
initialized = 1
|
|
93
94
|
running = 2
|
|
94
|
-
|
|
95
|
+
draining = 3
|
|
96
|
+
stopped = 4
|
|
95
97
|
|
|
96
98
|
|
|
97
99
|
PulseMode = Literal["subdomains", "single-server"]
|
|
98
100
|
|
|
99
101
|
|
|
100
|
-
class PrerenderPayload(TypedDict):
|
|
101
|
-
paths: list[str]
|
|
102
|
-
routeInfo: RouteInfo
|
|
103
|
-
ttlSeconds: NotRequired[float | int]
|
|
104
|
-
renderId: NotRequired[str]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
class PrerenderResult(TypedDict):
|
|
108
|
-
renderId: str
|
|
109
|
-
views: dict[str, ServerInitMessage | None]
|
|
110
|
-
|
|
111
|
-
|
|
112
102
|
class App:
|
|
113
103
|
"""
|
|
114
104
|
Pulse UI Application - the main entry point for defining your app.
|
|
@@ -277,6 +267,11 @@ class App:
|
|
|
277
267
|
try:
|
|
278
268
|
yield
|
|
279
269
|
finally:
|
|
270
|
+
try:
|
|
271
|
+
await self.close()
|
|
272
|
+
except Exception:
|
|
273
|
+
logger.exception("Error during App.close()")
|
|
274
|
+
|
|
280
275
|
try:
|
|
281
276
|
if isinstance(self.session_store, SessionStore):
|
|
282
277
|
await self.session_store.close()
|
|
@@ -380,7 +375,7 @@ class App:
|
|
|
380
375
|
|
|
381
376
|
# Debug middleware to log CORS-related request details
|
|
382
377
|
# @self.fastapi.middleware("http")
|
|
383
|
-
# async def cors_debug_middleware(
|
|
378
|
+
# async def cors_debug_middleware(
|
|
384
379
|
# request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
385
380
|
# ):
|
|
386
381
|
# origin = request.headers.get("origin")
|
|
@@ -483,7 +478,13 @@ class App:
|
|
|
483
478
|
)
|
|
484
479
|
cleanup = True
|
|
485
480
|
|
|
486
|
-
|
|
481
|
+
initial_result: PrerenderResult = {
|
|
482
|
+
"views": {},
|
|
483
|
+
"directives": {
|
|
484
|
+
"headers": {"X-Pulse-Render-Id": render_id},
|
|
485
|
+
"socketio": {"auth": {"render_id": render_id}, "headers": {}},
|
|
486
|
+
},
|
|
487
|
+
}
|
|
487
488
|
|
|
488
489
|
def _prerender_one(path: str):
|
|
489
490
|
captured = render.prerender_mount_capture(path, route_info)
|
|
@@ -499,10 +500,12 @@ class App:
|
|
|
499
500
|
# Fallback: shouldn't happen, return not found to be safe
|
|
500
501
|
return NotFound()
|
|
501
502
|
|
|
503
|
+
result = initial_result.copy()
|
|
504
|
+
|
|
502
505
|
with PulseContext.update(render=render):
|
|
503
506
|
for p in paths:
|
|
504
507
|
try:
|
|
505
|
-
res = self.middleware.
|
|
508
|
+
res = self.middleware.prerender_route(
|
|
506
509
|
path=p,
|
|
507
510
|
route_info=route_info,
|
|
508
511
|
request=PulseRequest.from_fastapi(request),
|
|
@@ -545,7 +548,19 @@ class App:
|
|
|
545
548
|
if cleanup:
|
|
546
549
|
later(float(ttl), _gc_if_unadopted, render_id)
|
|
547
550
|
|
|
548
|
-
|
|
551
|
+
# Call top-level batch prerender middleware to modify final result
|
|
552
|
+
def _return_result() -> PrerenderResult:
|
|
553
|
+
return result
|
|
554
|
+
|
|
555
|
+
final_result = self.middleware.prerender(
|
|
556
|
+
payload=payload,
|
|
557
|
+
result=result,
|
|
558
|
+
request=PulseRequest.from_fastapi(request),
|
|
559
|
+
session=session.data,
|
|
560
|
+
next=_return_result,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
resp = JSONResponse(serialize(final_result))
|
|
549
564
|
session.handle_response(resp)
|
|
550
565
|
return resp
|
|
551
566
|
|
|
@@ -572,7 +587,7 @@ class App:
|
|
|
572
587
|
sid: str, environ: dict[str, Any], auth: dict[str, str] | None
|
|
573
588
|
):
|
|
574
589
|
# Expect renderId during websocket auth and require a valid user session
|
|
575
|
-
rid = auth.get("
|
|
590
|
+
rid = auth.get("render_id") if auth else None
|
|
576
591
|
|
|
577
592
|
# Parse cookies from environ and ensure a session exists
|
|
578
593
|
cookie = self.cookie.get_from_socketio(environ)
|
|
@@ -825,6 +840,29 @@ class App:
|
|
|
825
840
|
if len(self._user_to_render[sid]) == 0:
|
|
826
841
|
self.close_session(sid)
|
|
827
842
|
|
|
843
|
+
async def close(self):
|
|
844
|
+
"""
|
|
845
|
+
Close the app and clean up all sessions.
|
|
846
|
+
This method is called automatically during shutdown.
|
|
847
|
+
"""
|
|
848
|
+
|
|
849
|
+
# Close all render sessions
|
|
850
|
+
for rid in list(self.render_sessions.keys()):
|
|
851
|
+
self.close_render(rid)
|
|
852
|
+
|
|
853
|
+
# Close all user sessions
|
|
854
|
+
for sid in list(self.user_sessions.keys()):
|
|
855
|
+
self.close_session(sid)
|
|
856
|
+
|
|
857
|
+
# Update status
|
|
858
|
+
self.status = AppStatus.stopped
|
|
859
|
+
# Call plugin on_shutdown hooks before closing
|
|
860
|
+
for plugin in self.plugins:
|
|
861
|
+
try:
|
|
862
|
+
plugin.on_shutdown(self)
|
|
863
|
+
except Exception:
|
|
864
|
+
logger.exception("Error during plugin.on_shutdown()")
|
|
865
|
+
|
|
828
866
|
def refresh_cookies(self, sid: str):
|
|
829
867
|
# If the session is currently inside an HTTP request, we don't need to schedule
|
|
830
868
|
# set-cookies via WS; cookies will be attached on the HTTP response.
|
|
@@ -416,18 +416,25 @@ def build_web_command(
|
|
|
416
416
|
port: int | None = None,
|
|
417
417
|
mode: PulseEnv = "dev",
|
|
418
418
|
) -> CommandSpec:
|
|
419
|
+
command_env = os.environ.copy()
|
|
419
420
|
if mode == "prod":
|
|
420
421
|
# Production: use built server
|
|
421
422
|
args = ["bun", "run", "start"]
|
|
422
423
|
else:
|
|
423
424
|
# Development: use dev server
|
|
424
425
|
args = ["bun", "run", "dev"]
|
|
426
|
+
|
|
425
427
|
if port is not None:
|
|
426
|
-
|
|
428
|
+
if mode == "prod":
|
|
429
|
+
# react-router-serve uses PORT environment variable
|
|
430
|
+
# Don't add --port flag for production
|
|
431
|
+
command_env["PORT"] = str(port)
|
|
432
|
+
else:
|
|
433
|
+
# react-router dev accepts --port flag
|
|
434
|
+
args.extend(["--port", str(port)])
|
|
427
435
|
if extra_args:
|
|
428
436
|
args.extend(extra_args)
|
|
429
437
|
|
|
430
|
-
command_env = os.environ.copy()
|
|
431
438
|
command_env.update(
|
|
432
439
|
{
|
|
433
440
|
"FORCE_COLOR": "1",
|
|
@@ -54,9 +54,19 @@ export async function clientLoader(args: ClientLoaderFunctionArgs) {
|
|
|
54
54
|
typeof window !== "undefined" && typeof sessionStorage !== "undefined"
|
|
55
55
|
? (sessionStorage.getItem("__PULSE_RENDER_ID") ?? undefined)
|
|
56
56
|
: undefined;
|
|
57
|
+
const directives =
|
|
58
|
+
typeof window !== "undefined" && typeof sessionStorage !== "undefined"
|
|
59
|
+
? (JSON.parse(sessionStorage.getItem("__PULSE_DIRECTIVES") ?? "{}"))
|
|
60
|
+
: {};
|
|
61
|
+
const headers: HeadersInit = { "content-type": "application/json" };
|
|
62
|
+
if (directives?.headers) {
|
|
63
|
+
for (const [key, value] of Object.entries(directives.headers)) {
|
|
64
|
+
headers[key] = value as string;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
57
67
|
const res = await fetch(`$${"{"}config.serverAddress}$${"{"}config.apiPrefix}/prerender`, {
|
|
58
68
|
method: "POST",
|
|
59
|
-
headers
|
|
69
|
+
headers,
|
|
60
70
|
credentials: "include",
|
|
61
71
|
body: JSON.stringify({ paths, routeInfo: extractServerRouteInfo(args), renderId }),
|
|
62
72
|
});
|
|
@@ -64,13 +74,20 @@ export async function clientLoader(args: ClientLoaderFunctionArgs) {
|
|
|
64
74
|
const body = await res.json();
|
|
65
75
|
if (body.redirect) return new Response(null, { status: 302, headers: { Location: body.redirect } });
|
|
66
76
|
if (body.notFound) return new Response(null, { status: 404 });
|
|
67
|
-
|
|
77
|
+
const prerenderData = deserialize(body) as PulsePrerender;
|
|
78
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined" && prerenderData.directives) {
|
|
79
|
+
sessionStorage.setItem("__PULSE_DIRECTIVES", JSON.stringify(prerenderData.directives));
|
|
80
|
+
}
|
|
81
|
+
return prerenderData as PulsePrerender;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
export default function PulseLayout() {
|
|
71
85
|
const data = useLoaderData<typeof loader>();
|
|
72
86
|
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
73
87
|
sessionStorage.setItem("__PULSE_RENDER_ID", data.renderId);
|
|
88
|
+
if (data.directives) {
|
|
89
|
+
sessionStorage.setItem("__PULSE_DIRECTIVES", JSON.stringify(data.directives));
|
|
90
|
+
}
|
|
74
91
|
}
|
|
75
92
|
return (
|
|
76
93
|
<PulseProvider config={config} prerender={data}>
|
|
@@ -78,6 +95,6 @@ export default function PulseLayout() {
|
|
|
78
95
|
</PulseProvider>
|
|
79
96
|
);
|
|
80
97
|
}
|
|
81
|
-
// Persist renderId in sessionStorage for reuse in clientLoader is handled within the component
|
|
98
|
+
// Persist renderId and directives in sessionStorage for reuse in clientLoader is handled within the component
|
|
82
99
|
"""
|
|
83
100
|
)
|
|
@@ -156,3 +156,25 @@ ClientPulseMessage = (
|
|
|
156
156
|
)
|
|
157
157
|
ClientChannelMessage = ClientChannelRequestMessage | ClientChannelResponseMessage
|
|
158
158
|
ClientMessage = ClientPulseMessage | ClientChannelMessage
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class PrerenderPayload(TypedDict):
|
|
162
|
+
paths: list[str]
|
|
163
|
+
routeInfo: RouteInfo
|
|
164
|
+
ttlSeconds: NotRequired[float | int]
|
|
165
|
+
renderId: NotRequired[str]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class SocketIODirectives(TypedDict):
|
|
169
|
+
headers: dict[str, str]
|
|
170
|
+
auth: dict[str, str]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class Directives(TypedDict):
|
|
174
|
+
headers: dict[str, str]
|
|
175
|
+
socketio: SocketIODirectives
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class PrerenderResult(TypedDict):
|
|
179
|
+
views: dict[str, ServerInitMessage | None]
|
|
180
|
+
directives: Directives
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Sequence
|
|
4
|
-
from typing import Any, Generic, TypeVar, overload, override
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload, override
|
|
5
5
|
|
|
6
6
|
from pulse.messages import ClientMessage, ServerInitMessage
|
|
7
7
|
from pulse.request import PulseRequest
|
|
8
8
|
from pulse.routing import RouteInfo
|
|
9
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from pulse.app import PrerenderPayload, PrerenderResult
|
|
12
|
+
|
|
10
13
|
T = TypeVar("T")
|
|
11
14
|
|
|
12
15
|
|
|
@@ -46,6 +49,22 @@ class PulseMiddleware:
|
|
|
46
49
|
"""
|
|
47
50
|
|
|
48
51
|
def prerender(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
payload: "PrerenderPayload",
|
|
55
|
+
result: "PrerenderResult",
|
|
56
|
+
request: PulseRequest,
|
|
57
|
+
session: dict[str, Any],
|
|
58
|
+
next: Callable[[], "PrerenderResult"],
|
|
59
|
+
) -> "PrerenderResult":
|
|
60
|
+
"""Handle batch prerender at the top level.
|
|
61
|
+
|
|
62
|
+
Receives the full PrerenderPayload and can modify the PrerenderResult
|
|
63
|
+
(views and directives) before it's returned to the client.
|
|
64
|
+
"""
|
|
65
|
+
return next()
|
|
66
|
+
|
|
67
|
+
def prerender_route(
|
|
49
68
|
self,
|
|
50
69
|
*,
|
|
51
70
|
path: str,
|
|
@@ -103,6 +122,34 @@ class MiddlewareStack(PulseMiddleware):
|
|
|
103
122
|
|
|
104
123
|
@override
|
|
105
124
|
def prerender(
|
|
125
|
+
self,
|
|
126
|
+
*,
|
|
127
|
+
payload: "PrerenderPayload",
|
|
128
|
+
result: "PrerenderResult",
|
|
129
|
+
request: PulseRequest,
|
|
130
|
+
session: dict[str, Any],
|
|
131
|
+
next: Callable[[], "PrerenderResult"],
|
|
132
|
+
) -> "PrerenderResult":
|
|
133
|
+
def dispatch(index: int) -> "PrerenderResult":
|
|
134
|
+
if index >= len(self._middlewares):
|
|
135
|
+
return next()
|
|
136
|
+
mw = self._middlewares[index]
|
|
137
|
+
|
|
138
|
+
def _next() -> "PrerenderResult":
|
|
139
|
+
return dispatch(index + 1)
|
|
140
|
+
|
|
141
|
+
return mw.prerender(
|
|
142
|
+
payload=payload,
|
|
143
|
+
result=result,
|
|
144
|
+
request=request,
|
|
145
|
+
session=session,
|
|
146
|
+
next=_next,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return dispatch(0)
|
|
150
|
+
|
|
151
|
+
@override
|
|
152
|
+
def prerender_route(
|
|
106
153
|
self,
|
|
107
154
|
*,
|
|
108
155
|
path: str,
|
|
@@ -119,7 +166,7 @@ class MiddlewareStack(PulseMiddleware):
|
|
|
119
166
|
def _next() -> PrerenderResponse:
|
|
120
167
|
return dispatch(index + 1)
|
|
121
168
|
|
|
122
|
-
return mw.
|
|
169
|
+
return mw.prerender_route(
|
|
123
170
|
path=path,
|
|
124
171
|
route_info=route_info,
|
|
125
172
|
request=request,
|
|
@@ -216,6 +263,20 @@ class PulseCoreMiddleware(PulseMiddleware):
|
|
|
216
263
|
run, and finally returns their response unchanged.
|
|
217
264
|
"""
|
|
218
265
|
|
|
266
|
+
@override
|
|
267
|
+
def prerender(
|
|
268
|
+
self,
|
|
269
|
+
*,
|
|
270
|
+
payload: "PrerenderPayload",
|
|
271
|
+
result: "PrerenderResult",
|
|
272
|
+
request: PulseRequest,
|
|
273
|
+
session: dict[str, Any],
|
|
274
|
+
next: Callable[[], "PrerenderResult"],
|
|
275
|
+
) -> "PrerenderResult":
|
|
276
|
+
res = next()
|
|
277
|
+
# Return the result as-is (no normalization needed)
|
|
278
|
+
return res
|
|
279
|
+
|
|
219
280
|
# --- Normalization helpers -------------------------------------------------
|
|
220
281
|
def _normalize_prerender_response(self, res: Any) -> PrerenderResponse:
|
|
221
282
|
if isinstance(res, (Ok, Redirect, NotFound)):
|
|
@@ -236,7 +297,7 @@ class PulseCoreMiddleware(PulseMiddleware):
|
|
|
236
297
|
return Ok(None)
|
|
237
298
|
|
|
238
299
|
@override
|
|
239
|
-
def
|
|
300
|
+
def prerender_route(
|
|
240
301
|
self,
|
|
241
302
|
*,
|
|
242
303
|
path: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/templates/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulse_framework-0.1.38a4 → pulse_framework-0.1.38a6}/src/pulse/codegen/templates/routes_ts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|