experimental-a2 0.9.0 → 0.11.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 (123) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/actor-D_54lz_1.d.ts +310 -0
  3. package/dist/actor-D_54lz_1.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +13 -4
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +63 -7
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -4
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +16 -2
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/actor.d.ts +2 -176
  13. package/dist/actor.js +13 -2
  14. package/dist/actor.js.map +1 -1
  15. package/dist/ai-server.d.ts +1 -1
  16. package/dist/ai-server.js +2 -2
  17. package/dist/ai.d.ts +1 -1
  18. package/dist/ai.js +1 -1
  19. package/dist/client-Bf6uSEAk.js +1342 -0
  20. package/dist/client-Bf6uSEAk.js.map +1 -0
  21. package/dist/client-P_NNNRM-.d.ts +243 -0
  22. package/dist/client-P_NNNRM-.d.ts.map +1 -0
  23. package/dist/client.d.ts +2 -202
  24. package/dist/client.js +2 -1026
  25. package/dist/errors-DCk6ch5n.js.map +1 -1
  26. package/dist/errors-DvhSXnxk.d.ts +28 -0
  27. package/dist/errors-DvhSXnxk.d.ts.map +1 -0
  28. package/dist/index.d.ts +3 -35
  29. package/dist/{internal-DRXJ56EI.js → internal-Dq2qYxou.js} +2 -2
  30. package/dist/{internal-DRXJ56EI.js.map → internal-Dq2qYxou.js.map} +1 -1
  31. package/dist/platform-B4TnJtWu.js +34 -0
  32. package/dist/platform-B4TnJtWu.js.map +1 -0
  33. package/dist/react.d.ts +3 -1
  34. package/dist/react.d.ts.map +1 -1
  35. package/dist/react.js +2 -1
  36. package/dist/react.js.map +1 -1
  37. package/dist/scheduler-qstash.d.ts +2 -2
  38. package/dist/scheduler-qstash.js +3 -2
  39. package/dist/scheduler-qstash.js.map +1 -1
  40. package/dist/scheduler-vercel.d.ts +2 -2
  41. package/dist/scheduler-vercel.js +2 -2
  42. package/dist/{server-CBET-jSz.js → server-Dkz2a84E.js} +295 -133
  43. package/dist/server-Dkz2a84E.js.map +1 -0
  44. package/dist/{server-CKY3_lbw.d.ts → server-DwPrMqHB.d.ts} +4 -2
  45. package/dist/server-DwPrMqHB.d.ts.map +1 -0
  46. package/dist/server.d.ts +2 -2
  47. package/dist/server.js +1 -1
  48. package/dist/{store-DGHeBtIQ.d.ts → store-DtDOWLSn.d.ts} +4 -5
  49. package/dist/{store-DGHeBtIQ.d.ts.map → store-DtDOWLSn.d.ts.map} +1 -1
  50. package/dist/store-N8PXxDAS.js.map +1 -1
  51. package/dist/store-memory.d.ts +1 -1
  52. package/dist/store-memory.js +1 -1
  53. package/dist/{store-polling-6DW7F1DT.js → store-polling-CmxUbV93.js} +56 -7
  54. package/dist/store-polling-CmxUbV93.js.map +1 -0
  55. package/dist/store-postgres.d.ts +6 -4
  56. package/dist/store-postgres.d.ts.map +1 -1
  57. package/dist/store-postgres.js +701 -53
  58. package/dist/store-postgres.js.map +1 -1
  59. package/dist/store-presence-polling-C7-XZyW9.js +94 -0
  60. package/dist/store-presence-polling-C7-XZyW9.js.map +1 -0
  61. package/dist/store-redis-http.d.ts +2 -2
  62. package/dist/store-redis-http.d.ts.map +1 -1
  63. package/dist/store-redis-http.js +184 -38
  64. package/dist/store-redis-http.js.map +1 -1
  65. package/dist/{store-redis-core-z-ykbyMg.js → store-redis-notify-BUCyXOn0.js} +491 -27
  66. package/dist/store-redis-notify-BUCyXOn0.js.map +1 -0
  67. package/dist/store-redis.d.ts +5 -13
  68. package/dist/store-redis.d.ts.map +1 -1
  69. package/dist/store-redis.js +27 -271
  70. package/dist/store-redis.js.map +1 -1
  71. package/dist/store-sqlite.d.ts +1 -1
  72. package/dist/store-sqlite.js +1 -1
  73. package/dist/{wire--yji6mO3.js → wire-BO5wWCb1.js} +18 -16
  74. package/dist/wire-BO5wWCb1.js.map +1 -0
  75. package/docs/actors/04-routes.mdx +40 -8
  76. package/docs/guides/03-react.mdx +52 -10
  77. package/docs/guides/05-production.mdx +54 -5
  78. package/docs/guides/09-presence.mdx +12 -7
  79. package/docs/guides/10-transports.mdx +85 -22
  80. package/docs/reference/01-api.mdx +90 -28
  81. package/docs/reference/02-errors.mdx +45 -11
  82. package/examples/playground/package.json +1 -1
  83. package/package.json +1 -1
  84. package/src/actor-client.ts +114 -14
  85. package/src/actor-react.ts +18 -3
  86. package/src/actor.ts +16 -1
  87. package/src/client-errors.ts +51 -0
  88. package/src/client.ts +577 -175
  89. package/src/errors.ts +4 -9
  90. package/src/index.ts +1 -1
  91. package/src/internal.ts +1 -1
  92. package/src/postgres-notification-scope.ts +134 -0
  93. package/src/postgres-notifications.ts +244 -0
  94. package/src/postgres-pool.ts +65 -0
  95. package/src/postgres-resources.ts +185 -0
  96. package/src/presence-recovery.ts +120 -0
  97. package/src/react.ts +3 -0
  98. package/src/redis-http-subscriptions.ts +199 -0
  99. package/src/server-fetch.ts +51 -23
  100. package/src/server.ts +146 -19
  101. package/src/session-socket.ts +241 -88
  102. package/src/sse.ts +17 -0
  103. package/src/store-polling.ts +32 -12
  104. package/src/store-postgres.ts +292 -75
  105. package/src/store-presence-polling.ts +125 -0
  106. package/src/store-redis-core.ts +37 -30
  107. package/src/store-redis-http.ts +51 -45
  108. package/src/store-redis-notify.ts +464 -0
  109. package/src/store-redis.ts +9 -364
  110. package/src/store.ts +3 -4
  111. package/src/stream-activity.ts +32 -0
  112. package/src/wire.ts +39 -15
  113. package/dist/actor-shared-BACubf4x.d.ts +0 -136
  114. package/dist/actor-shared-BACubf4x.d.ts.map +0 -1
  115. package/dist/actor.d.ts.map +0 -1
  116. package/dist/client.d.ts.map +0 -1
  117. package/dist/client.js.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/server-CBET-jSz.js.map +0 -1
  120. package/dist/server-CKY3_lbw.d.ts.map +0 -1
  121. package/dist/store-polling-6DW7F1DT.js.map +0 -1
  122. package/dist/store-redis-core-z-ykbyMg.js.map +0 -1
  123. package/dist/wire--yji6mO3.js.map +0 -1
