pulse-framework 0.1.62__py3-none-any.whl
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/__init__.py +1493 -0
- pulse/_examples.py +29 -0
- pulse/app.py +1086 -0
- pulse/channel.py +607 -0
- pulse/cli/__init__.py +0 -0
- pulse/cli/cmd.py +575 -0
- pulse/cli/dependencies.py +181 -0
- pulse/cli/folder_lock.py +134 -0
- pulse/cli/helpers.py +271 -0
- pulse/cli/logging.py +102 -0
- pulse/cli/models.py +35 -0
- pulse/cli/packages.py +262 -0
- pulse/cli/processes.py +292 -0
- pulse/cli/secrets.py +39 -0
- pulse/cli/uvicorn_log_config.py +87 -0
- pulse/code_analysis.py +38 -0
- pulse/codegen/__init__.py +0 -0
- pulse/codegen/codegen.py +359 -0
- pulse/codegen/templates/__init__.py +0 -0
- pulse/codegen/templates/layout.py +106 -0
- pulse/codegen/templates/route.py +345 -0
- pulse/codegen/templates/routes_ts.py +42 -0
- pulse/codegen/utils.py +20 -0
- pulse/component.py +237 -0
- pulse/components/__init__.py +0 -0
- pulse/components/for_.py +83 -0
- pulse/components/if_.py +86 -0
- pulse/components/react_router.py +94 -0
- pulse/context.py +108 -0
- pulse/cookies.py +322 -0
- pulse/decorators.py +344 -0
- pulse/dom/__init__.py +0 -0
- pulse/dom/elements.py +1024 -0
- pulse/dom/events.py +445 -0
- pulse/dom/props.py +1250 -0
- pulse/dom/svg.py +0 -0
- pulse/dom/tags.py +328 -0
- pulse/dom/tags.pyi +480 -0
- pulse/env.py +178 -0
- pulse/form.py +538 -0
- pulse/helpers.py +541 -0
- pulse/hooks/__init__.py +0 -0
- pulse/hooks/core.py +452 -0
- pulse/hooks/effects.py +88 -0
- pulse/hooks/init.py +668 -0
- pulse/hooks/runtime.py +464 -0
- pulse/hooks/setup.py +254 -0
- pulse/hooks/stable.py +138 -0
- pulse/hooks/state.py +192 -0
- pulse/js/__init__.py +125 -0
- pulse/js/__init__.pyi +115 -0
- pulse/js/_types.py +299 -0
- pulse/js/array.py +339 -0
- pulse/js/console.py +50 -0
- pulse/js/date.py +119 -0
- pulse/js/document.py +145 -0
- pulse/js/error.py +140 -0
- pulse/js/json.py +66 -0
- pulse/js/map.py +97 -0
- pulse/js/math.py +69 -0
- pulse/js/navigator.py +79 -0
- pulse/js/number.py +57 -0
- pulse/js/obj.py +81 -0
- pulse/js/object.py +172 -0
- pulse/js/promise.py +172 -0
- pulse/js/pulse.py +115 -0
- pulse/js/react.py +495 -0
- pulse/js/regexp.py +57 -0
- pulse/js/set.py +124 -0
- pulse/js/string.py +38 -0
- pulse/js/weakmap.py +53 -0
- pulse/js/weakset.py +48 -0
- pulse/js/window.py +205 -0
- pulse/messages.py +202 -0
- pulse/middleware.py +471 -0
- pulse/plugin.py +96 -0
- pulse/proxy.py +242 -0
- pulse/py.typed +0 -0
- pulse/queries/__init__.py +0 -0
- pulse/queries/client.py +609 -0
- pulse/queries/common.py +101 -0
- pulse/queries/effect.py +55 -0
- pulse/queries/infinite_query.py +1418 -0
- pulse/queries/mutation.py +295 -0
- pulse/queries/protocol.py +136 -0
- pulse/queries/query.py +1314 -0
- pulse/queries/store.py +120 -0
- pulse/react_component.py +88 -0
- pulse/reactive.py +1208 -0
- pulse/reactive_extensions.py +1172 -0
- pulse/render_session.py +768 -0
- pulse/renderer.py +584 -0
- pulse/request.py +205 -0
- pulse/routing.py +598 -0
- pulse/serializer.py +279 -0
- pulse/state.py +556 -0
- pulse/test_helpers.py +15 -0
- pulse/transpiler/__init__.py +111 -0
- pulse/transpiler/assets.py +81 -0
- pulse/transpiler/builtins.py +1029 -0
- pulse/transpiler/dynamic_import.py +130 -0
- pulse/transpiler/emit_context.py +49 -0
- pulse/transpiler/errors.py +96 -0
- pulse/transpiler/function.py +611 -0
- pulse/transpiler/id.py +18 -0
- pulse/transpiler/imports.py +341 -0
- pulse/transpiler/js_module.py +336 -0
- pulse/transpiler/modules/__init__.py +33 -0
- pulse/transpiler/modules/asyncio.py +57 -0
- pulse/transpiler/modules/json.py +24 -0
- pulse/transpiler/modules/math.py +265 -0
- pulse/transpiler/modules/pulse/__init__.py +5 -0
- pulse/transpiler/modules/pulse/tags.py +250 -0
- pulse/transpiler/modules/typing.py +63 -0
- pulse/transpiler/nodes.py +1987 -0
- pulse/transpiler/py_module.py +135 -0
- pulse/transpiler/transpiler.py +1100 -0
- pulse/transpiler/vdom.py +256 -0
- pulse/types/__init__.py +0 -0
- pulse/types/event_handler.py +50 -0
- pulse/user_session.py +386 -0
- pulse/version.py +69 -0
- pulse_framework-0.1.62.dist-info/METADATA +198 -0
- pulse_framework-0.1.62.dist-info/RECORD +126 -0
- pulse_framework-0.1.62.dist-info/WHEEL +4 -0
- pulse_framework-0.1.62.dist-info/entry_points.txt +3 -0
pulse/js/number.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JavaScript Number builtin module.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pulse.js import Number
|
|
8
|
+
Number.isFinite(42) # -> Number.isFinite(42)
|
|
9
|
+
Number.MAX_SAFE_INTEGER # -> Number.MAX_SAFE_INTEGER
|
|
10
|
+
Number(x) # -> new Number(x)
|
|
11
|
+
|
|
12
|
+
# Or import from module directly:
|
|
13
|
+
from pulse.js.number import Number
|
|
14
|
+
```
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Any as _Any
|
|
18
|
+
from typing import ClassVar as _ClassVar
|
|
19
|
+
|
|
20
|
+
from pulse.transpiler.js_module import JsModule
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Number:
|
|
24
|
+
"""JavaScript Number constructor and namespace."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, value: _Any) -> None: ...
|
|
27
|
+
|
|
28
|
+
EPSILON: _ClassVar[float]
|
|
29
|
+
MAX_SAFE_INTEGER: _ClassVar[int]
|
|
30
|
+
MAX_VALUE: _ClassVar[float]
|
|
31
|
+
MIN_SAFE_INTEGER: _ClassVar[int]
|
|
32
|
+
MIN_VALUE: _ClassVar[float]
|
|
33
|
+
NaN: _ClassVar[float]
|
|
34
|
+
NEGATIVE_INFINITY: _ClassVar[float]
|
|
35
|
+
POSITIVE_INFINITY: _ClassVar[float]
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def isFinite(value: float) -> bool: ...
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def isInteger(value: float) -> bool: ...
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def isNaN(value: float) -> bool: ...
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def isSafeInteger(value: float) -> bool: ...
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def parseFloat(string: str) -> float: ...
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def parseInt(string: str, radix: int = 10, /) -> int: ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Self-register this module as a JS builtin (global identifier)
|
|
57
|
+
JsModule.register(name=None)
|
pulse/js/obj.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JavaScript object literal creation.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pulse.js import obj
|
|
8
|
+
|
|
9
|
+
# Create plain JS objects (not Maps):
|
|
10
|
+
obj(a=1, b=2) # -> { a: 1, b: 2 }
|
|
11
|
+
|
|
12
|
+
# With spread syntax:
|
|
13
|
+
obj(**base, c=3) # -> { ...base, c: 3 }
|
|
14
|
+
obj(a=1, **base) # -> { a: 1, ...base }
|
|
15
|
+
|
|
16
|
+
# Empty object:
|
|
17
|
+
obj() # -> {}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Unlike `dict()` which transpiles to `new Map()`, `obj()` creates plain JavaScript
|
|
21
|
+
object literals. Use this for React props, style objects, and anywhere you
|
|
22
|
+
need a plain JS object.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import ast
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import TYPE_CHECKING, override
|
|
30
|
+
|
|
31
|
+
from pulse.transpiler.errors import TranspileError
|
|
32
|
+
from pulse.transpiler.nodes import Expr, Object, Spread, spread_dict
|
|
33
|
+
|
|
34
|
+
# TYPE_CHECKING avoids import cycle: Transpiler -> nodes -> Expr -> obj -> Transpiler
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(slots=True)
|
|
40
|
+
class ObjTransformer(Expr):
|
|
41
|
+
"""Transformer for `obj()` with `**spread` support.
|
|
42
|
+
|
|
43
|
+
- `obj(key=value, ...)` -> `{ key: value, ... }`
|
|
44
|
+
- `obj(**base, key=value)` -> `{ ...base, key: value }`
|
|
45
|
+
|
|
46
|
+
Creates a plain JavaScript object literal.
|
|
47
|
+
Use this instead of `dict()` when you need a plain object (e.g., for React props).
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
def emit(self, out: list[str]) -> None:
|
|
52
|
+
raise TypeError("obj cannot be emitted directly - must be called")
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def render(self):
|
|
56
|
+
raise TypeError("obj cannot be rendered - must be called")
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def transpile_call(
|
|
60
|
+
self,
|
|
61
|
+
args: list[ast.expr],
|
|
62
|
+
keywords: list[ast.keyword],
|
|
63
|
+
ctx: Transpiler,
|
|
64
|
+
) -> Expr:
|
|
65
|
+
if args:
|
|
66
|
+
raise TranspileError("obj() only accepts keyword arguments")
|
|
67
|
+
|
|
68
|
+
props: list[tuple[str, Expr] | Spread] = []
|
|
69
|
+
for kw in keywords:
|
|
70
|
+
if kw.arg is None:
|
|
71
|
+
# **spread syntax
|
|
72
|
+
props.append(spread_dict(ctx.emit_expr(kw.value)))
|
|
73
|
+
else:
|
|
74
|
+
# key=value
|
|
75
|
+
props.append((kw.arg, ctx.emit_expr(kw.value)))
|
|
76
|
+
|
|
77
|
+
return Object(props)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Create singleton instance for use as a callable
|
|
81
|
+
obj = ObjTransformer()
|
pulse/js/object.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JavaScript Object builtin module.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pulse.js import Object
|
|
8
|
+
Object.keys({"a": 1}) # -> Object.keys({"a": 1})
|
|
9
|
+
Object.assign({}, {"a": 1}) # -> Object.assign({}, {"a": 1})
|
|
10
|
+
Object.is_(x, y) # -> Object.is(x, y)
|
|
11
|
+
|
|
12
|
+
# Or import from module directly:
|
|
13
|
+
from pulse.js.object import Object
|
|
14
|
+
```
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from collections.abc import Iterable as _Iterable
|
|
18
|
+
from typing import Any as _Any
|
|
19
|
+
from typing import TypedDict as _TypedDict
|
|
20
|
+
from typing import TypeVar as _TypeVar
|
|
21
|
+
|
|
22
|
+
from pulse.transpiler.js_module import JsModule
|
|
23
|
+
|
|
24
|
+
T = _TypeVar("T")
|
|
25
|
+
K = _TypeVar("K", bound=str) # Object keys are always strings in JS
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Property descriptor type - JS uses a plain object with specific keys
|
|
29
|
+
class PropertyDescriptor(_TypedDict, total=False):
|
|
30
|
+
"""Type for Object.defineProperty descriptor."""
|
|
31
|
+
|
|
32
|
+
value: _Any
|
|
33
|
+
writable: bool
|
|
34
|
+
get: _Any # Callable[[], T] but we use _Any for flexibility
|
|
35
|
+
set: _Any # Callable[[T], None]
|
|
36
|
+
configurable: bool
|
|
37
|
+
enumerable: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Object:
|
|
41
|
+
"""JavaScript Object namespace - static methods for object manipulation.
|
|
42
|
+
|
|
43
|
+
Note: Object is primarily used as a namespace for static methods.
|
|
44
|
+
The types here are as precise as JavaScript's dynamic nature allows.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def assign(target: T, *sources: _Any) -> T:
|
|
49
|
+
"""Copy properties from sources to target. Returns target."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def create(
|
|
54
|
+
proto: _Any | None,
|
|
55
|
+
propertiesObject: dict[str, PropertyDescriptor] | None = None,
|
|
56
|
+
/,
|
|
57
|
+
) -> _Any:
|
|
58
|
+
"""Create a new object with the specified prototype."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def defineProperty(
|
|
63
|
+
obj: T, prop: str, descriptor: PropertyDescriptor | dict[str, _Any]
|
|
64
|
+
) -> T:
|
|
65
|
+
"""Define a property on an object. Returns the object."""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def defineProperties(
|
|
70
|
+
obj: T, props: dict[str, PropertyDescriptor | dict[str, _Any]]
|
|
71
|
+
) -> T:
|
|
72
|
+
"""Define multiple properties on an object. Returns the object."""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def entries(obj: dict[str, T]) -> list[tuple[str, T]]:
|
|
77
|
+
"""Return an array of [key, value] pairs."""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def freeze(obj: T) -> T:
|
|
82
|
+
"""Freeze an object (prevent modifications). Returns the object."""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def fromEntries(entries: _Iterable[tuple[str, T]]) -> dict[str, T]:
|
|
87
|
+
"""Create an object from an iterable of [key, value] pairs."""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def getOwnPropertyDescriptor(obj: _Any, prop: str) -> PropertyDescriptor | None:
|
|
92
|
+
"""Return the property descriptor for a property."""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def getOwnPropertyDescriptors(obj: _Any) -> dict[str, PropertyDescriptor]:
|
|
97
|
+
"""Return all own property descriptors."""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def getOwnPropertyNames(obj: _Any) -> list[str]:
|
|
102
|
+
"""Return all own property names (including non-enumerable)."""
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def getOwnPropertySymbols(obj: _Any) -> list[_Any]:
|
|
107
|
+
"""Return all own Symbol properties."""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def getPrototypeOf(obj: _Any) -> _Any | None:
|
|
112
|
+
"""Return the prototype of an object."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def hasOwn(obj: _Any, prop: str) -> bool:
|
|
117
|
+
"""Return True if the object has the specified own property."""
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def is_(value1: _Any, value2: _Any) -> bool:
|
|
122
|
+
"""Determine if two values are the same value (SameValue algorithm)."""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def isExtensible(obj: _Any) -> bool:
|
|
127
|
+
"""Return True if the object is extensible."""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def isFrozen(obj: _Any) -> bool:
|
|
132
|
+
"""Return True if the object is frozen."""
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def isSealed(obj: _Any) -> bool:
|
|
137
|
+
"""Return True if the object is sealed."""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def keys(obj: dict[str, _Any]) -> list[str]:
|
|
142
|
+
"""Return an array of enumerable property names."""
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def preventExtensions(obj: T) -> T:
|
|
147
|
+
"""Prevent new properties from being added. Returns the object."""
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def seal(obj: T) -> T:
|
|
152
|
+
"""Seal an object (prevent adding/removing properties). Returns the object."""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def setPrototypeOf(obj: T, prototype: _Any | None) -> T:
|
|
157
|
+
"""Set the prototype of an object. Returns the object."""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def values(obj: dict[str, T]) -> list[T]:
|
|
162
|
+
"""Return an array of enumerable property values."""
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def groupBy(items: _Iterable[T], keyFn: _Any) -> dict[str, list[T]]:
|
|
167
|
+
"""Group items by key function result (ES2024)."""
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Self-register this module as a JS builtin (global identifier)
|
|
172
|
+
JsModule.register(name=None)
|
pulse/js/promise.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JavaScript Promise builtin module.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pulse.js import Promise
|
|
8
|
+
Promise(executor) # -> new Promise(executor)
|
|
9
|
+
Promise.resolve(value) # -> Promise.resolve(value)
|
|
10
|
+
Promise.reject(reason) # -> Promise.reject(reason)
|
|
11
|
+
|
|
12
|
+
# Or import from module directly:
|
|
13
|
+
from pulse.js.promise import Promise
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The `Promise` class is generic and supports async/await via the Awaitable protocol.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from collections.abc import Callable as _Callable
|
|
20
|
+
from collections.abc import Generator as _Generator
|
|
21
|
+
from collections.abc import Iterable as _Iterable
|
|
22
|
+
from typing import Any as _Any
|
|
23
|
+
from typing import Generic as _Generic
|
|
24
|
+
from typing import TypeVar as _TypeVar
|
|
25
|
+
from typing import overload as _overload
|
|
26
|
+
|
|
27
|
+
from pulse.transpiler.js_module import JsModule
|
|
28
|
+
|
|
29
|
+
T = _TypeVar("T")
|
|
30
|
+
T_co = _TypeVar("T_co", covariant=True)
|
|
31
|
+
U = _TypeVar("U")
|
|
32
|
+
|
|
33
|
+
# Result types for allSettled
|
|
34
|
+
PromiseFulfilledResult = dict[str, T | str] # { status: "fulfilled", value: T }
|
|
35
|
+
PromiseRejectedResult = dict[str, str] # { status: "rejected", reason: any }
|
|
36
|
+
PromiseSettledResult = PromiseFulfilledResult[T] | PromiseRejectedResult
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Promise(_Generic[T_co]):
|
|
40
|
+
"""JavaScript Promise - a thenable that represents an async operation.
|
|
41
|
+
|
|
42
|
+
`Promise` is both generic over its resolved type and implements `Awaitable`,
|
|
43
|
+
allowing it to be used with Python's async/await syntax which transpiles
|
|
44
|
+
to JavaScript async/await.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
@javascript
|
|
50
|
+
async def fetch_data() -> str:
|
|
51
|
+
response: Promise[Response] = fetch("/api/data")
|
|
52
|
+
data = await response # Awaits the promise
|
|
53
|
+
return data.text()
|
|
54
|
+
```
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
executor: _Callable[
|
|
60
|
+
[_Callable[[T_co], None], _Callable[[Exception], None]], None
|
|
61
|
+
]
|
|
62
|
+
| None = None,
|
|
63
|
+
/,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Create a Promise.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
executor: Optional function receiving (resolve, reject) callbacks.
|
|
69
|
+
If omitted, creates a pending promise (for use with Promise.resolve/reject).
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
def then(
|
|
74
|
+
self,
|
|
75
|
+
on_fulfilled: _Callable[[T_co], U | "Promise[U]"] | None = None,
|
|
76
|
+
on_rejected: _Callable[[Exception], U | "Promise[U]"] | None = None,
|
|
77
|
+
/,
|
|
78
|
+
) -> "Promise[U]":
|
|
79
|
+
"""Attach callbacks to handle fulfillment and/or rejection."""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def catch(
|
|
83
|
+
self, on_rejected: _Callable[[Exception], U | "Promise[U]"]
|
|
84
|
+
) -> "Promise[T_co | U]":
|
|
85
|
+
"""Attach a rejection handler callback."""
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
def finally_(self, on_finally: _Callable[[], None]) -> "Promise[T_co]":
|
|
89
|
+
"""Attach a handler that is called when the promise settles (fulfilled or rejected)."""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
def __await__(self) -> _Generator[None, None, T_co]:
|
|
93
|
+
"""Support await syntax - transpiles to JavaScript await."""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
# Static methods for Promise construction
|
|
97
|
+
@staticmethod
|
|
98
|
+
@_overload
|
|
99
|
+
def resolve() -> "Promise[None]":
|
|
100
|
+
"""Create a Promise that resolves with None."""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
@_overload
|
|
105
|
+
def resolve(value: U, /) -> "Promise[U]":
|
|
106
|
+
"""Create a Promise that resolves with the given value."""
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def resolve(value: U | None = None, /) -> "Promise[U] | Promise[None]":
|
|
111
|
+
"""Create a Promise that resolves with the given value."""
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def reject(reason: Exception | str) -> "Promise[_Any]":
|
|
116
|
+
"""Create a Promise that rejects with the given reason."""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
# Static methods for combining promises
|
|
120
|
+
@staticmethod
|
|
121
|
+
def all(iterable: _Iterable["Promise[T]"]) -> "Promise[list[T]]":
|
|
122
|
+
"""Wait for all promises to resolve, or reject on first rejection.
|
|
123
|
+
|
|
124
|
+
Returns a promise that resolves to a list of all resolved values.
|
|
125
|
+
"""
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def allSettled(
|
|
130
|
+
iterable: _Iterable["Promise[T]"],
|
|
131
|
+
) -> "Promise[list[PromiseSettledResult[T]]]":
|
|
132
|
+
"""Wait for all promises to settle (resolve or reject).
|
|
133
|
+
|
|
134
|
+
Returns a promise that resolves to a list of result objects.
|
|
135
|
+
"""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def any(iterable: _Iterable["Promise[T]"]) -> "Promise[T]":
|
|
140
|
+
"""Return first fulfilled promise, or reject if all reject."""
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def race(iterable: _Iterable["Promise[T]"]) -> "Promise[T]":
|
|
145
|
+
"""Return first settled promise (fulfilled or rejected)."""
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def withResolvers() -> "PromiseWithResolvers[T]":
|
|
150
|
+
"""Create a promise with its resolve and reject functions exposed.
|
|
151
|
+
|
|
152
|
+
Returns an object with { promise, resolve, reject }.
|
|
153
|
+
ES2024 feature.
|
|
154
|
+
"""
|
|
155
|
+
...
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class PromiseWithResolvers(_Generic[T]):
|
|
159
|
+
"""Result type for Promise.withResolvers()."""
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def promise(self) -> Promise[T]: ...
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def resolve(self) -> _Callable[[T], None]: ...
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def reject(self) -> _Callable[[Exception | str], None]: ...
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Self-register this module as a JS builtin (global identifier)
|
|
172
|
+
JsModule.register(name=None)
|
pulse/js/pulse.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pulse UI client bindings for channel communication.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pulse.js.pulse import usePulseChannel, ChannelBridge, PulseChannelResetError
|
|
8
|
+
|
|
9
|
+
@ps.javascript(jsx=True)
|
|
10
|
+
def MyChannelComponent(*, channel_id: str):
|
|
11
|
+
bridge = usePulseChannel(channel_id)
|
|
12
|
+
|
|
13
|
+
# Subscribe to events
|
|
14
|
+
useEffect(
|
|
15
|
+
lambda: bridge.on("server:notify", lambda payload: console.log(payload)),
|
|
16
|
+
[bridge],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Emit events to server
|
|
20
|
+
def send_ping():
|
|
21
|
+
bridge.emit("client:ping", {"message": "hello"})
|
|
22
|
+
|
|
23
|
+
# Make requests to server
|
|
24
|
+
async def send_request():
|
|
25
|
+
response = await bridge.request("client:request", {"data": 123})
|
|
26
|
+
console.log(response)
|
|
27
|
+
|
|
28
|
+
return ps.div()[
|
|
29
|
+
ps.button(onClick=send_ping)["Send Ping"],
|
|
30
|
+
ps.button(onClick=send_request)["Send Request"],
|
|
31
|
+
]
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from collections.abc import Awaitable as _Awaitable
|
|
36
|
+
from collections.abc import Callable as _Callable
|
|
37
|
+
from typing import Any as _Any
|
|
38
|
+
from typing import TypeVar as _TypeVar
|
|
39
|
+
|
|
40
|
+
from pulse.transpiler.js_module import JsModule
|
|
41
|
+
|
|
42
|
+
T = _TypeVar("T")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PulseChannelResetError(Exception):
|
|
46
|
+
"""Error raised when a channel is closed or reset."""
|
|
47
|
+
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ChannelBridge:
|
|
52
|
+
"""A bridge for bidirectional communication between client and server.
|
|
53
|
+
|
|
54
|
+
Provides methods for emitting events, making requests, and subscribing
|
|
55
|
+
to server events on a specific channel.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def id(self) -> str:
|
|
60
|
+
"""The unique channel identifier."""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def emit(self, event: str, payload: _Any = None) -> None:
|
|
64
|
+
"""Emit an event to the server.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
event: The event name to emit.
|
|
68
|
+
payload: Optional data to send with the event.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
def request(self, event: str, payload: _Any = None) -> _Awaitable[_Any]:
|
|
73
|
+
"""Make a request to the server and await a response.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
event: The event name to send.
|
|
77
|
+
payload: Optional data to send with the request.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A Promise that resolves with the server's response.
|
|
81
|
+
"""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
def on(self, event: str, handler: _Callable[[_Any], _Any]) -> _Callable[[], None]:
|
|
85
|
+
"""Subscribe to events from the server.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
event: The event name to listen for.
|
|
89
|
+
handler: A callback function that receives the event payload.
|
|
90
|
+
May be sync or async. For request events, the return value
|
|
91
|
+
is sent back to the server.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A cleanup function that unsubscribes the handler.
|
|
95
|
+
"""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def usePulseChannel(channel_id: str) -> ChannelBridge:
|
|
100
|
+
"""React hook to connect to a Pulse channel.
|
|
101
|
+
|
|
102
|
+
Must be called from within a React component. The channel connection
|
|
103
|
+
is automatically managed based on component lifecycle.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
channel_id: The unique identifier for the channel to connect to.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
A ChannelBridge instance for interacting with the channel.
|
|
110
|
+
"""
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Register as a JS module with named imports from pulse-ui-client
|
|
115
|
+
JsModule.register(name="pulse", src="pulse-ui-client", values="named_import")
|