experimental-a2 0.8.0 → 0.9.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 (182) hide show
  1. package/AGENTS.md +11 -0
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +29 -0
  4. package/dist/actor-client.d.ts +46 -0
  5. package/dist/actor-client.d.ts.map +1 -0
  6. package/dist/actor-client.js +54 -0
  7. package/dist/actor-client.js.map +1 -0
  8. package/dist/actor-react.d.ts +54 -0
  9. package/dist/actor-react.d.ts.map +1 -0
  10. package/dist/actor-react.js +79 -0
  11. package/dist/actor-react.js.map +1 -0
  12. package/dist/actor-shared-BACubf4x.d.ts +136 -0
  13. package/dist/actor-shared-BACubf4x.d.ts.map +1 -0
  14. package/dist/actor-shared-DI7J5upy.js +127 -0
  15. package/dist/actor-shared-DI7J5upy.js.map +1 -0
  16. package/dist/actor.browser.d.ts +1 -0
  17. package/dist/actor.browser.js +13 -0
  18. package/dist/actor.browser.js.map +1 -0
  19. package/dist/actor.d.ts +176 -0
  20. package/dist/actor.d.ts.map +1 -0
  21. package/dist/actor.js +437 -0
  22. package/dist/actor.js.map +1 -0
  23. package/dist/ai-server.d.ts +2 -2
  24. package/dist/ai-server.js +2 -2
  25. package/dist/ai.d.ts +2 -2
  26. package/dist/client.d.ts +1 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +4 -4
  29. package/dist/client.js.map +1 -1
  30. package/dist/{errors-BQuJpe82.js → errors-DCk6ch5n.js} +16 -2
  31. package/dist/{errors-BQuJpe82.js.map → errors-DCk6ch5n.js.map} +1 -1
  32. package/dist/{idempotent-replay-DuqEkYA7.js → idempotent-replay-DVOlyYbx.js} +2 -2
  33. package/dist/{idempotent-replay-DuqEkYA7.js.map → idempotent-replay-DVOlyYbx.js.map} +1 -1
  34. package/dist/index.d.ts +16 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -2
  37. package/dist/react.d.ts +1 -1
  38. package/dist/{contract-jIfaR085.d.ts → reducer-DJKWm3cp.d.ts} +39 -39
  39. package/dist/reducer-DJKWm3cp.d.ts.map +1 -0
  40. package/dist/scheduler-qstash.d.ts +2 -2
  41. package/dist/scheduler-qstash.js +2 -2
  42. package/dist/scheduler-vercel.d.ts +2 -2
  43. package/dist/scheduler-vercel.js +1 -1
  44. package/dist/{server-B2XNevQA.js → server-CBET-jSz.js} +6 -6
  45. package/dist/server-CBET-jSz.js.map +1 -0
  46. package/dist/{server-DjPhHnbI.d.ts → server-CKY3_lbw.d.ts} +3 -3
  47. package/dist/{server-DjPhHnbI.d.ts.map → server-CKY3_lbw.d.ts.map} +1 -1
  48. package/dist/server.d.ts +3 -3
  49. package/dist/server.js +1 -1
  50. package/dist/{store-RJO35BMj.d.ts → store-DGHeBtIQ.d.ts} +2 -2
  51. package/dist/{store-RJO35BMj.d.ts.map → store-DGHeBtIQ.d.ts.map} +1 -1
  52. package/dist/store-memory.d.ts +1 -1
  53. package/dist/store-memory.js +2 -2
  54. package/dist/store-postgres.d.ts +1 -1
  55. package/dist/store-postgres.js +2 -2
  56. package/dist/{store-redis-core-DT01r4GZ.js → store-redis-core-z-ykbyMg.js} +3 -3
  57. package/dist/{store-redis-core-DT01r4GZ.js.map → store-redis-core-z-ykbyMg.js.map} +1 -1
  58. package/dist/store-redis-http.d.ts +1 -1
  59. package/dist/store-redis-http.js +2 -2
  60. package/dist/store-redis.d.ts +1 -1
  61. package/dist/store-redis.js +2 -2
  62. package/dist/store-sqlite.d.ts +1 -1
  63. package/dist/store-sqlite.js +2 -2
  64. package/dist/{wire-B6te_wns.js → wire--yji6mO3.js} +2 -2
  65. package/dist/{wire-B6te_wns.js.map → wire--yji6mO3.js.map} +1 -1
  66. package/docs/actors/01-introduction.mdx +189 -0
  67. package/docs/actors/02-concurrency.mdx +154 -0
  68. package/docs/actors/03-timers.mdx +120 -0
  69. package/docs/actors/04-routes.mdx +352 -0
  70. package/docs/actors/meta.ts +1 -0
  71. package/docs/concepts/meta.ts +1 -0
  72. package/docs/guides/07-examples.mdx +56 -0
  73. package/docs/guides/meta.ts +1 -0
  74. package/docs/index.mdx +16 -0
  75. package/docs/reference/02-errors.mdx +33 -0
  76. package/docs/reference/meta.ts +1 -0
  77. package/examples/README.md +15 -0
  78. package/examples/playground/AGENTS.md +11 -0
  79. package/examples/playground/DEPLOY.md +106 -0
  80. package/examples/playground/README.md +19 -0
  81. package/examples/playground/activity-feed.test.ts +10 -0
  82. package/examples/playground/app/agent/[agentId]/agent-client.tsx +376 -0
  83. package/examples/playground/app/agent/[agentId]/page.tsx +29 -0
  84. package/examples/playground/app/agent/events/route.ts +4 -0
  85. package/examples/playground/app/agent/model.ts +3 -0
  86. package/examples/playground/app/agent/new-agent-session.tsx +98 -0
  87. package/examples/playground/app/agent/page.tsx +25 -0
  88. package/examples/playground/app/agent/scheduler/route.ts +5 -0
  89. package/examples/playground/app/agent/server.ts +153 -0
  90. package/examples/playground/app/agent/session.ts +13 -0
  91. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +682 -0
  92. package/examples/playground/app/canvas/[canvasId]/canvas-replay.test.ts +68 -0
  93. package/examples/playground/app/canvas/[canvasId]/canvas-replay.ts +19 -0
  94. package/examples/playground/app/canvas/[canvasId]/page.tsx +22 -0
  95. package/examples/playground/app/canvas/[canvasId]/session.ts +19 -0
  96. package/examples/playground/app/canvas/events/route.ts +13 -0
  97. package/examples/playground/app/canvas/model.ts +94 -0
  98. package/examples/playground/app/canvas/open-canvas.tsx +40 -0
  99. package/examples/playground/app/canvas/page.tsx +20 -0
  100. package/examples/playground/app/canvas/server.ts +9 -0
  101. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +118 -0
  102. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +316 -0
  103. package/examples/playground/app/chat/[chatId]/chat-client.tsx +922 -0
  104. package/examples/playground/app/chat/[chatId]/chat-view.test.ts +152 -0
  105. package/examples/playground/app/chat/[chatId]/chat-view.ts +101 -0
  106. package/examples/playground/app/chat/[chatId]/composer.test.ts +44 -0
  107. package/examples/playground/app/chat/[chatId]/composer.ts +30 -0
  108. package/examples/playground/app/chat/[chatId]/page.tsx +30 -0
  109. package/examples/playground/app/chat/[chatId]/session.ts +7 -0
  110. package/examples/playground/app/chat/events/route.ts +7 -0
  111. package/examples/playground/app/chat/model.test.ts +155 -0
  112. package/examples/playground/app/chat/model.ts +310 -0
  113. package/examples/playground/app/chat/new-conversation.tsx +16 -0
  114. package/examples/playground/app/chat/page.tsx +25 -0
  115. package/examples/playground/app/chat/scheduler/route.ts +5 -0
  116. package/examples/playground/app/chat/server.ts +184 -0
  117. package/examples/playground/app/components/activity-feed.tsx +54 -0
  118. package/examples/playground/app/components/connection-pill.tsx +29 -0
  119. package/examples/playground/app/counter/counter-client.tsx +72 -0
  120. package/examples/playground/app/counter/events/route.ts +4 -0
  121. package/examples/playground/app/counter/model.test.ts +36 -0
  122. package/examples/playground/app/counter/model.ts +31 -0
  123. package/examples/playground/app/counter/page.tsx +24 -0
  124. package/examples/playground/app/counter/server.ts +9 -0
  125. package/examples/playground/app/counter/session.ts +13 -0
  126. package/examples/playground/app/documents/[documentId]/code-editor.tsx +80 -0
  127. package/examples/playground/app/documents/[documentId]/document-client.tsx +525 -0
  128. package/examples/playground/app/documents/[documentId]/page.tsx +23 -0
  129. package/examples/playground/app/documents/[documentId]/session.ts +7 -0
  130. package/examples/playground/app/documents/events/route.ts +4 -0
  131. package/examples/playground/app/documents/model.ts +55 -0
  132. package/examples/playground/app/documents/open-document.tsx +40 -0
  133. package/examples/playground/app/documents/page.tsx +22 -0
  134. package/examples/playground/app/documents/server.ts +9 -0
  135. package/examples/playground/app/globals.css +2078 -0
  136. package/examples/playground/app/layout.tsx +44 -0
  137. package/examples/playground/app/orders/[orderId]/order-client.tsx +140 -0
  138. package/examples/playground/app/orders/[orderId]/page.tsx +29 -0
  139. package/examples/playground/app/orders/[orderId]/session.ts +11 -0
  140. package/examples/playground/app/orders/create/route.ts +30 -0
  141. package/examples/playground/app/orders/events/route.ts +4 -0
  142. package/examples/playground/app/orders/model.ts +79 -0
  143. package/examples/playground/app/orders/new-order-form.tsx +98 -0
  144. package/examples/playground/app/orders/page.tsx +22 -0
  145. package/examples/playground/app/orders/scheduler/route.ts +5 -0
  146. package/examples/playground/app/orders/server.ts +50 -0
  147. package/examples/playground/app/page.tsx +111 -0
  148. package/examples/playground/app/recovery/[recoveryId]/page.tsx +31 -0
  149. package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +144 -0
  150. package/examples/playground/app/recovery/[recoveryId]/session.ts +7 -0
  151. package/examples/playground/app/recovery/events/route.ts +3 -0
  152. package/examples/playground/app/recovery/model.ts +55 -0
  153. package/examples/playground/app/recovery/new-recovery-session.tsx +20 -0
  154. package/examples/playground/app/recovery/page.tsx +22 -0
  155. package/examples/playground/app/recovery/scheduler/route.ts +7 -0
  156. package/examples/playground/app/recovery/server.ts +53 -0
  157. package/examples/playground/app/recovery/start/route.ts +41 -0
  158. package/examples/playground/app/vault/[vaultId]/route.ts +19 -0
  159. package/examples/playground/app/vault/page.tsx +12 -0
  160. package/examples/playground/app/vault/server.ts +9 -0
  161. package/examples/playground/app/vault/vault-client.tsx +124 -0
  162. package/examples/playground/app/vault/vault.test.ts +147 -0
  163. package/examples/playground/app/vault/vault.ts +119 -0
  164. package/examples/playground/css.d.ts +4 -0
  165. package/examples/playground/lib/store.ts +15 -0
  166. package/examples/playground/next-env.d.ts +5 -0
  167. package/examples/playground/next.config.ts +10 -0
  168. package/examples/playground/package.json +46 -0
  169. package/examples/playground/tsconfig.json +37 -0
  170. package/examples/playground/vercel.json +40 -0
  171. package/package.json +11 -2
  172. package/src/actor-client.ts +132 -0
  173. package/src/actor-react.ts +143 -0
  174. package/src/actor-shared.ts +356 -0
  175. package/src/actor.browser.ts +12 -0
  176. package/src/actor.ts +914 -0
  177. package/src/client.ts +9 -1
  178. package/src/errors.ts +15 -0
  179. package/src/index.ts +1 -1
  180. package/src/server.ts +13 -3
  181. package/dist/contract-jIfaR085.d.ts.map +0 -1
  182. package/dist/server-B2XNevQA.js.map +0 -1
