create-caspian-app 1.3.26 → 1.4.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.
@@ -20,21 +20,9 @@ def page(error_message: str, error_trace: Optional[str] = None):
20
20
  fill="none"
21
21
  xmlns="http://www.w3.org/2000/svg"
22
22
  aria-hidden="true">
23
- <path d="M11.001 2a1 1 0 0 1 .998 0L21 7v9a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V7l8.001-5z"
24
- stroke="currentColor"
25
- stroke-width="1.5"
26
- stroke-linecap="round"
27
- stroke-linejoin="round" />
28
- <path d="M12 8v4"
29
- stroke="currentColor"
30
- stroke-width="1.5"
31
- stroke-linecap="round"
32
- stroke-linejoin="round" />
33
- <path d="M12 16h.01"
34
- stroke="currentColor"
35
- stroke-width="1.5"
36
- stroke-linecap="round"
37
- stroke-linejoin="round" />
23
+ <path d="M11.001 2a1 1 0 0 1 .998 0L21 7v9a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V7l8.001-5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
24
+ <path d="M12 8v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
25
+ <path d="M12 16h.01" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
38
26
  </svg>
39
27
 
40
28
  <div class="flex-1">
@@ -51,37 +39,29 @@ def page(error_message: str, error_trace: Optional[str] = None):
51
39
  Home
52
40
  </a>
53
41
  <button class="inline-block rounded bg-blue-600 px-3 py-1.5 text-sm text-white"
54
- onclick="window.location.reload()">
55
- Reload
56
- </button>
42
+ onclick="window.location.reload()">Reload</button>
57
43
  {% if error_trace %}
58
- <button class="inline-block rounded border border-gray-200 bg-transparent px-3 py-1.5 text-sm text-black"
59
- onclick="setShowTrace(current => !current)">
60
- {showTrace ? "Hide details" : "Show details"}
61
- </button>
44
+ <button class="inline-block rounded border border-gray-200 bg-transparent px-3 py-1.5 text-sm text-black"
45
+ onclick="setShowTrace(current => !current)">{showTrace ? "Hide details" : "Show details"}</button>
62
46
  {% endif %}
63
47
  </div>
64
48
  </div>
65
49
  </div>
66
50
 
67
51
  {% if error_trace %}
68
- <section class="mt-4" hidden="{!showTrace}">
69
- <div class="mb-2 flex items-center justify-between">
70
- <div class="text-xs text-gray-500">Error details (development only)</div>
71
- <div class="flex gap-2">
72
- <button class="rounded border bg-gray-100 px-2 py-1 text-xs text-black"
73
- onclick="copyTrace()">
74
- {traceCopied ? "Copied" : "Copy"}
75
- </button>
76
- <button class="rounded border bg-gray-100 px-2 py-1 text-xs text-black"
77
- onclick="downloadTrace()">
78
- Download
79
- </button>
52
+ <section class="mt-4" hidden="{!showTrace}">
53
+ <div class="mb-2 flex items-center justify-between">
54
+ <div class="text-xs text-gray-500">Error details (development only)</div>
55
+ <div class="flex gap-2">
56
+ <button class="rounded border bg-gray-100 px-2 py-1 text-xs text-black"
57
+ onclick="copyTrace()">{traceCopied ? "Copied" : "Copy"}</button>
58
+ <button class="rounded border bg-gray-100 px-2 py-1 text-xs text-black"
59
+ onclick="downloadTrace()">Download</button>
60
+ </div>
80
61
  </div>
81
- </div>
82
- <pre pp-ref="{tracePre}"
83
- class="overflow-auto rounded bg-gray-100 p-4 text-xs text-black">{{ error_trace }}</pre>
84
- </section>
62
+ <pre pp-ref="{tracePre}"
63
+ class="overflow-auto rounded bg-gray-100 p-4 text-xs text-black">{{ error_trace }}</pre>
64
+ </section>
85
65
  {% endif %}
86
66
 
87
67
  <script>
@@ -109,4 +89,7 @@ def page(error_message: str, error_trace: Optional[str] = None):
109
89
  </script>
110
90
  </div>
111
91
  </main>
112
- """, error_message=error_message, error_trace=error_trace)
92
+ """,
93
+ error_message=error_message,
94
+ error_trace=error_trace,
95
+ )
@@ -15,16 +15,12 @@ def page():
15
15
  <div class="text-center">
16
16
  <h1 class="text-9xl font-black text-gray-200">404</h1>
17
17
 
