pulse-framework 0.1.65__tar.gz → 0.1.66a1__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.65 → pulse_framework-0.1.66a1}/PKG-INFO +1 -1
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/pyproject.toml +1 -1
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/app.py +73 -15
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/templates/layout.py +3 -8
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/render_session.py +74 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/README.md +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/_examples.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/channel.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/cmd.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/dependencies.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/folder_lock.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/helpers.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/logging.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/models.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/packages.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/processes.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/secrets.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cli/uvicorn_log_config.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/code_analysis.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/codegen.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/templates/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/templates/route.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/templates/routes_ts.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/codegen/utils.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/component.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/components/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/components/for_.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/components/if_.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/components/react_router.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/context.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/cookies.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/decorators.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/elements.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/events.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/props.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/svg.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/tags.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/dom/tags.pyi +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/env.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/forms.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/helpers.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/core.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/effects.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/init.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/runtime.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/setup.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/stable.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/hooks/state.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/__init__.pyi +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/_types.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/array.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/console.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/date.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/document.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/error.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/json.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/map.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/math.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/navigator.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/number.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/obj.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/object.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/promise.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/pulse.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/react.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/regexp.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/set.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/string.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/weakmap.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/weakset.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/js/window.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/messages.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/middleware.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/plugin.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/proxy.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/py.typed +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/client.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/common.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/effect.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/infinite_query.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/mutation.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/protocol.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/query.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/queries/store.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/react_component.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/reactive.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/reactive_extensions.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/renderer.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/request.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/requirements.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/routing.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/scheduling.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/serializer.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/state.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/test_helpers.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/assets.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/builtins.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/dynamic_import.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/emit_context.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/errors.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/function.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/id.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/imports.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/js_module.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/asyncio.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/json.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/math.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/pulse/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/pulse/tags.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/typing.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/nodes.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/py_module.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/transpiler.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/vdom.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/types/__init__.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/types/event_handler.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/user_session.py +0 -0
- {pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/version.py +0 -0
|
@@ -107,8 +107,8 @@ PulseMode = Literal["subdomains", "single-server"]
|
|
|
107
107
|
"""Deployment mode for the application.
|
|
108
108
|
|
|
109
109
|
Values:
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
"single-server": Python and React served from the same origin (default).
|
|
111
|
+
"subdomains": Python API on a subdomain (e.g., api.example.com).
|
|
112
112
|
"""
|
|
113
113
|
|
|
114
114
|
|
|
@@ -118,12 +118,12 @@ class ConnectionStatusConfig:
|
|
|
118
118
|
Configuration for connection status message delays.
|
|
119
119
|
|
|
120
120
|
Attributes:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
initial_connecting_delay: Delay in seconds before showing "Connecting..." message
|
|
122
|
+
on initial connection attempt. Default: 2.0
|
|
123
|
+
initial_error_delay: Additional delay in seconds before showing error message
|
|
124
|
+
on initial connection attempt (after connecting message). Default: 8.0
|
|
125
|
+
reconnect_error_delay: Delay in seconds before showing error message when
|
|
126
|
+
reconnecting after losing connection. Default: 8.0
|
|
127
127
|
"""
|
|
128
128
|
|
|
129
129
|
initial_connecting_delay: float = 2.0
|
|
@@ -171,15 +171,15 @@ class App:
|
|
|
171
171
|
import pulse as ps
|
|
172
172
|
|
|
173
173
|
app = ps.App(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
routes=[
|
|
175
|
+
ps.Route("/", render=home),
|
|
176
|
+
ps.Route("/users/:id", render=user_detail),
|
|
177
|
+
],
|
|
178
|
+
session_timeout=120.0,
|
|
179
179
|
)
|
|
180
180
|
|
|
181
181
|
if __name__ == "__main__":
|
|
182
|
-
|
|
182
|
+
app.run(port=8000)
|
|
183
183
|
```
|
|
184
184
|
"""
|
|
185
185
|
|
|
@@ -544,10 +544,21 @@ class App:
|
|
|
544
544
|
paths = [ensure_absolute_path(path) for path in paths]
|
|
545
545
|
payload["paths"] = paths
|
|
546
546
|
route_info = payload.get("routeInfo")
|
|
547
|
+
debug = os.environ.get("PULSE_DEBUG_RENDER")
|
|
548
|
+
if debug:
|
|
549
|
+
logger.info(
|
|
550
|
+
"[PulseDebug][prerender] session=%s header_render_id=%s payload_render_id=%s paths=%s route_info=%s",
|
|
551
|
+
session.sid,
|
|
552
|
+
request.headers.get("x-pulse-render-id"),
|
|
553
|
+
payload.get("renderId"),
|
|
554
|
+
paths,
|
|
555
|
+
route_info,
|
|
556
|
+
)
|
|
547
557
|
|
|
548
558
|
client_addr: str | None = get_client_address(request)
|
|
549
559
|
# Reuse render session from header (set by middleware) or create new one
|
|
550
560
|
render = PulseContext.get().render
|
|
561
|
+
reused = render is not None
|
|
551
562
|
if render is not None:
|
|
552
563
|
render_id = render.id
|
|
553
564
|
else:
|
|
@@ -556,9 +567,19 @@ class App:
|
|
|
556
567
|
render = self.create_render(
|
|
557
568
|
render_id, session, client_address=client_addr
|
|
558
569
|
)
|
|
570
|
+
if debug:
|
|
571
|
+
logger.info(
|
|
572
|
+
"[PulseDebug][prerender] session=%s render=%s reused=%s connected=%s",
|
|
573
|
+
session.sid,
|
|
574
|
+
render_id,
|
|
575
|
+
reused,
|
|
576
|
+
render.connected,
|
|
577
|
+
)
|
|
578
|
+
print(f"Prerendering for RenderSession {render_id}")
|
|
559
579
|
|
|
560
580
|
# Schedule cleanup timeout (will cancel/reschedule on activity)
|
|
561
|
-
|
|
581
|
+
if not render.connected:
|
|
582
|
+
self._schedule_render_cleanup(render_id)
|
|
562
583
|
|
|
563
584
|
def _normalize_prerender_result(
|
|
564
585
|
captured: ServerInitMessage | ServerNavigateToMessage,
|
|
@@ -698,6 +719,13 @@ class App:
|
|
|
698
719
|
if cookie is None:
|
|
699
720
|
raise ConnectionRefusedError("Socket connect missing cookie")
|
|
700
721
|
session = await self.get_or_create_session(cookie)
|
|
722
|
+
debug = os.environ.get("PULSE_DEBUG_RENDER")
|
|
723
|
+
if debug:
|
|
724
|
+
logger.info(
|
|
725
|
+
"[PulseDebug][connect] session=%s render_id=%s",
|
|
726
|
+
session.sid,
|
|
727
|
+
rid,
|
|
728
|
+
)
|
|
701
729
|
|
|
702
730
|
if not rid:
|
|
703
731
|
# Still refuse connections without a renderId
|
|
@@ -708,6 +736,13 @@ class App:
|
|
|
708
736
|
# Allow reconnects where the provided renderId no longer exists by creating a new RenderSession
|
|
709
737
|
render = self.render_sessions.get(rid)
|
|
710
738
|
if render is None:
|
|
739
|
+
# The client will try to attach to a non-existing RouteMount, which will cause a reload down the line
|
|
740
|
+
if debug:
|
|
741
|
+
logger.info(
|
|
742
|
+
"[PulseDebug][connect] render_missing session=%s render_id=%s creating=true",
|
|
743
|
+
session.sid,
|
|
744
|
+
rid,
|
|
745
|
+
)
|
|
711
746
|
render = self.create_render(
|
|
712
747
|
rid, session, client_address=get_client_address_socketio(environ)
|
|
713
748
|
)
|
|
@@ -718,6 +753,15 @@ class App:
|
|
|
718
753
|
f"Socket connect session mismatch render={render.id} "
|
|
719
754
|
+ f"owner={owner} session={session.sid}"
|
|
720
755
|
)
|
|
756
|
+
if debug:
|
|
757
|
+
logger.info(
|
|
758
|
+
"[PulseDebug][connect] render_found session=%s render_id=%s owner=%s connected=%s",
|
|
759
|
+
session.sid,
|
|
760
|
+
render.id,
|
|
761
|
+
owner,
|
|
762
|
+
render.connected,
|
|
763
|
+
)
|
|
764
|
+
print(f"Connected to RenderSession {render.id}")
|
|
721
765
|
|
|
722
766
|
def on_message(message: ServerMessage):
|
|
723
767
|
payload = serialize(message)
|
|
@@ -830,6 +874,20 @@ class App:
|
|
|
830
874
|
async def _handle_pulse_message(
|
|
831
875
|
self, render: RenderSession, session: UserSession, msg: ClientPulseMessage
|
|
832
876
|
) -> None:
|
|
877
|
+
if os.environ.get("PULSE_DEBUG_RENDER") and msg["type"] in (
|
|
878
|
+
"attach",
|
|
879
|
+
"update",
|
|
880
|
+
"detach",
|
|
881
|
+
):
|
|
882
|
+
logger.info(
|
|
883
|
+
"[PulseDebug][client-message] session=%s render=%s type=%s path=%s route_info=%s",
|
|
884
|
+
session.sid,
|
|
885
|
+
render.id,
|
|
886
|
+
msg["type"],
|
|
887
|
+
msg.get("path"),
|
|
888
|
+
msg.get("routeInfo"),
|
|
889
|
+
)
|
|
890
|
+
|
|
833
891
|
async def _next() -> Ok[None]:
|
|
834
892
|
if msg["type"] == "attach":
|
|
835
893
|
render.attach(msg["path"], msg["routeInfo"])
|
|
@@ -53,15 +53,11 @@ export async function loader(args: LoaderFunctionArgs) {
|
|
|
53
53
|
return data(prerenderData, { headers });
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Client loader: re-prerender on navigation while reusing
|
|
56
|
+
// Client loader: re-prerender on navigation while reusing directives
|
|
57
57
|
export async function clientLoader(args: ClientLoaderFunctionArgs) {
|
|
58
58
|
const url = new URL(args.request.url);
|
|
59
59
|
const matches = matchRoutes(rrPulseRouteTree, url.pathname) ?? [];
|
|
60
60
|
const paths = matches.map(m => m.route.uniquePath);
|
|
61
|
-
const renderId =
|
|
62
|
-
typeof window !== "undefined" && typeof sessionStorage !== "undefined"
|
|
63
|
-
? (sessionStorage.getItem("__PULSE_RENDER_ID") ?? undefined)
|
|
64
|
-
: undefined;
|
|
65
61
|
const directives =
|
|
66
62
|
typeof window !== "undefined" && typeof sessionStorage !== "undefined"
|
|
67
63
|
? (JSON.parse(sessionStorage.getItem("__PULSE_DIRECTIVES") ?? "{}"))
|
|
@@ -76,7 +72,7 @@ export async function clientLoader(args: ClientLoaderFunctionArgs) {
|
|
|
76
72
|
method: "POST",
|
|
77
73
|
headers,
|
|
78
74
|
credentials: "include",
|
|
79
|
-
body: JSON.stringify({ paths, routeInfo: extractServerRouteInfo(args)
|
|
75
|
+
body: JSON.stringify({ paths, routeInfo: extractServerRouteInfo(args) }),
|
|
80
76
|
});
|
|
81
77
|
if (!res.ok) throw new Error("Failed to prerender batch:" + res.status);
|
|
82
78
|
const body = await res.json();
|
|
@@ -92,7 +88,6 @@ export async function clientLoader(args: ClientLoaderFunctionArgs) {
|
|
|
92
88
|
export default function PulseLayout() {
|
|
93
89
|
const data = useLoaderData<typeof loader>();
|
|
94
90
|
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
95
|
-
sessionStorage.setItem("__PULSE_RENDER_ID", data.renderId);
|
|
96
91
|
sessionStorage.setItem("__PULSE_DIRECTIVES", JSON.stringify(data.directives));
|
|
97
92
|
}
|
|
98
93
|
return (
|
|
@@ -101,6 +96,6 @@ export default function PulseLayout() {
|
|
|
101
96
|
</PulseProvider>
|
|
102
97
|
);
|
|
103
98
|
}
|
|
104
|
-
// Persist
|
|
99
|
+
// Persist directives in sessionStorage for reuse in clientLoader is handled within the component
|
|
105
100
|
"""
|
|
106
101
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
3
4
|
import traceback
|
|
4
5
|
import uuid
|
|
5
6
|
from asyncio import iscoroutine
|
|
@@ -137,6 +138,14 @@ class RouteMount:
|
|
|
137
138
|
if self.state != "pending":
|
|
138
139
|
return
|
|
139
140
|
action = self.pending_action
|
|
141
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
142
|
+
logger.info(
|
|
143
|
+
"[PulseDebug][mount-timeout] render=%s path=%s action=%s state=%s",
|
|
144
|
+
self.render.id,
|
|
145
|
+
self.path,
|
|
146
|
+
action,
|
|
147
|
+
self.state,
|
|
148
|
+
)
|
|
140
149
|
self.pending_action = None
|
|
141
150
|
if action == "dispose":
|
|
142
151
|
self.render.dispose_mount(self.path, self)
|
|
@@ -144,6 +153,15 @@ class RouteMount:
|
|
|
144
153
|
self.to_idle()
|
|
145
154
|
|
|
146
155
|
def start_pending(self, timeout: float, *, action: PendingAction = "idle") -> None:
|
|
156
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
157
|
+
logger.info(
|
|
158
|
+
"[PulseDebug][mount-pending] render=%s path=%s state=%s action=%s timeout=%s",
|
|
159
|
+
self.render.id,
|
|
160
|
+
self.path,
|
|
161
|
+
self.state,
|
|
162
|
+
action,
|
|
163
|
+
timeout,
|
|
164
|
+
)
|
|
147
165
|
if self.state == "pending":
|
|
148
166
|
prev_action = self.pending_action
|
|
149
167
|
next_action: PendingAction = (
|
|
@@ -166,6 +184,14 @@ class RouteMount:
|
|
|
166
184
|
)
|
|
167
185
|
|
|
168
186
|
def activate(self, send_message: Callable[[ServerMessage], Any]) -> None:
|
|
187
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
188
|
+
logger.info(
|
|
189
|
+
"[PulseDebug][mount-activate] render=%s path=%s state=%s queued=%s",
|
|
190
|
+
self.render.id,
|
|
191
|
+
self.path,
|
|
192
|
+
self.state,
|
|
193
|
+
0 if not self.queue else len(self.queue),
|
|
194
|
+
)
|
|
169
195
|
if self.state != "pending":
|
|
170
196
|
return
|
|
171
197
|
self._cancel_pending_timeout()
|
|
@@ -192,6 +218,12 @@ class RouteMount:
|
|
|
192
218
|
def to_idle(self) -> None:
|
|
193
219
|
if self.state != "pending":
|
|
194
220
|
return
|
|
221
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
222
|
+
logger.info(
|
|
223
|
+
"[PulseDebug][mount-idle] render=%s path=%s",
|
|
224
|
+
self.render.id,
|
|
225
|
+
self.path,
|
|
226
|
+
)
|
|
195
227
|
self.state = "idle"
|
|
196
228
|
self.queue = None
|
|
197
229
|
self._cancel_pending_timeout()
|
|
@@ -317,11 +349,15 @@ class RenderSession:
|
|
|
317
349
|
|
|
318
350
|
def connect(self, send_message: Callable[[ServerMessage], Any]):
|
|
319
351
|
"""WebSocket connected. Set sender, don't auto-flush (attach does that)."""
|
|
352
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
353
|
+
logger.info("[PulseDebug][render-connect] render=%s", self.id)
|
|
320
354
|
self._send_message = send_message
|
|
321
355
|
self.connected = True
|
|
322
356
|
|
|
323
357
|
def disconnect(self):
|
|
324
358
|
"""WebSocket disconnected. Start queuing briefly before pausing."""
|
|
359
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
360
|
+
logger.info("[PulseDebug][render-disconnect] render=%s", self.id)
|
|
325
361
|
self._send_message = None
|
|
326
362
|
self.connected = False
|
|
327
363
|
|
|
@@ -395,6 +431,13 @@ class RenderSession:
|
|
|
395
431
|
- Creates mounts in PENDING state and starts queue
|
|
396
432
|
"""
|
|
397
433
|
normalized = [ensure_absolute_path(path) for path in paths]
|
|
434
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
435
|
+
logger.info(
|
|
436
|
+
"[PulseDebug][prerender] render=%s paths=%s route_info=%s",
|
|
437
|
+
self.id,
|
|
438
|
+
normalized,
|
|
439
|
+
route_info,
|
|
440
|
+
)
|
|
398
441
|
|
|
399
442
|
results: dict[str, ServerInitMessage | ServerNavigateToMessage] = {}
|
|
400
443
|
|
|
@@ -402,6 +445,15 @@ class RenderSession:
|
|
|
402
445
|
route = self.routes.find(path)
|
|
403
446
|
info = route_info or route.default_route_info()
|
|
404
447
|
mount = self.route_mounts.get(path)
|
|
448
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
449
|
+
route_label = repr(route)
|
|
450
|
+
logger.info(
|
|
451
|
+
"[PulseDebug][prerender] render=%s path=%s mount_state=%s route=%s",
|
|
452
|
+
self.id,
|
|
453
|
+
path,
|
|
454
|
+
mount.state if mount else None,
|
|
455
|
+
route_label,
|
|
456
|
+
)
|
|
405
457
|
|
|
406
458
|
if mount is None:
|
|
407
459
|
mount = RouteMount(self, path, route, info)
|
|
@@ -440,6 +492,15 @@ class RenderSession:
|
|
|
440
492
|
mount = self.route_mounts.get(path)
|
|
441
493
|
|
|
442
494
|
if mount is None or mount.state == "idle":
|
|
495
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
496
|
+
logger.info(
|
|
497
|
+
"[PulseDebug][attach] render=%s path=%s mount_state=%s mounts=%s route_info=%s",
|
|
498
|
+
self.id,
|
|
499
|
+
path,
|
|
500
|
+
mount.state if mount else None,
|
|
501
|
+
{key: value.state for key, value in self.route_mounts.items()},
|
|
502
|
+
route_info,
|
|
503
|
+
)
|
|
443
504
|
# Initial render must come from prerender
|
|
444
505
|
print(f"[DEBUG] Missing or idle route '{path}', reloading")
|
|
445
506
|
self.send({"type": "reload"})
|
|
@@ -448,6 +509,12 @@ class RenderSession:
|
|
|
448
509
|
# Update route info for active and pending mounts
|
|
449
510
|
mount.update_route(route_info)
|
|
450
511
|
if mount.state == "pending" and self._send_message:
|
|
512
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
513
|
+
logger.info(
|
|
514
|
+
"[PulseDebug][attach] render=%s path=%s activating=true",
|
|
515
|
+
self.id,
|
|
516
|
+
path,
|
|
517
|
+
)
|
|
451
518
|
mount.activate(self._send_message)
|
|
452
519
|
|
|
453
520
|
def update_route(self, path: str, route_info: RouteInfo):
|
|
@@ -463,6 +530,13 @@ class RenderSession:
|
|
|
463
530
|
current = self.route_mounts.get(path)
|
|
464
531
|
if current is not mount:
|
|
465
532
|
return
|
|
533
|
+
if os.environ.get("PULSE_DEBUG_RENDER"):
|
|
534
|
+
logger.info(
|
|
535
|
+
"[PulseDebug][mount-dispose] render=%s path=%s state=%s",
|
|
536
|
+
self.id,
|
|
537
|
+
path,
|
|
538
|
+
mount.state,
|
|
539
|
+
)
|
|
466
540
|
try:
|
|
467
541
|
self.route_mounts.pop(path, None)
|
|
468
542
|
mount.dispose()
|
|
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
|
{pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/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
|
|
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
|
{pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/pulse/__init__.py
RENAMED
|
File without changes
|
{pulse_framework-0.1.65 → pulse_framework-0.1.66a1}/src/pulse/transpiler/modules/pulse/tags.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
|