@nice-code/action 0.21.0 → 0.23.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.
Files changed (32) hide show
  1. package/README.md +95 -63
  2. package/build/{ActionDevtoolsCore-baIHExfj.d.mts → ActionDevtoolsCore-BjbhFqc0.d.mts} +2 -2
  3. package/build/{ActionDevtoolsCore-dApyYvTS.d.cts → ActionDevtoolsCore-kk7oZBv9.d.cts} +2 -2
  4. package/build/{ActionPayload.types-CQM1HRw_.d.cts → ActionPayload.types-B-OSg09t.d.mts} +147 -39
  5. package/build/{ActionPayload.types-DXGiw1SF.d.mts → ActionPayload.types-DIOeVapm.d.cts} +147 -39
  6. package/build/devtools/browser/index.d.cts +1 -1
  7. package/build/devtools/browser/index.d.mts +1 -1
  8. package/build/devtools/server/index.d.cts +1 -1
  9. package/build/devtools/server/index.d.mts +1 -1
  10. package/build/httpAcceptorCarrier-DJVxzDVd.mjs +3906 -0
  11. package/build/httpAcceptorCarrier-DJVxzDVd.mjs.map +1 -0
  12. package/build/httpAcceptorCarrier-hYPuoNuP.cjs +4291 -0
  13. package/build/httpAcceptorCarrier-hYPuoNuP.cjs.map +1 -0
  14. package/build/index.cjs +377 -4142
  15. package/build/index.cjs.map +1 -1
  16. package/build/index.d.cts +2 -2
  17. package/build/index.d.mts +2 -2
  18. package/build/index.mjs +314 -4078
  19. package/build/index.mjs.map +1 -1
  20. package/build/platform/cloudflare/index.cjs +30 -2
  21. package/build/platform/cloudflare/index.cjs.map +1 -1
  22. package/build/platform/cloudflare/index.d.cts +55 -2
  23. package/build/platform/cloudflare/index.d.mts +55 -2
  24. package/build/platform/cloudflare/index.mjs +28 -2
  25. package/build/platform/cloudflare/index.mjs.map +1 -1
  26. package/build/react-query/index.d.cts +1 -1
  27. package/build/react-query/index.d.mts +1 -1
  28. package/package.json +4 -4
  29. package/build/wsAcceptorCarrier-BDJRIPfu.cjs +0 -103
  30. package/build/wsAcceptorCarrier-BDJRIPfu.cjs.map +0 -1
  31. package/build/wsAcceptorCarrier-CW2qX25W.mjs +0 -80
  32. package/build/wsAcceptorCarrier-CW2qX25W.mjs.map +0 -1