18
- <p class="text-2xl font-bold tracking-tight text-gray-900 sm:text-4xl">
19
- Uh-oh!
20
- </p>
18
+ <p class="text-2xl font-bold tracking-tight text-gray-900 sm:text-4xl">Uh-oh!</p>
21
19
 
22
20
  <p class="mt-4 text-gray-500">We can't find that page.</p>
23
21
 
24
- <a
25
- href="/"
26
- class="mt-6 inline-block rounded bg-indigo-600 px-5 py-3 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring"
27
- >
22
+ <a href="/"
23
+ class="mt-6 inline-block rounded bg-indigo-600 px-5 py-3 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring">
28
24
  Go Back Home
29
25
  </a>
30
26
  </div>
@@ -21,7 +21,6 @@ def build_auth_settings() -> AuthSettings:
21
21
  default_token_validity="1h",
22
22
  # Sliding-session refresh only matters when the request flow calls auth.refresh_session().
23
23
  token_auto_refresh=False,
24
-
25
24
  # Route protection
26
25
  # This app-owned starter config begins public-first; switch to True only when most routes require auth.
27
26
  # Use all-private mode when only a few routes should stay public.
@@ -30,11 +29,9 @@ def build_auth_settings() -> AuthSettings:
30
29
  # Sign-in and signup stay public by default; only change this when the app explicitly needs it.
31
30
  auth_routes=["/signin", "/signup"],
32
31
  private_routes=[], # unused when all-routes-private is True
33
-
34
32
  # Role-based access
35
33
  is_role_based=False,
36
34
  role_identifier="role",
37
-
38
35
  # RBAC policy is app-owned here; the runtime expects ROUTE/PATTERN -> [ROLES].
39
36
  # Example (when enabled):
40
37
  # role_based_routes={
@@ -42,7 +39,6 @@ def build_auth_settings() -> AuthSettings:
42
39
  # "/admin": ["admin", "superadmin"],
43
40
  # },
44
41
  role_based_routes={},
45
-
46
42
  # Redirects / prefixes
47
43
  default_signin_redirect="/dashboard",
48
44
  default_signout_redirect="/signin",
@@ -123,9 +123,7 @@ def _rate_window_seconds() -> int:
123
123
  # Ceiling on simultaneous open sockets. Every open connection is a live task
124
124
  # plus a broadcast target, so an unbounded count lets cheap clients grow
125
125
  # server memory and turn each broadcast into an amplification.
126
- MAX_WEBSOCKET_CONNECTIONS = max(
127
- 1, int(os.getenv("MAX_WEBSOCKET_CONNECTIONS", 200))
128
- )
126
+ MAX_WEBSOCKET_CONNECTIONS = max(1, int(os.getenv("MAX_WEBSOCKET_CONNECTIONS", 200)))
129
127
 
130
128
 
131
129
  def _is_production() -> bool:
@@ -147,11 +145,7 @@ def _configured_websocket_origins() -> set[str]:
147
145
  ):
148
146
  raw_values.extend(os.getenv(env_name, "").split(","))
149
147
 
150
- return {
151
- _normalized_origin(origin)
152
- for origin in raw_values
153
- if _normalized_origin(origin)
154
- }
148
+ return {_normalized_origin(origin) for origin in raw_values if _normalized_origin(origin)}
155
149
 
156
150
 
157
151
  def _websocket_same_origin(websocket: WebSocket) -> str:
@@ -208,8 +202,7 @@ class SocketEntry:
208
202
  SOCKET_REGISTRY: dict[str, SocketEntry] = {}
209
203
 
210
204
 
