@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,6 +0,0 @@
1
- # Encryption
2
-
3
- LiveStore doesn't yet support encryption but might in the future.
4
- See [this issue](https://github.com/livestorejs/livestore/issues/70) for more details.
5
-
6
- For now you can implement encryption yourself e.g. by encrypting the events using a custom Effect Schema definition which applies a encryption transformation to the events.
@@ -1,5 +0,0 @@
1
- # External data
2
-
3
- LiveStore doesn't provide any built-in functionality to deal with external data. However, LiveStore was designed with this use case in mind (e.g. Overtone integrates with lots of external data like Spotify, ...). One way to deal with external data is to also model it as an event log and materialize it into LiveStore state as well.
4
-
5
- (If you're interested in learning more about the solution we're using for Overtone, get in touch.)
@@ -1,11 +0,0 @@
1
- # File management
2
-
3
- LiveStore doesn't have built-in support for file management but it's easy to use LiveStore alongside existing file storage solutions (e.g. S3).
4
-
5
- The basic idea is to store the file metadata (e.g. url, name, size, type) in LiveStore and the file content separately.
6
-
7
- ## Example
8
-
9
- ```ts
10
- // TODO (contribution welcome)
11
- ```
@@ -1,14 +0,0 @@
1
- # File structure
2
-
3
- While there are no strict requirements/conventions for how to structure your project (files, folders, etc), a common pattern is to have a `src/livestore` folder which contains all the LiveStore related code.
4
-
5
- ```
6
- src/
7
- livestore/
8
- index.ts # re-exports everything
9
- schema.ts # schema definitions
10
- queries.ts # query definitions
11
- events.ts # event definitions
12
- ...
13
- ...
14
- ```
@@ -1,369 +0,0 @@
1
- # List Ordering
2
-
3
- Fractional indexing enables conflict-free list ordering in distributed systems by assigning string-based position values that maintain lexicographic order. This makes it ideal for implementing drag-and-drop reordering in LiveStore applications where multiple clients may reorder items concurrently.
4
-
5
- To understand how the algorithm works, see these visual and interactive explanations:
6
- - [CRDT Fractional Indexing](https://madebyevan.com/algos/crdt-fractional-indexing/) by Evan Wallace - Visual explanation of the algorithm
7
- - [Implementing Fractional Indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) by David Greenspan - Interactive notebook walkthrough
8
-
9
- ## Why Fractional Indexing?
10
-
11
- Traditional numeric ordering (1, 2, 3...) requires renumbering multiple items when inserting or reordering, which creates conflicts in distributed systems. Fractional indexing solves this by:
12
-
13
- - Generating position values that can always be inserted between any two existing positions
14
- - Using lexicographic string ordering that works naturally with SQL `ORDER BY`
15
- - Eliminating the need for coordination between clients when reordering
16
- - Avoiding cascading updates when inserting items
17
-
18
- For example, inserting a new item between positions "a0" and "b0" generates "aV", which sorts correctly without modifying existing items.
19
-
20
- ## Installation
21
-
22
- Install the `fractional-indexing` package from [Rocicorp](https://github.com/rocicorp/fractional-indexing) (the same team behind Replicache):
23
-
24
- ```bash
25
- pnpm install fractional-indexing
26
- ```
27
-
28
- ## Schema Design
29
-
30
- Define a text column to store the fractional index and add a database index for efficient ordering:
31
-
32
- ## `patterns/list-ordering/schema.ts`
33
-
34
- ```ts filename="patterns/list-ordering/schema.ts"
35
-
36
- export const task = State.SQLite.table({
37
- name: 'task',
38
- columns: {
39
- id: State.SQLite.integer({ primaryKey: true }),
40
- title: State.SQLite.text({ default: '' }),
41
- completed: State.SQLite.integer({ default: 0 }),
42
- /** Fractional index for ordering tasks in the list */
43
- order: State.SQLite.text({ nullable: false, default: '' }),
44
- },
45
- indexes: [
46
- /** Index for efficient ordering queries */
47
- { name: 'task_order', columns: ['order'] },
48
- ],
49
- deriveEvents: true,
50
- })
51
-
52
- export type Task = typeof task.Type
53
-
54
- export const tables = { task }
55
- ```
56
-
57
- The `order` column stores string values like "a0", "a1", "aV" that maintain lexicographic ordering. Adding a database index on this column ensures efficient queries when retrieving ordered lists.
58
-
59
- ## Creating Ordered Items
60
-
61
- Use `generateKeyBetween(a, b)` to generate position values when creating new items:
62
-
63
- ## `patterns/list-ordering/create-item.ts`
64
-
65
- ```ts filename="patterns/list-ordering/create-item.ts"
66
-
67
- declare const store: Store
68
-
69
- /** Create a new task at the end of the list */
70
- export const createTaskAtEnd = (title: string) => {
71
- // Get the highest order value
72
- const highestOrder = store.query(tables.task.select('order').orderBy('order', 'desc').limit(1))[0] ?? null
73
-
74
- // Generate new order after the highest
75
- const order = generateKeyBetween(highestOrder, null)
76
-
77
- // Commit the event
78
- store.commit(events.createTask({ title, order }))
79
-
80
- return order
81
- }
82
-
83
- /** Create a new task at the beginning of the list */
84
- export const createTaskAtStart = (title: string) => {
85
- // Get the lowest order value
86
- const lowestOrder = store.query(tables.task.select('order').orderBy('order', 'asc').limit(1))[0] ?? null
87
-
88
- // Generate new order before the lowest
89
- const order = generateKeyBetween(null, lowestOrder)
90
-
91
- store.commit(events.createTask({ title, order }))
92
-
93
- return order
94
- }
95
-
96
- /** Create the very first task in an empty list */
97
- export const createFirstTask = (title: string) => {
98
- // When the list is empty, use a simple default value
99
- const order = 'a1'
100
-
101
- store.commit(events.createTask({ title, order }))
102
-
103
- return order
104
- }
105
- ```
106
-
107
- ### `patterns/list-ordering/events.ts`
108
-
109
- ```ts filename="patterns/list-ordering/events.ts"
110
-
111
- export const events = {
112
- createTask: Events.synced({
113
- name: 'v1.CreateTask',
114
- schema: Schema.Struct({
115
- title: Schema.String,
116
- order: Schema.String,
117
- }),
118
- }),
119
- updateTaskOrder: Events.synced({
120
- name: 'v1.UpdateTaskOrder',
121
- schema: Schema.Struct({
122
- id: Schema.Number,
123
- order: Schema.String,
124
- }),
125
- }),
126
- }
127
- ```
128
-
129
- ### `patterns/list-ordering/schema.ts`
130
-
131
- ```ts filename="patterns/list-ordering/schema.ts"
132
-
133
- export const task = State.SQLite.table({
134
- name: 'task',
135
- columns: {
136
- id: State.SQLite.integer({ primaryKey: true }),
137
- title: State.SQLite.text({ default: '' }),
138
- completed: State.SQLite.integer({ default: 0 }),
139
- /** Fractional index for ordering tasks in the list */
140
- order: State.SQLite.text({ nullable: false, default: '' }),
141
- },
142
- indexes: [
143
- /** Index for efficient ordering queries */
144
- { name: 'task_order', columns: ['order'] },
145
- ],
146
- deriveEvents: true,
147
- })
148
-
149
- export type Task = typeof task.Type
150
-
151
- export const tables = { task }
152
- ```
153
-
154
- Key patterns:
155
- - `generateKeyBetween(highest, null)` - Append to end of list
156
- - `generateKeyBetween(null, lowest)` - Prepend to start of list
157
- - Use `"a1"` as a simple default for the first item in an empty list
158
-
159
- ## Reordering Items
160
-
161
- Handle drag-and-drop by generating a new position between the target boundaries:
162
-
163
- ## `patterns/list-ordering/reorder.ts`
164
-
165
- ```ts filename="patterns/list-ordering/reorder.ts"
166
-
167
- declare const store: Store
168
-
169
- /**
170
- * Reorder a task by moving it between two other tasks
171
- *
172
- * @param taskId - The task to reorder
173
- * @param beforeOrder - The order value of the task that will be before this one (null if moving to end)
174
- * @param afterOrder - The order value of the task that will be after this one (null if moving to start)
175
- */
176
- export const reorderTask = (taskId: number, beforeOrder: string | null, afterOrder: string | null) => {
177
- // Generate a new fractional index between the two positions
178
- const newOrder = generateKeyBetween(beforeOrder, afterOrder)
179
-
180
- // Commit the update event
181
- store.commit(events.updateTaskOrder({ id: taskId, order: newOrder }))
182
-
183
- return newOrder
184
- }
185
-
186
- /**
187
- * Handle drag-and-drop reordering
188
- *
189
- * This is a more complete example showing how to handle drag-and-drop
190
- * with proper boundary checks.
191
- */
192
- export const handleDragDrop = (draggedTaskId: number, targetTaskId: number, dropPosition: 'before' | 'after') => {
193
- const before = dropPosition === 'before'
194
-
195
- // Get the target task's order
196
- const targetOrder = store.query(tables.task.select('order').where({ id: targetTaskId }).first({ behaviour: 'error' }))
197
-
198
- // Find the nearest task in the drop direction
199
- const nearestOrder =
200
- store.query(
201
- tables.task
202
- .select('order')
203
- .where({
204
- order: { op: before ? '>' : '<', value: targetOrder },
205
- })
206
- .orderBy('order', before ? 'asc' : 'desc')
207
- .limit(1),
208
- )[0] ?? null
209
-
210
- // Generate new order between target and nearest
211
- const newOrder = generateKeyBetween(before ? targetOrder : nearestOrder, before ? nearestOrder : targetOrder)
212
-
213
- // Commit the update
214
- store.commit(events.updateTaskOrder({ id: draggedTaskId, order: newOrder }))
215
-
216
- return newOrder
217
- }
218
- ```
219
-
220
- ### `patterns/list-ordering/events.ts`
221
-
222
- ```ts filename="patterns/list-ordering/events.ts"
223
-
224
- export const events = {
225
- createTask: Events.synced({
226
- name: 'v1.CreateTask',
227
- schema: Schema.Struct({
228
- title: Schema.String,
229
- order: Schema.String,
230
- }),
231
- }),
232
- updateTaskOrder: Events.synced({
233
- name: 'v1.UpdateTaskOrder',
234
- schema: Schema.Struct({
235
- id: Schema.Number,
236
- order: Schema.String,
237
- }),
238
- }),
239
- }
240
- ```
241
-
242
- ### `patterns/list-ordering/schema.ts`
243
-
244
- ```ts filename="patterns/list-ordering/schema.ts"
245
-
246
- export const task = State.SQLite.table({
247
- name: 'task',
248
- columns: {
249
- id: State.SQLite.integer({ primaryKey: true }),
250
- title: State.SQLite.text({ default: '' }),
251
- completed: State.SQLite.integer({ default: 0 }),
252
- /** Fractional index for ordering tasks in the list */
253
- order: State.SQLite.text({ nullable: false, default: '' }),
254
- },
255
- indexes: [
256
- /** Index for efficient ordering queries */
257
- { name: 'task_order', columns: ['order'] },
258
- ],
259
- deriveEvents: true,
260
- })
261
-
262
- export type Task = typeof task.Type
263
-
264
- export const tables = { task }
265
- ```
266
-
267
- The `generateKeyBetween` function automatically creates a string value that sorts lexicographically between the two boundary positions. Pass `null` to represent the start or end of the list.
268
-
269
- ## Querying Ordered Lists
270
-
271
- Query items using standard SQL ordering on the fractional index column:
272
-
273
- ## `patterns/list-ordering/query.ts`
274
-
275
- ```ts filename="patterns/list-ordering/query.ts"
276
-
277
- declare const store: Store
278
-
279
- /**
280
- * Query tasks in their proper order
281
- *
282
- * The fractional index values maintain lexicographic ordering,
283
- * so we can simply order by the 'order' column.
284
- */
285
- export const getOrderedTasks = () => {
286
- return store.query(tables.task.select().orderBy('order', 'asc'))
287
- }
288
-
289
- /**
290
- * Get the highest order value (for appending new items)
291
- */
292
- export const getHighestOrder = (): string | null => {
293
- const order = store.query(tables.task.select('order').orderBy('order', 'desc').limit(1))[0]
294
-
295
- return order ?? null
296
- }
297
-
298
- /**
299
- * Get the lowest order value (for prepending new items)
300
- */
301
- export const getLowestOrder = (): string | null => {
302
- const order = store.query(tables.task.select('order').orderBy('order', 'asc').limit(1))[0]
303
-
304
- return order ?? null
305
- }
306
- ```
307
-
308
- ### `patterns/list-ordering/schema.ts`
309
-
310
- ```ts filename="patterns/list-ordering/schema.ts"
311
-
312
- export const task = State.SQLite.table({
313
- name: 'task',
314
- columns: {
315
- id: State.SQLite.integer({ primaryKey: true }),
316
- title: State.SQLite.text({ default: '' }),
317
- completed: State.SQLite.integer({ default: 0 }),
318
- /** Fractional index for ordering tasks in the list */
319
- order: State.SQLite.text({ nullable: false, default: '' }),
320
- },
321
- indexes: [
322
- /** Index for efficient ordering queries */
323
- { name: 'task_order', columns: ['order'] },
324
- ],
325
- deriveEvents: true,
326
- })
327
-
328
- export type Task = typeof task.Type
329
-
330
- export const tables = { task }
331
- ```
332
-
333
- Since fractional index values maintain lexicographic ordering, you can use simple `ORDER BY` clauses without special handling.
334
-
335
- ## Best Practices
336
-
337
- - **Use text/string columns** - Fractional index values are strings, not numbers
338
- - **Add database indexes** - Index the order column for efficient queries
339
- - **Validate lexicographic ordering** - Ensure your sorting logic uses standard string comparison, not locale-aware comparison (avoid `String.prototype.localeCompare()`)
340
- - **Handle empty lists** - Use a simple default like `"a1"` for the first item
341
- - **Consider bulk operations** - For creating multiple items at once, use `generateNKeysBetween(a, b, n)` from the same package
342
-
343
- ## Real-World Example
344
-
345
- The [web-linearlite example](https://github.com/livestorejs/livestore/tree/dev/examples/web-linearlite) demonstrates fractional indexing for Kanban board ordering. It shows:
346
- - Schema definition with `kanbanorder` column
347
- - Creating issues with proper ordering
348
- - Drag-and-drop reordering across columns
349
- - Querying issues in order by status
350
-
351
- Check `examples/web-linearlite/src/livestore/schema/issue.ts` for the schema and `examples/web-linearlite/src/components/column.tsx` for the drag-and-drop implementation.
352
-
353
- ## How It Works
354
-
355
- Fractional indexing generates position strings that always allow insertion between any two positions:
356
-
357
- ```
358
- Initial: a0 a1 a2
359
- Insert between a0 and a1: a0V (sorts: a0 < a0V < a1)
360
- Insert between a0V and a1: a0n (sorts: a0 < a0V < a0n < a1)
361
- ```
362
-
363
- The algorithm ensures:
364
- - Position strings stay reasonably short
365
- - No coordination needed between clients
366
- - Conflicts resolve naturally through lexicographic ordering
367
- - Works seamlessly with LiveStore's event sourcing model
368
-
369
- For more details on the algorithm, see the [fractional-indexing documentation](https://github.com/rocicorp/fractional-indexing).
@@ -1,32 +0,0 @@
1
- # Offline support
2
-
3
- - LiveStore supports offline data management out of the box. In order to make your app work fully offline, you might need to also consider the following:
4
- - Design your app in a way to treat the network as an optional feature (e.g. when relying on other APIs / external data)
5
- - Use service workers to cache assets locally (e.g. images, videos, etc.)
6
-
7
- ## Tracking connectivity
8
-
9
- Use `store.networkStatus` to react to connectivity transitions. The subscribable emits every time the sync backend connection flips or the devtools latch simulates an offline state.
10
-
11
- ## `patterns/offline/tracking-connectivity.ts`
12
-
13
- ```ts filename="patterns/offline/tracking-connectivity.ts"
14
-
15
- declare const store: Store
16
-
17
- // ---cut---
18
-
19
- const status = await store.networkStatus.pipe(Effect.runPromise)
20
- if (status.isConnected === false) {
21
- console.warn('Sync backend offline since', new Date(status.timestampMs))
22
- }
23
-
24
- await store.networkStatus.changes.pipe(
25
- Stream.tap((next) => Effect.sync(() => console.log('network status updated', next))),
26
- Stream.runDrain,
27
- Effect.scoped,
28
- Effect.runPromise,
29
- )
30
- ```
31
-
32
- When devtools close the sync latch to simulate an offline client, `status.devtools.latchClosed` is `true`, allowing you to differentiate between real and simulated outages. Remember to dispose of long-lived subscriptions using the Effect scope you already manage for your runtime.
@@ -1,18 +0,0 @@
1
- # ORM
2
-
3
- - LiveStore has a built-in query builder which should be sufficient for most simple use cases.
4
- - You can always fall back to using raw SQL queries if you need more complex queries.
5
- - As long as the ORM allows supports synchronously generating SQL statements (and binding parameters), you should be able to use it with LiveStore.
6
- - Supported ORMs:
7
- - [Knex](https://knexjs.org/)
8
- - [Kysely](https://kysely.dev/)
9
- - [Drizzle](https://orm.drizzle.team/)
10
- - [Objection.js](https://vincit.github.io/objection.js/)
11
- - Unsupported ORMs:
12
- - [Prisma](https://www.prisma.io/) (because it's async)
13
-
14
- ## Example
15
-
16
- ```ts
17
- // TODO (contribution welcome)
18
- ```
@@ -1,11 +0,0 @@
1
- # Presence
2
-
3
- LiveStore doesn't yet have any built-in presence functionality (e.g. to track online/offline users).
4
-
5
- Common presence use cases are:
6
- - Track which users are online / in a room
7
- - Track which users are typing (e.g. in a chat)
8
- - Text cursor (similar to Google Docs)
9
- - Cursor movements (similar to Figma)
10
-
11
- For now it's recommend to implement presence functionality in your application or use a third party service (e.g. Liveblocks).
@@ -1,11 +0,0 @@
1
- # Rich text editing
2
-
3
- LiveStore doesn't yet have any built-in support for rich text editing. It's currently recommended to use a purpose-built library (e.g. [Yjs](https://yjs.dev/) or [Automerge](https://automerge.org/)) for this use case in combination with LiveStore.
4
-
5
- The idea here is to reference the rich text document from within LiveStore's event log and sync both in parallel.
6
-
7
- ## Example
8
-
9
- ```ts
10
- // TODO
11
- ```
@@ -1,97 +0,0 @@
1
- # Server-side clients
2
-
3
- You can also use LiveStore on the server side e.g. via the `@livestore/adapter-node` adapter. This allows you to:
4
- - have an up-to-date server-side SQLite database (read model)
5
- - react to events / state changes on the server side (e.g. to send emails/push notifications)
6
- - commit events on the server side (e.g. for sensitive/trusted operations)
7
-
8
- <ServerSideClientDiagram />
9
-
10
- Note about the schema: While the `events` schema needs to be shared across all clients, the `state` schema can be different for each client (e.g. to allow for a different SQLite table design on the server side).
11
-
12
- ## Example
13
-
14
- <Tabs>
15
- <TabItem label="main.ts">
16
-
17
- ## `reference/syncing/server-side-clients/main.ts`
18
-
19
- ```ts filename="reference/syncing/server-side-clients/main.ts"
20
- /** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet keeps inline setup */
21
- // ---cut---
22
-
23
- const adapter = makeAdapter({
24
- storage: { type: 'fs', baseDirectory: 'tmp' },
25
- sync: { backend: makeWsSync({ url: 'ws://localhost:8787' }), onSyncError: 'shutdown' },
26
- })
27
-
28
- const store = await createStorePromise({
29
- adapter,
30
- schema,
31
- storeId: 'test',
32
- syncPayload: { authToken: 'insecure-token-change-me' },
33
- })
34
-
35
- const todos = store.query(tables.todos.where({ completed: false }))
36
- ```
37
-
38
- ### `reference/syncing/server-side-clients/schema.ts`
39
-
40
- ```ts filename="reference/syncing/server-side-clients/schema.ts"
41
-
42
- const events = {}
43
-
44
- const tables = {
45
- todos: State.SQLite.table({
46
- name: 'todos',
47
- columns: {
48
- id: State.SQLite.text({ primaryKey: true }),
49
- text: State.SQLite.text(),
50
- completed: State.SQLite.boolean({ default: false }),
51
- },
52
- }),
53
- }
54
-
55
- const state = State.SQLite.makeState({ tables, materializers: {} })
56
-
57
- export const schema = makeSchema({ events, state })
58
-
59
- export { tables }
60
- ```
61
-
62
- </TabItem>
63
- <TabItem label="schema.ts">
64
-
65
- ## `reference/syncing/server-side-clients/schema.ts`
66
-
67
- ```ts filename="reference/syncing/server-side-clients/schema.ts"
68
-
69
- const events = {}
70
-
71
- const tables = {
72
- todos: State.SQLite.table({
73
- name: 'todos',
74
- columns: {
75
- id: State.SQLite.text({ primaryKey: true }),
76
- text: State.SQLite.text(),
77
- completed: State.SQLite.boolean({ default: false }),
78
- },
79
- }),
80
- }
81
-
82
- const state = State.SQLite.makeState({ tables, materializers: {} })
83
-
84
- export const schema = makeSchema({ events, state })
85
-
86
- export { tables }
87
- ```
88
-
89
- </TabItem>
90
- </Tabs>
91
-
92
- ## Further notes
93
-
94
- ### Cloudflare Workers
95
-
96
- - The `@livestore/adapter-node` adapter doesn't yet work with Cloudflare Workers but you can follow [this issue](https://github.com/livestorejs/livestore/issues/266) for a Cloudflare adapter to enable this use case.
97
- - Having a `@livestore/adapter-cf-worker` adapter could enable serverless server-side client scenarios.
@@ -1,11 +0,0 @@
1
- # Side effect
2
-
3
- TODO: Document how to safely run side-effects as response to LiveStore events.
4
-
5
- Notes for writing those docs:
6
- - Scenarios:
7
- - Run side-effect in each client session
8
- - Run side-effect only once per client (i.e. use a lock between client sessions)
9
- - Run side-effect only once globally (will require some kind of global transaction)
10
- - How to deal with rollbacks/rebases
11
- - Allow for filtering events based on whether they have been confirmed by the sync backend or include unconfirmed events
@@ -1,11 +0,0 @@
1
- # State machines
2
-
3
- LiveStore can be used to implement state machines or together with existing state machine libraries (e.g. [XState](https://stately.ai/docs/xstate)).
4
-
5
- The basic idea is to listen query results and emit events when the query results change. The state machine side effects can then further commit new mutations to LiveStore.
6
-
7
- ## Example
8
-
9
- ```ts
10
- // TODO (contribution welcome)
11
- ```