@livestore/livestore 0.4.0-dev.21 → 0.4.0-dev.23

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 (216) hide show
  1. package/README.md +0 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.js +1 -1
  4. package/dist/QueryCache.js.map +1 -1
  5. package/dist/SqliteDbWrapper.d.ts +5 -5
  6. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  7. package/dist/SqliteDbWrapper.js +8 -8
  8. package/dist/SqliteDbWrapper.js.map +1 -1
  9. package/dist/SqliteDbWrapper.test.js +2 -2
  10. package/dist/SqliteDbWrapper.test.js.map +1 -1
  11. package/dist/effect/LiveStore.d.ts +130 -2
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +185 -6
  14. package/dist/effect/LiveStore.js.map +1 -1
  15. package/dist/effect/LiveStore.test.d.ts +2 -0
  16. package/dist/effect/LiveStore.test.d.ts.map +1 -0
  17. package/dist/effect/LiveStore.test.js +42 -0
  18. package/dist/effect/LiveStore.test.js.map +1 -0
  19. package/dist/effect/mod.d.ts +1 -1
  20. package/dist/effect/mod.d.ts.map +1 -1
  21. package/dist/effect/mod.js +3 -1
  22. package/dist/effect/mod.js.map +1 -1
  23. package/dist/live-queries/base-class.d.ts +3 -3
  24. package/dist/live-queries/base-class.d.ts.map +1 -1
  25. package/dist/live-queries/base-class.js +2 -2
  26. package/dist/live-queries/base-class.js.map +1 -1
  27. package/dist/live-queries/client-document-get-query.d.ts +1 -1
  28. package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
  29. package/dist/live-queries/client-document-get-query.js +1 -1
  30. package/dist/live-queries/client-document-get-query.js.map +1 -1
  31. package/dist/live-queries/computed.d.ts.map +1 -1
  32. package/dist/live-queries/computed.js +2 -2
  33. package/dist/live-queries/computed.js.map +1 -1
  34. package/dist/live-queries/db-query.js +14 -14
  35. package/dist/live-queries/db-query.js.map +1 -1
  36. package/dist/live-queries/db-query.test.js +2 -2
  37. package/dist/live-queries/db-query.test.js.map +1 -1
  38. package/dist/live-queries/signal.test.js +2 -2
  39. package/dist/live-queries/signal.test.js.map +1 -1
  40. package/dist/mod.d.ts +2 -1
  41. package/dist/mod.d.ts.map +1 -1
  42. package/dist/mod.js +1 -0
  43. package/dist/mod.js.map +1 -1
  44. package/dist/reactive.d.ts +9 -9
  45. package/dist/reactive.d.ts.map +1 -1
  46. package/dist/reactive.js +9 -26
  47. package/dist/reactive.js.map +1 -1
  48. package/dist/reactive.test.js +2 -2
  49. package/dist/reactive.test.js.map +1 -1
  50. package/dist/store/StoreRegistry.d.ts +215 -0
  51. package/dist/store/StoreRegistry.d.ts.map +1 -0
  52. package/dist/store/StoreRegistry.js +267 -0
  53. package/dist/store/StoreRegistry.js.map +1 -0
  54. package/dist/store/StoreRegistry.test.d.ts +2 -0
  55. package/dist/store/StoreRegistry.test.d.ts.map +1 -0
  56. package/dist/store/StoreRegistry.test.js +381 -0
  57. package/dist/store/StoreRegistry.test.js.map +1 -0
  58. package/dist/store/create-store.d.ts +56 -6
  59. package/dist/store/create-store.d.ts.map +1 -1
  60. package/dist/store/create-store.js +32 -7
  61. package/dist/store/create-store.js.map +1 -1
  62. package/dist/store/devtools.d.ts +1 -1
  63. package/dist/store/devtools.d.ts.map +1 -1
  64. package/dist/store/devtools.js +16 -3
  65. package/dist/store/devtools.js.map +1 -1
  66. package/dist/store/store-eventstream.test.js +2 -2
  67. package/dist/store/store-eventstream.test.js.map +1 -1
  68. package/dist/store/store-types.d.ts +59 -9
  69. package/dist/store/store-types.d.ts.map +1 -1
  70. package/dist/store/store-types.js.map +1 -1
  71. package/dist/store/store-types.test.js +1 -1
  72. package/dist/store/store-types.test.js.map +1 -1
  73. package/dist/store/store.d.ts +102 -6
  74. package/dist/store/store.d.ts.map +1 -1
  75. package/dist/store/store.js +148 -47
  76. package/dist/store/store.js.map +1 -1
  77. package/dist/utils/dev.js.map +1 -1
  78. package/dist/utils/stack-info.js +2 -2
  79. package/dist/utils/stack-info.js.map +1 -1
  80. package/dist/utils/tests/fixture.d.ts +1 -1
  81. package/dist/utils/tests/fixture.d.ts.map +1 -1
  82. package/dist/utils/tests/fixture.js.map +1 -1
  83. package/dist/utils/tests/otel.d.ts.map +1 -1
  84. package/dist/utils/tests/otel.js +5 -5
  85. package/dist/utils/tests/otel.js.map +1 -1
  86. package/package.json +59 -18
  87. package/src/QueryCache.ts +1 -1
  88. package/src/SqliteDbWrapper.test.ts +4 -2
  89. package/src/SqliteDbWrapper.ts +12 -11
  90. package/src/ambient.d.ts +0 -7
  91. package/src/effect/LiveStore.test.ts +61 -0
  92. package/src/effect/LiveStore.ts +381 -8
  93. package/src/effect/mod.ts +13 -1
  94. package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
  95. package/src/live-queries/base-class.ts +7 -6
  96. package/src/live-queries/client-document-get-query.ts +4 -2
  97. package/src/live-queries/computed.ts +3 -2
  98. package/src/live-queries/db-query.test.ts +3 -2
  99. package/src/live-queries/db-query.ts +15 -15
  100. package/src/live-queries/signal.test.ts +3 -2
  101. package/src/mod.ts +2 -0
  102. package/src/reactive.test.ts +3 -2
  103. package/src/reactive.ts +22 -23
  104. package/src/store/StoreRegistry.test.ts +540 -0
  105. package/src/store/StoreRegistry.ts +418 -0
  106. package/src/store/create-store.ts +76 -15
  107. package/src/store/devtools.ts +20 -6
  108. package/src/store/store-eventstream.test.ts +4 -2
  109. package/src/store/store-types.test.ts +3 -1
  110. package/src/store/store-types.ts +64 -13
  111. package/src/store/store.ts +197 -60
  112. package/src/utils/dev.ts +2 -2
  113. package/src/utils/stack-info.ts +2 -2
  114. package/src/utils/tests/fixture.ts +2 -1
  115. package/src/utils/tests/otel.ts +8 -7
  116. package/docs/api/index.md +0 -3
  117. package/docs/building-with-livestore/complex-ui-state/index.md +0 -5
  118. package/docs/building-with-livestore/crud/index.md +0 -5
  119. package/docs/building-with-livestore/data-modeling/index.md +0 -1
  120. package/docs/building-with-livestore/debugging/index.md +0 -17
  121. package/docs/building-with-livestore/devtools/index.md +0 -79
  122. package/docs/building-with-livestore/events/index.md +0 -355
  123. package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
  124. package/docs/building-with-livestore/examples/index.md +0 -30
  125. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -891
  126. package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
  127. package/docs/building-with-livestore/opentelemetry/index.md +0 -208
  128. package/docs/building-with-livestore/production-checklist/index.md +0 -5
  129. package/docs/building-with-livestore/reactivity-system/index.md +0 -202
  130. package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
  131. package/docs/building-with-livestore/state/materializers/index.md +0 -300
  132. package/docs/building-with-livestore/state/sql-queries/index.md +0 -72
  133. package/docs/building-with-livestore/state/sqlite/index.md +0 -45
  134. package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
  135. package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
  136. package/docs/building-with-livestore/store/index.md +0 -281
  137. package/docs/building-with-livestore/syncing/index.md +0 -136
  138. package/docs/building-with-livestore/tools/cli/index.md +0 -177
  139. package/docs/building-with-livestore/tools/mcp/index.md +0 -187
  140. package/docs/examples/cloudflare-adapter/index.md +0 -44
  141. package/docs/examples/expo-adapter/index.md +0 -44
  142. package/docs/examples/index.md +0 -55
  143. package/docs/examples/node-adapter/index.md +0 -44
  144. package/docs/examples/web-adapter/index.md +0 -52
  145. package/docs/framework-integrations/custom-elements/index.md +0 -142
  146. package/docs/framework-integrations/react-integration/index.md +0 -918
  147. package/docs/framework-integrations/solid-integration/index.md +0 -293
  148. package/docs/framework-integrations/svelte-integration/index.md +0 -42
  149. package/docs/framework-integrations/vue-integration/index.md +0 -294
  150. package/docs/getting-started/expo/index.md +0 -736
  151. package/docs/getting-started/node/index.md +0 -115
  152. package/docs/getting-started/react-web/index.md +0 -573
  153. package/docs/getting-started/solid/index.md +0 -3
  154. package/docs/getting-started/vue/index.md +0 -471
  155. package/docs/index.md +0 -209
  156. package/docs/llms.txt +0 -147
  157. package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
  158. package/docs/misc/FAQ/index.md +0 -37
  159. package/docs/misc/community/index.md +0 -88
  160. package/docs/misc/credits/index.md +0 -14
  161. package/docs/misc/design-partners/index.md +0 -13
  162. package/docs/misc/package-management/index.md +0 -21
  163. package/docs/misc/performance/index.md +0 -25
  164. package/docs/misc/resources/index.md +0 -46
  165. package/docs/misc/state-of-the-project/index.md +0 -37
  166. package/docs/misc/troubleshooting/index.md +0 -82
  167. package/docs/overview/concepts/index.md +0 -78
  168. package/docs/overview/how-livestore-works/index.md +0 -56
  169. package/docs/overview/introduction/index.md +0 -5
  170. package/docs/overview/technology-comparison/index.md +0 -40
  171. package/docs/overview/when-livestore/index.md +0 -81
  172. package/docs/overview/why-livestore/index.md +0 -5
  173. package/docs/patterns/ai/index.md +0 -15
  174. package/docs/patterns/anonymous-user-transition/index.md +0 -10
  175. package/docs/patterns/app-evolution/index.md +0 -72
  176. package/docs/patterns/auth/index.md +0 -226
  177. package/docs/patterns/effect/index.md +0 -1495
  178. package/docs/patterns/encryption/index.md +0 -6
  179. package/docs/patterns/external-data/index.md +0 -5
  180. package/docs/patterns/file-management/index.md +0 -11
  181. package/docs/patterns/file-structure/index.md +0 -14
  182. package/docs/patterns/list-ordering/index.md +0 -369
  183. package/docs/patterns/offline/index.md +0 -32
  184. package/docs/patterns/orm/index.md +0 -18
  185. package/docs/patterns/presence/index.md +0 -11
  186. package/docs/patterns/rich-text-editing/index.md +0 -11
  187. package/docs/patterns/server-side-clients/index.md +0 -97
  188. package/docs/patterns/side-effects/index.md +0 -11
  189. package/docs/patterns/state-machines/index.md +0 -11
  190. package/docs/patterns/storybook/index.md +0 -192
  191. package/docs/patterns/undo-redo/index.md +0 -9
  192. package/docs/patterns/version-control/index.md +0 -8
  193. package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
  194. package/docs/platform-adapters/electron-adapter/index.md +0 -15
  195. package/docs/platform-adapters/expo-adapter/index.md +0 -245
  196. package/docs/platform-adapters/node-adapter/index.md +0 -160
  197. package/docs/platform-adapters/tauri-adapter/index.md +0 -15
  198. package/docs/platform-adapters/web-adapter/index.md +0 -218
  199. package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
  200. package/docs/sustainable-open-source/contributing/info/index.md +0 -63
  201. package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
  202. package/docs/sustainable-open-source/sponsoring/index.md +0 -104
  203. package/docs/sync-providers/cloudflare/index.md +0 -773
  204. package/docs/sync-providers/custom/index.md +0 -65
  205. package/docs/sync-providers/electricsql/index.md +0 -159
  206. package/docs/sync-providers/s2/index.md +0 -230
  207. package/docs/tutorial/0-welcome/index.md +0 -48
  208. package/docs/tutorial/1-setup-starter-project/index.md +0 -105
  209. package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
  210. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -511
  211. package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
  212. package/docs/tutorial/5-expand-business-logic/index.md +0 -174
  213. package/docs/tutorial/6-persist-ui-state/index.md +0 -453
  214. package/docs/tutorial/7-next-steps/index.md +0 -22
  215. package/docs/understanding-livestore/design-decisions/index.md +0 -33
  216. package/docs/understanding-livestore/event-sourcing/index.md +0 -40