211
- def socket(require_auth: bool = False,
212
- allowed_roles: list[str] | None = None):
205
+ def socket(require_auth: bool = False, allowed_roles: list[str] | None = None):
213
206
  """Register an async function as a named socket.
214
207
 
215
208
  The function's own name is the name the browser connects with, so it must
@@ -309,9 +302,7 @@ class _Shared:
309
302
  """State the `Socket` and every cloned `SocketSender` see together."""
310
303
 
311
304
  def __init__(self) -> None:
312
- self.outgoing: asyncio.Queue[str | None] = asyncio.Queue(
313
- maxsize=SEND_QUEUE_SIZE
314
- )
305
+ self.outgoing: asyncio.Queue[str | None] = asyncio.Queue(maxsize=SEND_QUEUE_SIZE)
315
306
  self.closed = False
316
307
  self.last_activity = time.monotonic()
317
308
 
@@ -452,8 +443,7 @@ class Socket:
452
443
  return json.loads(text)
453
444
  except json.JSONDecodeError as e:
454
445
  raise ValueError(
455
- f"`{self._name}` could not read a frame -- {e}. Each frame is "
456
- "one JSON value."
446
+ f"`{self._name}` could not read a frame -- {e}. Each frame is one JSON value."
457
447
  ) from e
458
448
 
459
449
  async def recv_text(self) -> str | None:
@@ -467,9 +457,7 @@ class Socket:
467
457
  idle_timeout = _idle_timeout_seconds()
468
458
  while not self._shared.closed:
469
459
  try:
470
- text = await asyncio.wait_for(
471
- self._websocket.receive_text(), timeout=idle_timeout
472
- )
460
+ text = await asyncio.wait_for(self._websocket.receive_text(), timeout=idle_timeout)
473
461
  except asyncio.TimeoutError:
474
462
  # Outbound traffic counts as liveness: a passive listener in
475
463
  # an active room is not idle, it is listening.
@@ -520,7 +508,7 @@ class Socket:
520
508
  """Let queued frames drain, then reclaim the writer task."""
521
509
  try:
522
510
  await asyncio.wait_for(self._writer, timeout=5)
523
- except (asyncio.CancelledError, Exception):
511
+ except asyncio.CancelledError, Exception:
524
512
  self._writer.cancel()
525
513
  self._shared.closed = True
526
514
 
@@ -574,9 +562,7 @@ def _authorize(websocket: WebSocket, entry: SocketEntry) -> _Refusal | None:
574
562
 
575
563
  if auth.is_authenticated():
576
564
  payload = auth.get_payload() or {}
577
- if entry.allowed_roles and not auth.check_role(
578
- payload, list(entry.allowed_roles)
579
- ):
565
+ if entry.allowed_roles and not auth.check_role(payload, list(entry.allowed_roles)):
580
566
  return _Refusal(f"The socket `{entry.name}` is not available to this account.")
581
567
  return None
582
568
 
@@ -596,9 +582,7 @@ async def _first_frame(websocket: WebSocket) -> dict[str, Any] | _Refusal | None
596
582
  mid-handshake. Not an error, and nobody left to tell.
597
583
  """
598
584
  try:
599
- text = await asyncio.wait_for(
600
- websocket.receive_text(), timeout=ARGS_TIMEOUT_SECONDS
601
- )
585
+ text = await asyncio.wait_for(websocket.receive_text(), timeout=ARGS_TIMEOUT_SECONDS)
602
586
  except asyncio.TimeoutError:
603
587
  return _Refusal(
604
588
  "This socket opened and sent no arguments. The first frame is the "
@@ -631,14 +615,13 @@ def _call_kwargs(
631
615
  from the wire.
632
616
  """
633
617
  parameters = inspect.signature(entry.handler).parameters.values()
634
- accepts_kwargs = any(
635
- p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters
636
- )
618
+ accepts_kwargs = any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters)
637
619
 
638
620
  accepted = {
639
621
  p.name
640
622
  for p in parameters
641
- if p.kind in (
623
+ if p.kind
624
+ in (
642
625
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
643
626
  inspect.Parameter.KEYWORD_ONLY,
644
627
  )
@@ -647,15 +630,13 @@ def _call_kwargs(
647
630
  missing = [
648
631
  p.name
649
632
  for p in parameters
650
- if p.name in accepted
651
- and p.default is inspect.Parameter.empty
652
- and p.name not in args
633
+ if p.name in accepted and p.default is inspect.Parameter.empty and p.name not in args
653
634
  ]
654
635
  if missing:
655
636
  return _Refusal(
656
637
  f"`{entry.name}` is missing its `{missing[0]}` argument. The "
657
638
  f'browser opens pp.socket("{entry.name}", '
658
- f'{{ {missing[0]}: ... }}).'
639
+ f"{{ {missing[0]}: ... }})."
659
640
  )
660
641
 
661
642
  if accepts_kwargs:
@@ -729,11 +710,7 @@ async def serve_named_socket(websocket: WebSocket) -> None:
729
710
  except Exception as e:
730
711
  print(f"[Socket Error] {entry.name}: {e}")
731
712
  traceback.print_exc()
732
- message = (
733
- "Internal server error"
734
- if is_production_environment()
735
- else f"{entry.name}: {e}"
736
- )
713
+ message = "Internal server error" if is_production_environment() else f"{entry.name}: {e}"
737
714
  await sock.sender()._error(message)
738
715
  await sock._finish()
739
716
  finally:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "1.3.26",
3
+ "version": "1.4.0",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",