package/README.md CHANGED
@@ -45,6 +45,10 @@ The high-level entry points — `connectChannel` (dial out) and `serveChannel` (
45
45
  reach for 95% of the time. The lower-level handler/carrier/transport objects they desugar to are
46
46
  documented at the end under [Lower-level building blocks](#lower-level-building-blocks).
47
47
 
48
+ > **Code blocks below are labelled by where they run:** `// shared.ts` (imported by both ends),
49
+ > `// server.ts` (the acceptor), `// client.ts` (the connector). A typical app is exactly these three
50
+ > files.
51
+
48
52
  ---
49
53
 
50
54
  ## Defining actions
@@ -52,6 +56,7 @@ documented at the end under [Lower-level building blocks](#lower-level-building-
52
56
  ### 1. Create a root domain (shared between both ends)
53
57
 
54
58
  ```ts
59
+ // shared.ts
55
60
  import { createActionRootDomain, actionSchema } from "@nice-code/action";
56
61
  import * as v from "valibot";
57
62
 
@@ -159,6 +164,7 @@ A **local handler** runs actions in the current process. Build one and register
159
164
  is what answers incoming requests.
160
165
 
161
166
  ```ts
167
+ // server.ts
162
168
  import { ActionRuntime, createLocalHandler, RuntimeCoordinate } from "@nice-code/action";
163
169
 
164
170
  export const serverCoord = RuntimeCoordinate.env("backend");
@@ -194,9 +200,10 @@ for local-first clients that resolve a few actions themselves and forward the re
194
200
 
195
201
  `serveChannel` is the one call that stands up an acceptor: it builds the crypto identity **once**, fans it
196
202
  across every carrier, registers your handlers, wires hibernation, and returns a server object whose
197
- `fetch` / `duplex` / `pushToClient` you forward straight to the host.
203
+ `fetch` / `receive` / `drop` / `pushToClient` you forward straight to the host.
198
204
 
199
205
  ```ts
206
+ // server.ts
200
207
  import {
201
208
  ActionRuntime,
202
209
  RuntimeCoordinate,
@@ -217,29 +224,35 @@ const server = serveChannel(runtime, appChannel, {
217
224
  ],
218
225
  });
219
226
 
220
- // Wire the host's events to the server:
227
+ // Wire the host's events to the server — `receive`/`drop` are the universal forwarding seam:
221
228
  // fetch(req) => server.fetch(req)
222
- // webSocketMessage(ws, msg) => server.duplex?.receive(ws, msg)
223
- // webSocketClose/Error(ws) => server.duplex?.drop(ws)
229
+ // webSocketMessage(ws, msg) => server.receive(ws, msg)
230
+ // webSocketClose/Error(ws) => server.drop(ws)
224
231
  ```
225
232
 
226
- `serveChannel(runtime, channel, options)` returns `{ handlers, fetch, duplex?, pushToClient }`:
233
+ `serveChannel(runtime, channel, options)` returns `{ handlers, fetch, receive, drop, pushToClient, broadcast }`:
227
234
 
228
235
  - **`fetch`** — a web-standard handler that does the WS upgrade, the (secure or plain) HTTP action POST,
229
236
  CORS preflight, and a `404` fallback. Forward the host's `fetch` straight to it.
230
- - **`duplex`** the `{ receive, drop }` lifecycle for the *sole* duplex carrier (a shortcut for the
231
- common single-WebSocket case; `undefined` when there are zero or several then feed each carrier
232
- handle directly).
233
- - **`pushToClient(target, request, opts?)`** — push a server-initiated action to one connected client
234
- (see [Bi-directional](#bi-directional-communication-acceptor--connector)).
237
+ - **`receive(conn, frame)` / `drop(conn)`** the universal connection lifecycle; forward your host's
238
+ message and close/error events here. Routes to the sole duplex carrier (the common single-WebSocket
239
+ case); with several duplex carriers, feed each carrier handle directly.
240
+ - **`pushToClient(target, request, opts?)`** / **`broadcast(makeRequest, opts?)`** — push a
241
+ server-initiated action to one / every connected client (see
242
+ [Bi-directional](#bi-directional-communication-acceptor--connector)).
235
243
  - **`handlers`** — the acceptor handlers it built, one per duplex carrier (reach for these for per-handler
236
244
  `broadcast`).
237
245
 
238
246
  Key options: `clientEnv` (required), `storage` (required only when a carrier is secure — the default),
239
- `carriers`, `handlers`, plus `securityLevel` / `link` / `verifyKeyResolver` / `defaultTimeout`.
247
+ `carriers`, `handlers`, `channelCases` (connection-aware cases see below), plus `securityLevel` /
248
+ `link` / `verifyKeyResolver` / `defaultTimeout`.
240
249
 
241
250
  > On Cloudflare, [`@nice-code/action/platform/cloudflare`](#cloudflare-durable-objects) collapses the
242
- > Durable Object carrier + storage boilerplate to one-liners.
251
+ > Durable Object carrier + storage + lifecycle boilerplate into a single `serveDurableObject` call.
252
+ >
253
+ > Serving in another environment (Bun, Node, …)? `serveChannel`'s carriers are environment-neutral, and
254
+ > `serveHost(runtime, channel, hostAdapter, options)` folds a reusable `{ carriers, storage, onServed }`
255
+ > host adapter into it — the same seam `serveDurableObject` is built on.
243
256
 
244
257
  ### Connecting — `connectChannel`
245
258
 
@@ -251,6 +264,7 @@ from `onPush` — all derived from the channel, no restated lists. It's the exac
251
264
  `serveChannel`.
252
265
 
253
266
  ```ts
267
+ // client.ts
254
268
  import {
255
269
  ActionRuntime,
256
270
  RuntimeCoordinate,
@@ -267,7 +281,7 @@ connectChannel(clientRuntime, appChannel, {
267
281
  storage, // one crypto identity, fanned across every secure transport
268
282
  securityLevel: ESecurityLevel.encrypted,
269
283
  transports: [
270
- { carrier: wsCarrier("wss://api.example.com/resolve_action/ws") }, // secure WS, preferred
284
+ { carrier: wsCarrier(() => ({ url: "wss://api.example.com/resolve_action/ws" })) }, // secure WS, preferred
271
285
  { carrier: httpCarrier(() => ({ url: "https://api.example.com/resolve_action" })), secure: false }, // plain HTTP fallback
272
286
  ],
273
287
  // onPush: { ... } // handlers for the channel's toConnector pushes (see below)
@@ -292,10 +306,11 @@ connectChannel(clientRuntime, appChannel, {
292
306
 
293
307
  ## Calling actions
294
308
 
295
- Once a runtime is wired, calling an action looks the same regardless of where it resolves (locally or
296
- over a carrier):
309
+ Once a runtime is wired, calling an action looks the same on **any** side client or server — and
310
+ regardless of where it resolves (locally or over a carrier):
297
311
 
298
312
  ```ts
313
+ // any runtime with the domain wired
299
314
  // Run and get the output directly (throws on a declared/transport error)
300
315
  const output = await userDomain.action.getUser
301
316
  .request({ userId: "u_123" })
@@ -325,7 +340,7 @@ connection — no second channel, no polling. The shape:
325
340
  ### Shared channel
326
341
 
327
342
  ```ts
328
- // Bidirectional: client sends `start_feed`; server pushes `position_update` back.
343
+ // shared.ts — bidirectional: client sends `start_feed`; server pushes `position_update` back.
329
344
  export const lobbyDomain = appRoot.createChildDomain({
330
345
  domain: "lobby",
331
346
  actions: {
@@ -347,10 +362,11 @@ export const appChannel = defineSecureChannel({
347
362
  ### Connector side — handle pushes with `onPush`
348
363
 
349
364
  ```ts
365
+ // client.ts
350
366
  connectChannel(clientRuntime, appChannel, {
351
367
  peer: serverCoord,
352
368
  storage,
353
- transports: [{ carrier: wsCarrier(wsUrl) }, { carrier: httpCarrier(httpUrl), secure: false }],
369
+ transports: [{ carrier: wsCarrier(() => ({ url: wsUrl })) }, { carrier: httpCarrier(() => ({ url: httpUrl })), secure: false }],
354
370
  onPush: {
355
371
  // Keyed by the toConnector action id; input + output typed from the channel.
356
372
  position_update: async ({ player, x, y }) => {
@@ -366,6 +382,7 @@ connectChannel(clientRuntime, appChannel, {
366
382
  The local handler reads `action.context.originClient` to know who asked, then pushes to them:
367
383
 
368
384
  ```ts
385
+ // server.ts
369
386
  const lobbyHandler = createLocalHandler().forDomainActionCases(lobbyDomain, {
370
387
  start_feed: async (action) => {
371
388
  let delivered = 0;
@@ -392,26 +409,28 @@ server.broadcast(
392
409
  );
393
410
  ```
394
411
 
395
- > **When a handler needs the originating connection itself** (to register it in a room, etc.) rather than
396
- > just the client coordinate, pass `channelCases` to `serveChannel` — each case receives the request *and*
397
- > that client's live connection. With several duplex carriers (no sole handler), reach for a specific
398
- > `server.handlers[i].broadcast(...)` and `acceptChannelConnections(handler, channel, { ... })` directly
399
- > (see [Lower-level building blocks](#lower-level-building-blocks)).
412
+ > **When a handler needs the originating connection itself** (to register it in a room, track per-socket
413
+ > state, etc.) rather than just the client coordinate, pass `channelCases` to `serveChannel` /
414
+ > `serveDurableObject` each case receives the request *and* an `IConnectionContext` (`conn.state` /
415
+ > `conn.setState` / `conn.broadcast({ exceptSelf }) ` / `conn.pushBack` / `conn.connection`). With several
416
+ > duplex carriers (no sole handler), reach for a specific `server.handlers[i].broadcast(...)` and
417
+ > `acceptChannelConnections(handler, channel, { ... })` directly (see
418
+ > [Lower-level building blocks](#lower-level-building-blocks)).
400
419
 
401
420
  ---
402
421
 
403
422
  ## Cloudflare Durable Objects
404
423
 
405
- `@nice-code/action/platform/cloudflare` collapses the DO boilerplate (the `WebSocketPair` upgrade, the
406
- hibernation attachment wiring, and the DO-storage adapter) into one-liners you hand to `serveChannel`.
407
- The core library stays platform-agnostic nothing here is reachable from the main entry.
424
+ `@nice-code/action/platform/cloudflare` collapses the *entire* DO transport stack the hibernatable
425
+ secure WebSocket, an HTTP fallback, the DO-storage crypto identity, and the `ping`/`pong` keepalive
426
+ into a single `serveDurableObject` call, leaving the DO to forward its four socket lifecycle methods. The
427
+ core library stays platform-agnostic — nothing here is reachable from the main entry.
408
428
 
409
429
  ```ts
410
430
  import { DurableObject } from "cloudflare:workers";
411
- import { ActionRuntime, serveChannel, httpAcceptorCarrier } from "@nice-code/action";
431
+ import { ActionRuntime } from "@nice-code/action";
412
432
  import {
413
- durableObjectWsCarrier,
414
- durableObjectStorage,
433
+ serveDurableObject,
415
434
  type TDurableObjectChannelServer,
416
435
  } from "@nice-code/action/platform/cloudflare";
417
436
 
@@ -422,13 +441,12 @@ export class MyDurableObject extends DurableObject {
422
441
  if (this._server != null) return this._server;
423
442
  const runtime = new ActionRuntime(serverCoord);
424
443
 
425
- // One WS carrier (duplex pushes + hibernation persistence) + a secure HTTP fallback (exchange),
426
- // both sharing one crypto identity built from this DO's storage, surviving eviction.
427
- this._server = serveChannel(runtime, appChannel, {
444
+ // Hibernatable secure WS + plain HTTP fallback + DO-storage crypto identity + keepalive, all folded in.
445
+ this._server = serveDurableObject(this.ctx, appChannel, {
446
+ runtime,
428
447
  clientEnv: RuntimeCoordinate.env("frontend"),
429
- storage: durableObjectStorage(this.ctx, { keyPrefix: "ws:" }),
448
+ keyPrefix: "ws:",
430
449
  handlers: [userHandler],
431
- carriers: [durableObjectWsCarrier(this.ctx), httpAcceptorCarrier()],
432
450
  });
433
451
  return this._server;
434
452
  }
@@ -437,40 +455,53 @@ export class MyDurableObject extends DurableObject {
437
455
  return this.getServer().fetch(request);
438
456
  }
439
457
  async webSocketMessage(ws: WebSocket, msg: string | ArrayBuffer) {
440
- this.getServer().duplex?.receive(ws, msg);
458
+ this.getServer().receive(ws, msg);
441
459
  }
442
- async webSocketClose(ws: WebSocket) { this.getServer().duplex?.drop(ws); }
443
- async webSocketError(ws: WebSocket) { this.getServer().duplex?.drop(ws); }
460
+ async webSocketClose(ws: WebSocket) { this.getServer().drop(ws); }
461
+ async webSocketError(ws: WebSocket) { this.getServer().drop(ws); }
444
462
  }
445
463
  ```
446
464
 
447
- - **`durableObjectWsCarrier(ctx, { secure? })`** a hibernatable-WebSocket acceptor carrier with `send`,
448
- the `WebSocketPair` upgrade, and the socket-attachment access all derived from the DO's `ctx`. Pass
449
- `secure: false` for a plain WS endpoint (then no `storage` is needed for it). It exposes the attachment to
450
- `serveChannel`, which owns the layout: it persists the routing binding there and (when `connectionState`
451
- is requested) co-stores per-connection app state in the *same* slot, so both survive eviction.
452
- - **`durableObjectStorage(ctx, { keyPrefix? })`** — wraps the DO's storage as a `StorageAdapter` for
453
- `serveChannel`'s `storage`.
465
+ `serveDurableObject(ctx, channel, options)` takes the same `serveChannel` surface (`clientEnv`,
466
+ `handlers`, `channelCases`, `connectionState`, …) plus the host knobs:
467
+
468
+ - **`runtime`** this DO's runtime.
469
+ - **`keyPrefix`** namespace for the DO-storage crypto-identity keys.
470
+ - **`httpFallback`** — `"plain"` (default), `"secure"` (handshake-protected exchange sharing the WS
471
+ identity), or `false` (WebSocket only).
472
+ - **`secure`** — whether the WebSocket itself runs the handshake (default `true`).
473
+
474
+ For finer control the lower-level pieces are still exported: `cloudflareDurableObjectHost(ctx, opts)`
475
+ builds just the `{ carriers, storage, onServed }` host adapter (hand it to `serveHost`), and
476
+ `durableObjectWsCarrier` / `durableObjectStorage` build the individual carrier / storage adapter.
454
477
 
455
478
  ### Per-connection state + broadcast (stateful DOs)
456
479
 
457
- A presence/room DO that tracks who's on each socket and fans messages out adds two `serveChannel` knobs —
458
- no `handlers[0]`, no hand-wired attachment store:
480
+ A presence/room DO that tracks who's on each socket and fans messages out adds two knobs —
481
+ `connectionState` (typed per-socket app state, co-stored with the routing binding so both survive a wake)
482
+ and `channelCases`. Each case gets an **`IConnectionContext`** as its second argument — the originating
483
+ connection plus everything a case reaches for, so it never threads `ws` through `server.connections` /
484
+ `server.broadcast` by hand:
459
485
 
460
486
  ```ts
461
- const server = serveChannel(runtime, lobbyChannel, {
487
+ const server = serveDurableObject(this.ctx, lobbyChannel, {
488
+ runtime,
462
489
  clientEnv: RuntimeCoordinate.env("web"),
463
- storage: durableObjectStorage(this.ctx, { keyPrefix: "lobby-ws:" }),
464
- carriers: [durableObjectWsCarrier(this.ctx), httpAcceptorCarrier({ secure: false })],
465
- // Co-store each player's identity with the routing binding in the socket attachment (survives a wake).
490
+ keyPrefix: "lobby-ws:",
466
491
  connectionState: { schema: vs_player },
467
- // Connection-aware cases: each gets the request + the originating socket.
468
492
  channelCases: {
469
- join: (action, ws) => {
470
- if (ws) server.connections.set(ws, action.input); // typed by the schema
471
- server.broadcast(() => lobbyPush.action.player_joined.request(action.input), { except: ws });
493
+ join: (action, conn) => {
494
+ conn.setState(action.input); // typed by the schema; co-stored with the binding
495
+ conn.broadcast(() => lobbyPush.action.player_joined.request(action.input), { exceptSelf: true });
472
496
  return { players: this.roster() };
473
497
  },
498
+ move: (action, conn) => {
499
+ const player = conn.state; // this socket's typed app state (or null)
500
+ if (!player) return; // a move before join is dropped
501
+ conn.broadcast(() => lobbyPush.action.player_moved.request({ id: player.id, ...action.input }), {
502
+ exceptSelf: true,
503
+ });
504
+ },
474
505
  },
475
506
  });
476
507
 
@@ -478,12 +509,12 @@ const server = serveChannel(runtime, lobbyChannel, {
478
509
  for (const [, player] of server.connections.entries()) this.players.set(player.id, player);
479
510
  ```
480
511
 
481
- `connectionState` narrows the return so `server.connections` is non-optional, and `server.broadcast`
482
- fans out over the sole duplex carrier. The store's `read`/`write` are pluggable, so a DO that prefers to
483
- keep state in its SQLite/KV storage rather than the socket attachment can supply its own.
484
-
485
- Because the WS carrier persists each connection's binding on bind and replays it on construction, results
486
- and pushes still route to the right socket after the object wakes from eviction.
512
+ The `IConnectionContext` (`conn`) gives a case: `conn.state` / `conn.setState(x)` / `conn.clearState()`
513
+ (the typed app state), `conn.broadcast(makeRequest, { exceptSelf })`, `conn.pushBack(request)` (push down
514
+ this same socket), and `conn.connection` (the raw socket, `null` on the HTTP-exchange path). Passing
515
+ `connectionState` narrows the return so `server.connections` is non-optional. Because the WS carrier
516
+ persists each connection's binding on bind and replays it on construction, results and pushes still route
517
+ to the right socket after the object wakes from eviction.
487
518
 
488
519
  ---
489
520
 
@@ -514,7 +545,8 @@ plain HTTP fallback by giving the acceptor a `httpAcceptorCarrier({ secure: fals
514
545
  `serveChannel` accepts **any number of duplex carriers** (e.g. WebSocket + WebRTC) plus at most one
515
546
  exchange carrier. They all share one crypto identity and one runtime, and each result/push routes back
516
547
  over the carrier its client actually connected on (connection-aware return routing). With several duplex
517
- carriers, `server.duplex` is `undefined` — feed each carrier handle's own `receive`/`drop` directly.
548
+ carriers, `server.receive`/`server.drop` throw (they can't pick a carrier) — feed each carrier handle's
549
+ own `receive`/`drop` directly.
518
550
 
519
551
  ```ts
520
552
  const ws = wsAcceptorCarrier({ send: wsSend, upgrade, attachmentStore });
@@ -541,19 +573,19 @@ predicate is re-evaluated per action dispatch, so the transport switches on the
541
573
  holds, with no reconnect.
542
574
 
543
575
  ```ts
544
- // Prefer the secure socket, but only once a session id exists; fall back to HTTP meanwhile.
576
+ // client.ts — prefer the secure socket, but only once a session id exists; fall back to HTTP meanwhile.
545
577
  connectChannel(runtime, appChannel, {
546
578
  peer: serverCoord,
547
579
  storage,
548
580
  transports: [
549
581
  {
550
- carrier: wsCarrier(`${url}/ws`, {
582
+ carrier: wsCarrier(() => ({ url: `${url}/ws` }), {
551
583
  // Only ever called once `available` passes, so no need for a placeholder cache key.
552
584
  getTransportCacheKey: () => [sessionId],
553
585
  }),
554
586
  available: () => sessionId != null, // ws preferred; HTTP serves while ws is gated off
555
587
  },
556
- { carrier: httpCarrier(httpUrl), secure: false },
588
+ { carrier: httpCarrier(() => ({ url: httpUrl })), secure: false },
557
589
  ],
558
590
  });
559
591
  ```
@@ -1,4 +1,4 @@
1
- import { Pr as IRuntimeCoordinate, _ as TActionProgress } from "./ActionPayload.types-DXGiw1SF.mjs";
1
+ import { _ as TActionProgress, zr as IRuntimeCoordinate } from "./ActionPayload.types-B-OSg09t.mjs";
2
2
  //#region ../nice-devtools-shared/src/components/PanelChrome.d.ts
3
3
  /** Where a devtools panel is docked. */
4
4
  type TDevtoolsPosition = "dock-bottom" | "dock-top" | "dock-left" | "dock-right";
@@ -76,4 +76,4 @@ declare class ActionDevtoolsCore {
76
76
  }
77
77
  //#endregion
78
78
  export { TDevtoolsActionStatus as a, IDevtoolsObservableDomain as i, IActionDevtoolsCoreOptions as n, TDevtoolsListener as o, IDevtoolsActionEntry as r, TDevtoolsPosition as s, ActionDevtoolsCore as t };
79
- //# sourceMappingURL=ActionDevtoolsCore-baIHExfj.d.mts.map
79
+ //# sourceMappingURL=ActionDevtoolsCore-BjbhFqc0.d.mts.map
@@ -1,4 +1,4 @@
1
- import { Pr as IRuntimeCoordinate, _ as TActionProgress } from "./ActionPayload.types-CQM1HRw_.cjs";
1
+ import { _ as TActionProgress, zr as IRuntimeCoordinate } from "./ActionPayload.types-DIOeVapm.cjs";
2
2
  //#region ../nice-devtools-shared/src/components/PanelChrome.d.ts
3
3
  /** Where a devtools panel is docked. */
4
4
  type TDevtoolsPosition = "dock-bottom" | "dock-top" | "dock-left" | "dock-right";
@@ -76,4 +76,4 @@ declare class ActionDevtoolsCore {
76
76
  }
77
77
  //#endregion
78
78
  export { TDevtoolsActionStatus as a, IDevtoolsObservableDomain as i, IActionDevtoolsCoreOptions as n, TDevtoolsListener as o, IDevtoolsActionEntry as r, TDevtoolsPosition as s, ActionDevtoolsCore as t };
79
- //# sourceMappingURL=ActionDevtoolsCore-dApyYvTS.d.cts.map
79
+ //# sourceMappingURL=ActionDevtoolsCore-kk7oZBv9.d.cts.map