@@ -1,65 +0,0 @@
1
- # Build your own sync provider
2
-
3
- It's very straightforward to implement your own sync provider. A sync provider implementation needs to do the following:
4
-
5
- ## Client-side
6
-
7
- Implement the `SyncBackend` interface (running in the client) which describes the protocol for syncing events between the client and the server.
8
-
9
- ```ts
10
- // Slightly simplified API (see packages/@livestore/common/src/sync/sync.ts for the full API)
11
- export type SyncBackend = {
12
- pull: (cursor: EventSequenceNumber) => Stream<{ batch: LiveStoreEvent[] }, InvalidPullError>
13
- push: (batch: LiveStoreEvent[]) => Effect<void, InvalidPushError>
14
- }
15
-
16
- // my-sync-backend.ts
17
- const makeMySyncBackend = (args: { /* ... */ }) => {
18
- return {
19
- pull: (cursor) => {
20
- // ...
21
- },
22
- push: (batch) => {
23
- // ...
24
- }
25
- }
26
- }
27
-
28
- // my-app.ts
29
- const adapter = makeAdapter({
30
- sync: {
31
- backend: makeMySyncBackend({ /* ... */ })
32
- }
33
- })
34
- ```
35
-
36
- The actual implementation of those methods is left to the developer and mostly depends on the network protocol used to communicate between the client and the server.
37
-
38
- Ideally this implementation considers the following:
39
-
40
- - Network connectivity (offline, unstable connection, etc.)
41
- - Ordering of events in case of out-of-order delivery
42
- - Backoff and retry logic
43
-
44
- ## Server-side
45
-
46
- Implement the actual sync backend protocol (running in the server). At minimum this sync backend needs to do the following:
47
-
48
- - For client `push` requests:
49
- - Validate the batch of events
50
- - Ensure the batch sequence numbers are in ascending order and larger than the sync backend head
51
- - Further validation checks (e.g. schema-aware payload validation)
52
- - Persist the events in the event store (implying a new sync backend head equal to the sequence number of the pushed last event)
53
- - Return a success response
54
- - It's important that the server only processes one push request at a time to ensure a total ordering of events.
55
-
56
- - For client `pull` requests:
57
- - Validate the cursor
58
- - Query the events from the database
59
- - Return the events to the client
60
- - This can be done in a batch or streamed to the client
61
- - `pull` requests can be handled in parallel by the server
62
-
63
- ## General recommendations
64
-
65
- It's recommended to study the existing sync backend implementations for inspiration.
@@ -1,159 +0,0 @@
1
- # ElectricSQL
2
-
3
- The `@livestore/sync-electric` package lets you sync LiveStore with ElectricSQL.
4
-
5
- - Package: `pnpm add @livestore/sync-electric`
6
- - Protocol: HTTP push/pull with long-polling support
7
-
8
- ## Architecture
9
-
10
- ```mermaid
11
- graph LR
12
- subgraph Browser
13
- LS[LiveStore Client]
14
- end
15
-
16
- subgraph "Your Server"
17
- AP[API Proxy<br/>'/api/electric']
18
- end
19
-
20
- subgraph "Infrastructure"
21
- ES[Electric Server<br/>':30000']
22
- PG[(Postgres DB)]
23
- end
24
-
25
- LS -->|"GET (pull)"| AP
26
- LS -->|"POST (push)"| AP
27
- AP -->|"Pull requests<br/>(proxied)"| ES
28
- AP -->|"Push events<br/>(direct write)"| PG
29
- ES -->|"Listen"| PG
30
-
31
- style LS fill:#e1f5fe
32
- style AP fill:#fff3e0
33
- style ES fill:#f3e5f5
34
- style PG fill:#e8f5e9
35
- ```
36
-
37
- The API proxy has dual responsibilities:
38
- - **Push Events**: Writes events directly to Postgres tables (bypasses Electric)
39
- - **Pull Requests**: Proxies to Electric server for reading events
40
- - **Authentication**: Implements your custom auth logic
41
- - **Database Management**: Initializes tables and manages connections
42
-
43
- ## Client setup
44
-
45
- Basic usage in your worker/server code:
46
-
47
- ## `reference/syncing/sync-provider/electricsql/client-setup.ts`
48
-
49
- ```ts filename="reference/syncing/sync-provider/electricsql/client-setup.ts"
50
-
51
- const _backend = makeSyncBackend({
52
- endpoint: '/api/electric', // Your API proxy endpoint
53
- ping: { enabled: true },
54
- })
55
- ```
56
-
57
- ## API proxy implementation
58
-
59
- ElectricSQL requires an API proxy on your server to handle authentication and database operations. Your proxy needs two endpoints:
60
-
61
- ### Minimal implementation example
62
-
63
- ## `reference/syncing/sync-provider/electricsql/api-proxy.ts`
64
-
65
- ```ts filename="reference/syncing/sync-provider/electricsql/api-proxy.ts"
66
-
67
- const electricHost = 'http://localhost:30000' // Your Electric server
68
-
69
- /** Placeholder for your database factory function */
70
- declare const makeDb: (storeId: string) => {
71
- migrate: () => Promise<void>
72
- disconnect: () => Promise<void>
73
- createEvents: (batch: (typeof ApiSchema.PushPayload.Type)['batch']) => Promise<void>
74
- }
75
-
76
- // ---cut---
77
-
78
- // GET /api/electric - Pull events (proxied through Electric)
79
- export async function GET(request: Request) {
80
- const searchParams = new URL(request.url).searchParams
81
- const { url, storeId, needsInit } = makeElectricUrl({
82
- electricHost,
83
- searchParams,
84
- apiSecret: 'your-electric-secret',
85
- })
86
-
87
- // Add your authentication logic here
88
- // if (!isAuthenticated(request)) {
89
- // return new Response('Unauthorized', { status: 401 })
90
- // }
91
-
92
- // Initialize database tables if needed
93
- if (needsInit) {
94
- const db = makeDb(storeId)
95
- await db.migrate()
96
- await db.disconnect()
97
- }
98
-
99
- // Proxy pull request to Electric server for reading
100
- return fetch(url)
101
- }
102
-
103
- // POST /api/electric - Push events (direct database write)
104
- export async function POST(request: Request) {
105
- const payload = await request.json()
106
- const parsed = Schema.decodeUnknownSync(ApiSchema.PushPayload)(payload)
107
-
108
- // Write events directly to Postgres table (bypasses Electric)
109
- const db = makeDb(parsed.storeId)
110
- await db.createEvents(parsed.batch)
111
- await db.disconnect()
112
-
113
- return Response.json({ success: true })
114
- }
115
- ```
116
-
117
- ### Important considerations
118
-
119
- - **Database Setup**: Ensure your Postgres database is configured for Electric
120
- - **Authentication**: Implement proper auth checks in your proxy
121
- - **Error Handling**: Add robust error handling for database operations
122
- - **Connection Management**: Properly manage database connections
123
-
124
- ## Example
125
-
126
- See the
127
- [todomvc-sync-electric](https://github.com/livestorejs/livestore/tree/main/examples/web-todomvc-sync-electric)
128
- example for a complete implementation.
129
-
130
- ## How the sync provider works
131
-
132
- The initial version of the ElectricSQL sync provider will use the server-side
133
- Postgres DB as a store for the mutation event history.
134
-
135
- Events are stored in a table following the pattern
136
- `eventlog_${PERSISTENCE_FORMAT_VERSION}_${storeId}` where
137
- `PERSISTENCE_FORMAT_VERSION` is a number that is incremented whenever the
138
- `sync-electric` internal storage format changes.
139
-
140
- ## F.A.Q.
141
-
142
- ### Can I use my existing Postgres database with the sync provider?
143
-
144
- Unless the database is already modelled as a eventlog following the
145
- `@livestore/sync-electric` storage format, you won't be able to easily use your
146
- existing database with this sync backend implementation.
147
-
148
- We might support this use case in the future, you can follow the progress
149
- [here](https://github.com/livestorejs/livestore/issues/286). Please share any
150
- feedback you have on this use case there.
151
-
152
- ### Why do I need an API proxy in front of the ElectricSQL server?
153
-
154
- The API proxy is used to handle pull/push requests between LiveStore and ElectricSQL,
155
- allowing you to implement custom logic such as:
156
- - Authentication and authorization
157
- - Rate limiting and quota management
158
- - Database initialization and migration
159
- - Custom business logic and validation
@@ -1,230 +0,0 @@
1
- # S2
2
-
3
- export const CODE = {
4
- recordStructureExample: recordStructureExampleCode,
5
- };
6
-
7
- The `@livestore/sync-s2` package lets you sync LiveStore with the official S2 backend (s2.dev).
8
-
9
- - Package: `pnpm add @livestore/sync-s2`
10
- - Protocol: HTTP push/pull, live pull via SSE
11
-
12
- ## Architecture
13
-
14
- ```mermaid
15
- graph LR
16
- subgraph Browser
17
- LS[LiveStore Client]
18
- end
19
-
20
- subgraph "Your Server"
21
- AP[API Proxy<br/>'/api/s2']
22
- end
23
-
24
- subgraph "S2 Cloud"
25
- S2[S2 Backend<br/>'*.s2.dev']
26
- end
27
-
28
- LS -->|"GET (pull)<br/>POST (push)<br/>HEAD (ping)"| AP
29
- AP -->|"Authenticated<br/>Requests"| S2
30
-
31
- style LS fill:#e1f5fe
32
- style AP fill:#fff3e0
33
- style S2 fill:#f3e5f5
34
- ```
35
-
36
- The API proxy handles:
37
- - **Business logic**: Any kind of business logic that is specific to your application (e.g. rate limiting, auth, logging, etc.)
38
- - **S2 Stream Management**: Creates basins and streams as needed
39
- - **S2 Request Translation**: Converts LiveStore sync operations to authenticated S2 API calls
40
-
41
- ## Client setup
42
-
43
- Basic usage in your worker/server code:
44
-
45
- ## `reference/syncing/s2/client-setup.ts`
46
-
47
- ```ts filename="reference/syncing/s2/client-setup.ts"
48
-
49
- const _backend = makeSyncBackend({
50
- endpoint: '/api/s2', // Your API proxy endpoint
51
- // more options...
52
- })
53
- ```
54
-
55
- ## API proxy implementation
56
-
57
- S2 requires authentication and stream management that can't be handled directly from the browser. You'll need to implement an API proxy on your server that:
58
-
59
- 1. **Handles authentication** with S2 using your access token
60
- 2. **Manages basins and streams** (creates them if they don't exist)
61
- 3. **Proxies requests** between LiveStore and S2
62
-
63
- Your proxy needs three endpoints:
64
-
65
- ### Using helper functions
66
-
67
- The `@livestore/sync-s2` package provides helper functions to simplify the proxy implementation:
68
-
69
- ## `reference/syncing/s2/api-proxy-implementation.ts`
70
-
71
- ```ts filename="reference/syncing/s2/api-proxy-implementation.ts"
72
-
73
- // Configure S2 connection
74
- const s2Config: S2Helpers.S2Config = {
75
- basin: process.env.S2_BASIN ?? 'your-basin',
76
- token: process.env.S2_ACCESS_TOKEN!, // Your S2 access token
77
- }
78
-
79
- // HEAD /api/s2 - Health check/ping
80
- export async function HEAD() {
81
- return new Response(null, { status: 200 })
82
- }
83
-
84
- // GET /api/s2 - Pull events
85
- export async function GET(request: Request) {
86
- const url = new URL(request.url)
87
- const args = S2.decodePullArgsFromSearchParams(url.searchParams)
88
- const streamName = S2.makeS2StreamName(args.storeId)
89
-
90
- // Ensure basin and stream exist
91
- await S2Helpers.ensureBasin(s2Config)
92
- await S2Helpers.ensureStream(s2Config, streamName)
93
-
94
- // Build request with appropriate headers and URL
95
- // Note: buildPullRequest handles cursor+1 conversion internally
96
- const { url: pullUrl, headers } = S2Helpers.buildPullRequest({ config: s2Config, args })
97
-
98
- const res = await fetch(pullUrl, { headers })
99
-
100
- // For live pulls (SSE), proxy the response
101
- if (args.live === true) {
102
- if (!res.ok) {
103
- return S2Helpers.sseKeepAliveResponse()
104
- }
105
- return new Response(res.body, {
106
- status: 200,
107
- headers: { 'content-type': 'text/event-stream' },
108
- })
109
- }
110
-
111
- // For regular pulls
112
- if (!res.ok) {
113
- return S2Helpers.emptyBatchResponse()
114
- }
115
-
116
- const batch = await res.text()
117
- return new Response(batch, {
118
- headers: { 'content-type': 'application/json' },
119
- })
120
- }
121
-
122
- // POST /api/s2 - Push events
123
- export async function POST(request: Request) {
124
- const requestBody = await request.json()
125
- const parsed = Schema.decodeUnknownSync(S2.ApiSchema.PushPayload)(requestBody)
126
- const streamName = S2.makeS2StreamName(parsed.storeId)
127
-
128
- // Ensure basin and stream exist
129
- await S2Helpers.ensureBasin(s2Config)
130
- await S2Helpers.ensureStream(s2Config, streamName)
131
-
132
- // Build push request with proper formatting
133
- const pushRequests = S2Helpers.buildPushRequests({
134
- config: s2Config,
135
- storeId: parsed.storeId,
136
- batch: parsed.batch,
137
- })
138
-
139
- for (const pushRequest of pushRequests) {
140
- const res = await fetch(pushRequest.url, {
141
- method: 'POST',
142
- headers: pushRequest.headers,
143
- body: pushRequest.body,
144
- })
145
-
146
- if (!res.ok) {
147
- return S2Helpers.errorResponse('Push failed', 500)
148
- }
149
- }
150
-
151
- return S2Helpers.successResponse()
152
- }
153
- ```
154
-
155
- ### Cursor semantics
156
-
157
- The S2 sync provider uses a cursor that represents the **last processed record**:
158
- - The cursor points to the last S2 sequence number we've seen
159
- - S2's `seq_num` parameter expects where to start reading from (inclusive)
160
- - The helper functions automatically handle the `+1` conversion: `seq_num = cursor + 1`
161
- - When starting from the beginning, cursor is `'from-start'` which maps to `seq_num = 0`
162
-
163
- ### Important considerations
164
-
165
- - **Stream provisioning**: The helper functions provide `ensureBasin()` and `ensureStream()` to handle creation automatically.
166
- - **Error handling**: The helpers include fallback responses (`emptyBatchResponse()`, `sseKeepAliveResponse()`) to maintain stream continuity during errors.
167
- - **Authentication**: Store your S2 access token securely (e.g., environment variables).
168
- - **Rate limiting**: Consider implementing rate limiting to protect your S2 quota.
169
- - **Response helpers**: Use the provided response helpers (`successResponse()`, `errorResponse()`) for consistent API responses.
170
-
171
- ## Live pull (SSE)
172
-
173
- S2 provider supports live pulls over Server-Sent Events (SSE). When `live: true` is passed to `pull`, the client:
174
- - Immediately emits one page (possibly empty) with `pageInfo: NoMore`.
175
- - Parses SSE frames robustly (multi-line `data:` support) and reacts to typed events:
176
- - `event: batch` → parses `data` as S2 `ReadBatch` and emits items.
177
- - `event: ping` → ignored; keeps the stream alive.
178
- - `event: error` → mapped to `InvalidPullError`.
179
-
180
- ## Implementation notes
181
-
182
- ### Data storage & encoding
183
-
184
- LiveStore leverages S2 streams for durable event storage. Understanding the mapping between LiveStore concepts and S2 primitives helps developers comprehend the persistence layer, though direct manipulation is discouraged.
185
-
186
- #### LiveStore → S2 mapping
187
-
188
- **Store to Stream**: Each LiveStore `storeId` maps to exactly one S2 stream. The stream name is derived from the `storeId` after sanitization to meet S2 naming requirements.
189
-
190
- **Event Encoding**: LiveStore events (`AnyEncodedGlobal`) are JSON-serialized and stored as the `body` field of S2 records. Each event contains:
191
- - `name`: Event type identifier
192
- - `args`: Event-specific payload data
193
- - `seqNum`: LiveStore's global event sequence number
194
- - `parentSeqNum`: Previous event's sequence number for ordering
195
- - `clientId`: Origin client identifier
196
- - `sessionId`: Session that created the event
197
-
198
- **Record Structure**: When pushed to S2, each LiveStore event becomes one S2 record:
199
-
200
- <Code lang="json" code={CODE.recordStructureExample} title="S2 Record Example" />
201
-
202
- #### Sequence number handling
203
-
204
- **LiveStore and S2 maintain completely independent sequence numbering systems**:
205
-
206
- - **LiveStore's `seqNum`**: Stored inside the JSON event payload (starts at 0). Used for logical event ordering and cursor management within LiveStore.
207
- - **S2's `seq_num`**: Assigned by S2 to each record in the stream (also starts at 0). Used solely for stream positioning when reading records.
208
-
209
- These are **two separate numbering systems** that happen to both start at 0. While they often align numerically (first event is LiveStore seqNum 0, stored in S2 record with seq_num 0), this is coincidental rather than a direct mapping. The sync provider:
210
- - Preserves LiveStore's sequence numbers unchanged in the event payload
211
- - Uses S2's seq_num only for querying records from the stream (e.g., "read from position X")
212
- - Never relies on S2's seq_num for LiveStore's logical event ordering
213
-
214
- #### Technical details
215
-
216
- **Format**: The provider uses `s2-format: raw` when communicating with S2, treating record bodies as UTF-8 JSON strings.
217
-
218
- **Headers**: S2 record headers are not utilized; all LiveStore metadata is contained within the JSON body.
219
-
220
- **Batch Operations**: Multiple events can be pushed in a single batch, with each event becoming a separate S2 record while maintaining order.
221
-
222
- #### Important note
223
-
224
- **Direct stream manipulation is strongly discouraged**. Always interact with S2 streams through LiveStore's sync provider to ensure:
225
- - Proper event encoding/decoding
226
- - Sequence number integrity
227
- - Cursor management consistency
228
- - Compatibility with LiveStore's sync protocol
229
-
230
- Bypassing LiveStore to modify S2 streams directly may corrupt the event log and break synchronization.
@@ -1,48 +0,0 @@
1
- # Overview and prerequisites
2
-
3
- ## Welcome to the LiveStore tutorial
4
-
5
- This tutorial will guide you through the process of building a simple todo app with React, Vite & Tailwind, using LiveStore as its data layer.
6
-
7
- It is focused on building a _minimalistic_ application to gradually introduce the main concepts of LiveStore. That being said, LiveStore itself has been specifically designed for large, complex applications, and shines especially when used in these contexts.
8
-
9
- :::note[Goal of this tutorial: Education]
10
- The goal of the tutorial is to _educate_. It reduces as much noise as possible so you can focus on the parts that actually matter for building an application with LiveStore.
11
-
12
- **If you just want to see LiveStore in action and play around with it, consider setting up the [starter project](/getting-started/react-web/) directly.**
13
- :::
14
-
15
- ## Prerequisites
16
-
17
- ### Useful knowledge
18
-
19
- While the tutorial is aimed at LiveStore newcomers, it will be helpful to have some knowledge in basic areas of web development, such as of [React](https://react.dev/), [TypeScript](https://www.typescriptlang.org/) and [event-driven architectures](/overview/how-livestore-works#event-sourcing).
20
-
21
- ### Technical setup
22
-
23
- - The tutorial assumes that you're using a Unix-like shell, e.g. by using commands like `mkdir` and `touch` for creating directories and files.
24
- - It gives you the option to use either [Bun](https://bun.sh/) or [`pnpm`](https://pnpm.io/) as the package manager.
25
- - You'll deploy the application to Cloudflare. If you don't have an account there yet, you can sign up for a free one [here](https://dash.cloudflare.com/sign-up) (or skip the deployment steps in the tutorial).
26
-
27
- ## What you'll do
28
-
29
- Here's an overview of the steps you'll take in the tutorial:
30
-
31
- 1. Set up a starter project with React, Vite & Tailwind
32
- - This project will be used as a starting point for the tutorial and already comes with basic functionality for a todo app.
33
- - It uses local React state to manage the todo list. Throughout the tutorial you'll gradually replace the ephemeral, local state with LiveStore persistent storage.
34
- 1. Deploy the application to Cloudflare
35
- - This enables you to observe the evolution in behaviour as you're introducing LiveStore.
36
- 1. Add LiveStore to the project
37
- - You'll add LiveStore dependencies to the project and implement persisting the todos so that they survive a page refresh.
38
- 1. Automatically sync data to Cloudflare
39
- - You'll use LiveStore to set up syncing of the todo list data via Cloudflare Workers and Durable Objects in the background.
40
- - Now your todos will not only survive a page refresh, but also automatically sync across browser sessions, and even across devices.
41
- 1. Expand the business logic with more LiveStore events
42
- - You'll learn how to use LiveStore events to expand the business logic of the todo app.
43
- 1. Persist UI state
44
- - LiveStore can also be used to persist UI state, such as the text from an input field or a filter selection.
45
-
46
- ## Credits
47
-
48
- This tutorial has been written by [Nikolas Burk](https://x.com/nikolasburk).
@@ -1,105 +0,0 @@
1
- # 1. Set up starter project with React, Vite & Tailwind
2
-
3
- ## Set up a starter project with React, Vite & Tailwind
4
-
5
- We have prepared a [starter project](https://github.com/livestorejs/livestore/tree/dev/examples/tutorial-starter) for you that you can use as a starting point for the tutorial. Download it via the following command:
6
-
7
- <Tabs syncKey="package-manager">
8
-
9
- <TabItem label="bun">
10
-
11
- <Code code={`bunx @livestore/cli@dev create \\\n --example tutorial-starter livestore-todo-app`} lang="sh" />
12
-
13
- </TabItem>
14
-
15
- <TabItem label="pnpm">
16
-
17
- <Code code={`pnpm dlx @livestore/cli@dev create \\\n --example tutorial-starter livestore-todo-app`} lang="sh" />
18
-
19
- </TabItem>
20
-
21
- </Tabs>
22
-
23
- Once you've downloaded the project, you can navigate to the project directory and install the dependencies:
24
-
25
- The project currently is set up as follows:
26
- - Minimal project created via [`vite create`](https://vite.dev/guide/#scaffolding-your-first-vite-project) using React and TypeScript.
27
- - Using [Tailwind CSS](https://tailwindcss.com/) for styling.
28
- - Has basic functionality for adding and deleting todos via local [`React.useState`](https://react.dev/learn/state-a-components-memory).
29
-
30
- ## Understand the current project state
31
-
32
- Run the app with:
33
-
34
- <Tabs syncKey="package-manager">
35
-
36
- <TabItem label="bun">
37
-
38
- <Code code={`bun dev`} lang="sh" />
39
-
40
- </TabItem>
41
-
42
- <TabItem label="pnpm">
43
-
44
- <Code code={`pnpm dev`} lang="sh" />
45
-
46
- </TabItem>
47
-
48
- </Tabs>
49
-
50
- Here's the UI you're going to see after adding a few todos:
51
-
52
- ![](../../../assets/tutorial/chapter-1/0-screenshot-todo-list.png)
53
-
54
- Let's take a quick moment to understand how the app is currently implemented:
55
-
56
- All relevant code lives in `App.tsx`. Here's a simplified version of it:
57
-
58
- ```ts
59
- interface Todo {
60
- id: number
61
- text: string
62
- }
63
-
64
- function App() {
65
- const [todos, setTodos] = useState<Todo[]>([])
66
- const [input, setInput] = useState('')
67
-
68
- const addTodo = () => {
69
- const newTodo: Todo = {
70
- id: Date.now(),
71
- text: input
72
- }
73
- setTodos([...todos, newTodo])
74
- setInput('')
75
- }
76
-
77
- const deleteTodo = (id: number) => {
78
- setTodos(todos.filter(todo => todo.id !== id))
79
- }
80
-
81
- return (
82
- // Render input text field and todo list ...
83
- // ... and invoke `addTodo` and `deleteTodo`
84
- // ... when the buttons are clicked.
85
- )
86
- }
87
- ```
88
-
89
- For any React developer, this is a very familiar setup:
90
-
91
- <StateAndUiDiagram class="my-8" />
92
-
93
- You have two pieces of state:
94
- - application state: `todos: Todo[]` → manipulated by the `addTodo` and `deleteTodo` functions.
95
- - UI state: `input: string` → manipulated when the text in the input field changes.
96
-
97
- The "problem" with this code is that the todo items are not _persisted_, meaning they vanish when:
98
- - the page is refreshed in the browser.
99
- - the development server is restarted.
100
-
101
- In the next chapters, you'll learn how to persist the todos in the list, so that they'll "survive" both actions.
102
-
103
- Even more: They will not only persist, they will automatically sync across multiple browsers tabs/windows, and even across devices—without you needing to think about the syncing logic and managing remote state.
104
-
105
- That's the power of LiveStore!