@@ -41,6 +41,7 @@ import type { A2Client, Connection } from './client.ts'
41
41
  import type { ContractEvent, EventDefs, PresenceDefs } from './contract.ts'
42
42
  import type { Reducer } from './reducer.ts'
43
43
  import { useSession } from './react.ts'
44
+ import { useMemo } from 'react'
44
45
 
45
46
  /** The server-to-client handoff: `await def.actor(id).state()`. */
46
47
  export type ActorSnapshot<S> = { state: S; index: number }
@@ -66,13 +67,14 @@ type PresenceOf<D extends AnyActorDefinition> = D extends {
66
67
  : never
67
68
 
68
69
  export type UseActorResult<D extends AnyActorDefinition, View> = {
69
- /** The actor's state, live: every commit streams in and replaces it. */
70
+ /** The live view, advanced by call answers, refreshes, and stream commits. */
70
71
  state: View
71
- /** The stream frontier. */
72
+ /** The latest adopted state index. */
72
73
  index: number
73
74
  connection: Connection
74
75
  /** Typed calls over POST — the response is the answer. */
75
76
  call: ActorClientCall<D, View>
77
+ refresh: () => Promise<ActorSnapshot<View>>
76
78
  /** The state-commit feed this browser has observed. */
77
79
  events: ContractEvent<EventDefs>[]
78
80
  /**
@@ -128,12 +130,25 @@ export function useActor<D extends AnyActorDefinition, View = ActorStateOf<D>>(
128
130
  ? {}
129
131
  : { participant: options.participant }),
130
132
  })
131
- const { call } = createActorClient<D, View>({ api })
133
+ const { call, state: refresh } = useMemo(
134
+ () =>
135
+ createActorClient<D, View>({
136
+ api,
137
+ onSnapshot: ({ state, index }) => {
138
+ client.session(options.id, {
139
+ initialState: state,
140
+ initialIndex: index,
141
+ })
142
+ },
143
+ }),
144
+ [api, client, options.id],
145
+ )
132
146
  return {
133
147
  state: session.state as View,
134
148
  index: session.index,
135
149
  connection: session.connection,
136
150
  call,
151
+ refresh,
137
152
  events: session.events,
138
153
  presence: session.presence as ActorPresenceMap<PresenceOf<D>>,
139
154
  setPresence: session.setPresence as (
package/src/actor.ts CHANGED
@@ -169,9 +169,10 @@ export type ActorOptions<D extends ActorProtocol> = {
169
169
  /**
170
170
  * One HTTP operation `handle.fetch` is about to serve — the actor
171
171
  * mirror of core's `A2Operation`. `authorize` sees it before any
172
- * write or subscription; returning `false` answers 403.
172
+ * read, write, or subscription; returning `false` answers 403.
173
173
  */
174
174
  export type ActorOperation =
175
+ | { readonly type: 'state'; readonly id: string }
175
176
  | {
176
177
  readonly type: 'stream'
177
178
  readonly id: string
@@ -736,6 +737,19 @@ export function actor<D extends ActorProtocol>(
736
737
 
737
738
  if (request.method === 'GET') {
738
739
  const url = new URL(request.url)
740
+ if (url.searchParams.has('snapshot')) {
741
+ if (!(await authorized({ type: 'state', id }))) return forbidden()
742
+ const result = await readState()
743
+ return Response.json(
744
+ {
745
+ state: fetchOptions?.view
746
+ ? applyView(fetchOptions.view, result.state)
747
+ : result.state,
748
+ index: result.index,
749
+ },
750
+ { headers: { 'cache-control': 'private, no-store' } },
751
+ )
752
+ }
739
753
  const raw = Number(url.searchParams.get('index'))
740
754
  const startAfter = Number.isSafeInteger(raw) && raw >= 0 ? raw : 0
741
755
  if (!(await authorized({ type: 'stream', id, startAfter }))) {
@@ -851,6 +865,7 @@ export function actor<D extends ActorProtocol>(
851
865
  index: result.index,
852
866
  }
853
867
  : result,
868
+ { headers: { 'cache-control': 'private, no-store' } },
854
869
  )
855
870
  } catch (error) {
856
871
  if (error instanceof ActorRefusedError) {
@@ -0,0 +1,51 @@
1
+ import { A2Error, type A2ErrorCode } from './errors.ts'
2
+
3
+ export type A2ClientErrorOptions = {
4
+ details?: unknown
5
+ cause?: unknown
6
+ status?: number
7
+ closeCode?: number
8
+ closeReason?: string
9
+ phase?: 'stream' | 'subscribe'
10
+ timeoutMs?: number
11
+ }
12
+
13
+ export class A2ClientError extends A2Error {
14
+ readonly status: number | undefined
15
+ readonly closeCode: number | undefined
16
+ readonly closeReason: string | undefined
17
+ readonly phase: 'stream' | 'subscribe' | undefined
18
+ readonly timeoutMs: number | undefined
19
+
20
+ constructor(
21
+ code: A2ErrorCode,
22
+ message: string,
23
+ options?: A2ClientErrorOptions,
24
+ ) {
25
+ super(code, message, options)
26
+ this.name = 'A2ClientError'
27
+ this.status = options?.status
28
+ this.closeCode = options?.closeCode
29
+ this.closeReason = options?.closeReason
30
+ this.phase = options?.phase
31
+ this.timeoutMs = options?.timeoutMs
32
+ }
33
+ }
34
+
35
+ export function asClientError(
36
+ error: unknown,
37
+ options?: { code?: A2ErrorCode },
38
+ ): A2ClientError {
39
+ if (error instanceof A2ClientError) return error
40
+ if (error instanceof A2Error) {
41
+ return new A2ClientError(error.code, error.message, {
42
+ details: error.details,
43
+ cause: error,
44
+ })
45
+ }
46
+ return new A2ClientError(
47
+ options?.code ?? 'UNKNOWN',
48
+ error instanceof Error ? error.message : 'client operation failed',
49
+ { cause: error },
50
+ )
51
+ }