@@ -0,0 +1,4 @@
1
+ import { counterServer } from '../server'
2
+
3
+ export const GET = counterServer.fetch
4
+ export const POST = counterServer.fetch
@@ -0,0 +1,36 @@
1
+ import type { ContractEvent } from 'experimental-a2'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { counter, counterReducer } from './model'
4
+
5
+ type CounterEvents = typeof counter.events
6
+ type CounterEvent<
7
+ K extends keyof CounterEvents & string = keyof CounterEvents & string,
8
+ > = ContractEvent<CounterEvents, K>
9
+
10
+ const event = <K extends keyof CounterEvents & string>(
11
+ index: number,
12
+ type: K,
13
+ ): CounterEvent<K> =>
14
+ ({
15
+ id: `event-${index}`,
16
+ type,
17
+ payload: {},
18
+ index,
19
+ sessionId: 'shared',
20
+ createdAt: new Date(index * 1_000),
21
+ }) as CounterEvent<K>
22
+
23
+ describe('counter reducer', () => {
24
+ it('folds increments and decrements in log order', () => {
25
+ const state = [
26
+ event(1, 'incremented'),
27
+ event(2, 'incremented'),
28
+ event(3, 'decremented'),
29
+ ].reduce(
30
+ (current, nextEvent) => counterReducer.fold(current, nextEvent),
31
+ counterReducer.initialState,
32
+ )
33
+
34
+ expect(state).toEqual({ value: 1 })
35
+ })
36
+ })
@@ -0,0 +1,31 @@
1
+ import * as a2 from 'experimental-a2'
2
+ import { z } from 'zod'
3
+
4
+ export const counter = a2.contract({
5
+ name: 'counter',
6
+ events: {
7
+ incremented: z.object({}),
8
+ decremented: z.object({}),
9
+ },
10
+ })
11
+
12
+ const counterState = z.object({ value: z.number().int() })
13
+
14
+ export type CounterState = z.output<typeof counterState>
15
+
16
+ export const counterReducer = counter
17
+ .reducer({
18
+ name: 'counter-value',
19
+ initialState: { value: 0 },
20
+ stateSchema: counterState,
21
+ })
22
+ .fold((state, event) => {
23
+ switch (event.type) {
24
+ case 'incremented':
25
+ return { value: state.value + 1 }
26
+ case 'decremented':
27
+ return { value: state.value - 1 }
28
+ default:
29
+ return state
30
+ }
31
+ })
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react'
2
+ import { counterReducer } from './model'
3
+ import { counterServer } from './server'
4
+ import { CounterClient } from './counter-client'
5
+ import { SessionProvider } from './session'
6
+
7
+ export const dynamic = 'force-dynamic'
8
+
9
+ const COUNTER_SESSION_ID = 'shared'
10
+
11
+ export default async function CounterPage(): Promise<ReactNode> {
12
+ const session = counterServer.session(COUNTER_SESSION_ID)
13
+ const { state, index } = await session.state(counterReducer)
14
+
15
+ return (
16
+ <SessionProvider
17
+ sessionId={COUNTER_SESSION_ID}
18
+ initialState={state}
19
+ initialIndex={index}
20
+ >
21
+ <CounterClient />
22
+ </SessionProvider>
23
+ )
24
+ }
@@ -0,0 +1,9 @@
1
+ import 'server-only'
2
+ import { createServer } from 'experimental-a2/server'
3
+ import { store } from '@/lib/store'
4
+ import { counter } from './model'
5
+
6
+ export const counterServer = createServer({
7
+ ...(store ? { store } : {}),
8
+ contract: counter,
9
+ })
@@ -0,0 +1,13 @@
1
+ 'use client'
2
+ import { createClient } from 'experimental-a2/client'
3
+ import { createReact } from 'experimental-a2/react'
4
+ import { counterReducer } from './model'
5
+
6
+ export const counterClient = createClient({
7
+ reducer: counterReducer,
8
+ api: '/counter/events',
9
+ })
10
+
11
+ export const { SessionProvider, useSession } = createReact({
12
+ client: counterClient,
13
+ })
@@ -0,0 +1,80 @@
1
+ 'use client'
2
+ import type { ReactNode } from 'react'
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import { basicSetup } from 'codemirror'
5
+ import { Compartment, EditorState } from '@codemirror/state'
6
+ import { EditorView, placeholder } from '@codemirror/view'
7
+ import { yCollab } from 'y-codemirror.next'
8
+ import type { Awareness } from 'y-protocols/awareness'
9
+ import type * as Y from 'yjs'
10
+
11
+ const bodyPlaceholder =
12
+ 'Start writing. Open this URL in another tab to collaborate…'
13
+
14
+ export function CodeMirrorEditor({
15
+ doc,
16
+ awareness,
17
+ disabled,
18
+ }: {
19
+ doc: Y.Doc
20
+ awareness: Awareness
21
+ disabled: boolean
22
+ }): ReactNode {
23
+ const host = useRef<HTMLDivElement>(null)
24
+ const view = useRef<EditorView | null>(null)
25
+ const [editable] = useState(() => new Compartment())
26
+
27
+ useEffect(() => {
28
+ if (!host.current) return
29
+
30
+ const body = doc.getText('body')
31
+ const editor = new EditorView({
32
+ parent: host.current,
33
+ state: EditorState.create({
34
+ doc: body.toString(),
35
+ extensions: [
36
+ basicSetup,
37
+ EditorView.lineWrapping,
38
+ EditorView.contentAttributes.of({
39
+ 'aria-label': 'Document body',
40
+ spellcheck: 'true',
41
+ }),
42
+ placeholder(bodyPlaceholder),
43
+ // Awareness rides the session's presence plane: cursors and
44
+ // selections replicate last-writer-wins and expire, while only
45
+ // document changes enter the durable log.
46
+ yCollab(body, awareness),
47
+ // Upstream keeps the caret when the editor blurs (its clear
48
+ // branch requires focus, so it never runs); an unfocused
49
+ // editor should show no phantom caret.
50
+ EditorView.updateListener.of((update) => {
51
+ if (update.focusChanged && !update.view.hasFocus) {
52
+ awareness.setLocalStateField('cursor', null)
53
+ }
54
+ }),
55
+ editable.of([
56
+ EditorState.readOnly.of(disabled),
57
+ EditorView.editable.of(!disabled),
58
+ ]),
59
+ ],
60
+ }),
61
+ })
62
+ view.current = editor
63
+
64
+ return () => {
65
+ editor.destroy()
66
+ view.current = null
67
+ }
68
+ }, [doc, awareness, editable])
69
+
70
+ useEffect(() => {
71
+ view.current?.dispatch({
72
+ effects: editable.reconfigure([
73
+ EditorState.readOnly.of(disabled),
74
+ EditorView.editable.of(!disabled),
75
+ ]),
76
+ })
77
+ }, [disabled, editable])
78
+
79
+ return <div className="document-body" ref={host} />
80
+ }