@livestore/livestore 0.0.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 (205) hide show
  1. package/README.md +108 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/LiveRiffleStore.d.ts +42 -0
  4. package/dist/LiveRiffleStore.d.ts.map +1 -0
  5. package/dist/LiveRiffleStore.js +36 -0
  6. package/dist/LiveRiffleStore.js.map +1 -0
  7. package/dist/QueryCache.d.ts +20 -0
  8. package/dist/QueryCache.d.ts.map +1 -0
  9. package/dist/QueryCache.js +71 -0
  10. package/dist/QueryCache.js.map +1 -0
  11. package/dist/__tests__/react/fixture.d.ts +141 -0
  12. package/dist/__tests__/react/fixture.d.ts.map +1 -0
  13. package/dist/__tests__/react/fixture.js +72 -0
  14. package/dist/__tests__/react/fixture.js.map +1 -0
  15. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +2 -0
  16. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
  17. package/dist/__tests__/react/useLiveStoreComponent.test.js +78 -0
  18. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
  19. package/dist/__tests__/react/useRiffleComponent.test.d.ts +2 -0
  20. package/dist/__tests__/react/useRiffleComponent.test.d.ts.map +1 -0
  21. package/dist/__tests__/react/useRiffleComponent.test.js +78 -0
  22. package/dist/__tests__/react/useRiffleComponent.test.js.map +1 -0
  23. package/dist/__tests__/reactive.test.d.ts +2 -0
  24. package/dist/__tests__/reactive.test.d.ts.map +1 -0
  25. package/dist/__tests__/reactive.test.js +167 -0
  26. package/dist/__tests__/reactive.test.js.map +1 -0
  27. package/dist/backends/base.d.ts +13 -0
  28. package/dist/backends/base.d.ts.map +1 -0
  29. package/dist/backends/base.js +53 -0
  30. package/dist/backends/base.js.map +1 -0
  31. package/dist/backends/index.d.ts +41 -0
  32. package/dist/backends/index.d.ts.map +1 -0
  33. package/dist/backends/index.js +38 -0
  34. package/dist/backends/index.js.map +1 -0
  35. package/dist/backends/noop.d.ts +18 -0
  36. package/dist/backends/noop.d.ts.map +1 -0
  37. package/dist/backends/noop.js +21 -0
  38. package/dist/backends/noop.js.map +1 -0
  39. package/dist/backends/tauri.d.ts +24 -0
  40. package/dist/backends/tauri.d.ts.map +1 -0
  41. package/dist/backends/tauri.js +48 -0
  42. package/dist/backends/tauri.js.map +1 -0
  43. package/dist/backends/utils/idb.d.ts +10 -0
  44. package/dist/backends/utils/idb.d.ts.map +1 -0
  45. package/dist/backends/utils/idb.js +58 -0
  46. package/dist/backends/utils/idb.js.map +1 -0
  47. package/dist/backends/web-in-memory.d.ts +24 -0
  48. package/dist/backends/web-in-memory.d.ts.map +1 -0
  49. package/dist/backends/web-in-memory.js +46 -0
  50. package/dist/backends/web-in-memory.js.map +1 -0
  51. package/dist/backends/web-worker.d.ts +17 -0
  52. package/dist/backends/web-worker.d.ts.map +1 -0
  53. package/dist/backends/web-worker.js +139 -0
  54. package/dist/backends/web-worker.js.map +1 -0
  55. package/dist/backends/web.d.ts +28 -0
  56. package/dist/backends/web.d.ts.map +1 -0
  57. package/dist/backends/web.js +64 -0
  58. package/dist/backends/web.js.map +1 -0
  59. package/dist/bounded-collections.d.ts +34 -0
  60. package/dist/bounded-collections.d.ts.map +1 -0
  61. package/dist/bounded-collections.js +103 -0
  62. package/dist/bounded-collections.js.map +1 -0
  63. package/dist/componentKey.d.ts +20 -0
  64. package/dist/componentKey.d.ts.map +1 -0
  65. package/dist/componentKey.js +3 -0
  66. package/dist/componentKey.js.map +1 -0
  67. package/dist/effect/LiveStore.d.ts +42 -0
  68. package/dist/effect/LiveStore.d.ts.map +1 -0
  69. package/dist/effect/LiveStore.js +36 -0
  70. package/dist/effect/LiveStore.js.map +1 -0
  71. package/dist/effect/index.d.ts +2 -0
  72. package/dist/effect/index.d.ts.map +1 -0
  73. package/dist/effect/index.js +2 -0
  74. package/dist/effect/index.js.map +1 -0
  75. package/dist/events.d.ts +7 -0
  76. package/dist/events.d.ts.map +1 -0
  77. package/dist/events.js +2 -0
  78. package/dist/events.js.map +1 -0
  79. package/dist/inMemoryDatabase.d.ts +65 -0
  80. package/dist/inMemoryDatabase.d.ts.map +1 -0
  81. package/dist/inMemoryDatabase.js +241 -0
  82. package/dist/inMemoryDatabase.js.map +1 -0
  83. package/dist/index.d.ts +20 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +10 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/otel.d.ts +5 -0
  88. package/dist/otel.d.ts.map +1 -0
  89. package/dist/otel.js +17 -0
  90. package/dist/otel.js.map +1 -0
  91. package/dist/react/LiveStoreContext.d.ts +11 -0
  92. package/dist/react/LiveStoreContext.d.ts.map +1 -0
  93. package/dist/react/LiveStoreContext.js +10 -0
  94. package/dist/react/LiveStoreContext.js.map +1 -0
  95. package/dist/react/LiveStoreProvider.d.ts +21 -0
  96. package/dist/react/LiveStoreProvider.d.ts.map +1 -0
  97. package/dist/react/LiveStoreProvider.js +48 -0
  98. package/dist/react/LiveStoreProvider.js.map +1 -0
  99. package/dist/react/RiffleProvider.d.ts +21 -0
  100. package/dist/react/RiffleProvider.d.ts.map +1 -0
  101. package/dist/react/RiffleProvider.js +48 -0
  102. package/dist/react/RiffleProvider.js.map +1 -0
  103. package/dist/react/StoreContext.d.ts +11 -0
  104. package/dist/react/StoreContext.d.ts.map +1 -0
  105. package/dist/react/StoreContext.js +10 -0
  106. package/dist/react/StoreContext.js.map +1 -0
  107. package/dist/react/index.d.ts +7 -0
  108. package/dist/react/index.d.ts.map +1 -0
  109. package/dist/react/index.js +6 -0
  110. package/dist/react/index.js.map +1 -0
  111. package/dist/react/useGlobalQuery.d.ts +3 -0
  112. package/dist/react/useGlobalQuery.d.ts.map +1 -0
  113. package/dist/react/useGlobalQuery.js +25 -0
  114. package/dist/react/useGlobalQuery.js.map +1 -0
  115. package/dist/react/useGraphQL.d.ts +11 -0
  116. package/dist/react/useGraphQL.d.ts.map +1 -0
  117. package/dist/react/useGraphQL.js +68 -0
  118. package/dist/react/useGraphQL.js.map +1 -0
  119. package/dist/react/useLiveStoreComponent.d.ts +70 -0
  120. package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
  121. package/dist/react/useLiveStoreComponent.js +261 -0
  122. package/dist/react/useLiveStoreComponent.js.map +1 -0
  123. package/dist/react/useRiffleComponent.d.ts +70 -0
  124. package/dist/react/useRiffleComponent.d.ts.map +1 -0
  125. package/dist/react/useRiffleComponent.js +261 -0
  126. package/dist/react/useRiffleComponent.js.map +1 -0
  127. package/dist/react/useRiffleJsonHook.d.ts +4 -0
  128. package/dist/react/useRiffleJsonHook.d.ts.map +1 -0
  129. package/dist/react/useRiffleJsonHook.js +21 -0
  130. package/dist/react/useRiffleJsonHook.js.map +1 -0
  131. package/dist/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
  132. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
  133. package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
  134. package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
  135. package/dist/reactive.d.ts +140 -0
  136. package/dist/reactive.d.ts.map +1 -0
  137. package/dist/reactive.js +301 -0
  138. package/dist/reactive.js.map +1 -0
  139. package/dist/reactiveQueries/base-class.d.ts +24 -0
  140. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  141. package/dist/reactiveQueries/base-class.js +22 -0
  142. package/dist/reactiveQueries/base-class.js.map +1 -0
  143. package/dist/reactiveQueries/graphql.d.ts +25 -0
  144. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  145. package/dist/reactiveQueries/graphql.js +14 -0
  146. package/dist/reactiveQueries/graphql.js.map +1 -0
  147. package/dist/reactiveQueries/js.d.ts +19 -0
  148. package/dist/reactiveQueries/js.d.ts.map +1 -0
  149. package/dist/reactiveQueries/js.js +13 -0
  150. package/dist/reactiveQueries/js.js.map +1 -0
  151. package/dist/reactiveQueries/sql.d.ts +31 -0
  152. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  153. package/dist/reactiveQueries/sql.js +28 -0
  154. package/dist/reactiveQueries/sql.js.map +1 -0
  155. package/dist/schema.d.ts +163 -0
  156. package/dist/schema.d.ts.map +1 -0
  157. package/dist/schema.js +92 -0
  158. package/dist/schema.js.map +1 -0
  159. package/dist/store.d.ts +175 -0
  160. package/dist/store.d.ts.map +1 -0
  161. package/dist/store.js +546 -0
  162. package/dist/store.js.map +1 -0
  163. package/dist/util.d.ts +24 -0
  164. package/dist/util.d.ts.map +1 -0
  165. package/dist/util.js +51 -0
  166. package/dist/util.js.map +1 -0
  167. package/package.json +52 -0
  168. package/src/QueryCache.ts +81 -0
  169. package/src/__tests__/react/fixture.tsx +106 -0
  170. package/src/__tests__/react/useLiveStoreComponent.test.tsx +111 -0
  171. package/src/__tests__/reactive.test.ts +227 -0
  172. package/src/ambient.d.ts +7 -0
  173. package/src/backends/base.ts +67 -0
  174. package/src/backends/index.ts +94 -0
  175. package/src/backends/noop.ts +32 -0
  176. package/src/backends/tauri.ts +74 -0
  177. package/src/backends/utils/idb.ts +71 -0
  178. package/src/backends/web-in-memory.ts +65 -0
  179. package/src/backends/web-worker.ts +176 -0
  180. package/src/backends/web.ts +96 -0
  181. package/src/bounded-collections.ts +112 -0
  182. package/src/componentKey.ts +9 -0
  183. package/src/effect/LiveStore.ts +123 -0
  184. package/src/effect/index.ts +7 -0
  185. package/src/events.ts +8 -0
  186. package/src/inMemoryDatabase.ts +347 -0
  187. package/src/index.ts +47 -0
  188. package/src/otel.ts +20 -0
  189. package/src/react/LiveStoreContext.ts +23 -0
  190. package/src/react/LiveStoreProvider.tsx +93 -0
  191. package/src/react/index.ts +11 -0
  192. package/src/react/useGlobalQuery.ts +40 -0
  193. package/src/react/useGraphQL.ts +113 -0
  194. package/src/react/useLiveStoreComponent.ts +493 -0
  195. package/src/react/utils/useStateRefWithReactiveInput.ts +51 -0
  196. package/src/reactive.ts +538 -0
  197. package/src/reactiveQueries/base-class.ts +49 -0
  198. package/src/reactiveQueries/graphql.ts +52 -0
  199. package/src/reactiveQueries/js.ts +38 -0
  200. package/src/reactiveQueries/sql.ts +65 -0
  201. package/src/schema.ts +219 -0
  202. package/src/store.ts +889 -0
  203. package/src/util.ts +59 -0
  204. package/tsconfig.json +15 -0
  205. package/vitest.config.js +13 -0
@@ -0,0 +1,65 @@
1
+ import type * as otel from '@opentelemetry/api'
2
+
3
+ import type { ComponentKey } from '../componentKey.js'
4
+ import type { GetAtom, Thunk } from '../reactive.js'
5
+ import type { Store } from '../store.js'
6
+ import { LiveStoreQueryBase } from './base-class.js'
7
+ import type { LiveStoreJSQuery } from './js.js'
8
+
9
+ /* An object encapsulating a reactive SQL query */
10
+ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
11
+ _tag: 'sql' = 'sql'
12
+ /** A reactive thunk representing the query text */
13
+ queryString$: Thunk<string>
14
+ /** A reactive thunk representing the query results */
15
+ results$: Thunk<Row[]>
16
+
17
+ constructor({
18
+ queryString$,
19
+ results$,
20
+ ...baseProps
21
+ }: {
22
+ queryString$: Thunk<string>
23
+ results$: Thunk<Row[]>
24
+ componentKey: ComponentKey
25
+ label: string
26
+ store: Store<any>
27
+ otelContext: otel.Context
28
+ }) {
29
+ super(baseProps)
30
+
31
+ this.queryString$ = queryString$
32
+ this.results$ = results$
33
+ }
34
+
35
+ /**
36
+ * Returns a new reactive query that contains the result of
37
+ * running an arbitrary JS computation on the results of this SQL query.
38
+ */
39
+ pipe = <U>(f: (result: Row[], get: GetAtom) => U): LiveStoreJSQuery<U> =>
40
+ this.store.queryJS(
41
+ (get) => {
42
+ const results = get(this.results$)
43
+ return f(results, get)
44
+ },
45
+ this.componentKey,
46
+ `${this.label}:js`,
47
+ this.otelContext,
48
+ )
49
+
50
+ /** Returns a reactive query */
51
+ getFirstRow = (args?: { defaultValue?: Row }) =>
52
+ this.store.queryJS(
53
+ (get) => {
54
+ const results = get(this.results$)
55
+ if (results.length === 0 && args?.defaultValue === undefined) {
56
+ const queryLabel = this._tag === 'sql' ? this.queryString$.result : this.label
57
+ throw new Error(`Expected query ${queryLabel} to return at least one result`)
58
+ }
59
+ return (results[0] ?? args?.defaultValue) as Row
60
+ },
61
+ this.componentKey,
62
+ `${this.label}:first`,
63
+ this.otelContext,
64
+ )
65
+ }
package/src/schema.ts ADDED
@@ -0,0 +1,219 @@
1
+ import type { Backend } from './backends/index.js'
2
+ import { EVENTS_TABLE_NAME } from './events.js'
3
+ import type { InMemoryDatabase } from './inMemoryDatabase.js'
4
+ import { sql } from './util.js'
5
+
6
+ export type ColumnDefinition = {
7
+ nullable?: boolean
8
+ primaryKey?: boolean
9
+ } & (
10
+ | { type: 'text'; default?: string }
11
+ | { type: 'json'; default?: string }
12
+ | { type: 'integer'; default?: number }
13
+ | { type: 'boolean'; default?: boolean }
14
+ | { type: 'real'; default?: number }
15
+ | { type: 'blob'; default?: any }
16
+ ) // sqlite uses numbers for booleans but we fake it
17
+
18
+ // TODO: defaults should be nullable for nullable columns
19
+ type ColumnDefinitionWithDefault = {
20
+ primaryKey?: boolean
21
+ } & (
22
+ | { type: 'text'; nullable?: true; default: string }
23
+ | { type: 'json'; nullable?: true; default: string }
24
+ | { type: 'integer'; nullable?: true; default: number }
25
+ | { type: 'boolean'; nullable?: true; default: boolean }
26
+ | { type: 'real'; nullable: true; default: number | null }
27
+ | { type: 'blob'; nullable: true; default: any | null }
28
+ )
29
+
30
+ export type TableDefinition = {
31
+ columns: {
32
+ [key: string]: ColumnDefinition
33
+ }
34
+ /**
35
+ * Can be used for various purposes e.g. to provide a foreign key constraint like below:
36
+ * ```ts
37
+ * columnsRaw: (columnsStr) => `${columnsStr}, foreign key (userId) references users(id)`
38
+ * ```
39
+ */
40
+ columnsRaw?: (columnsStr: string) => string
41
+ indexes?: Index[]
42
+ }
43
+
44
+ export type Index = {
45
+ name: string
46
+ columns: string[]
47
+ /** @default false */
48
+ isUnique?: boolean
49
+ }
50
+
51
+ export type ComponentStateSchema<T> = {
52
+ componentType: string
53
+ columns: {
54
+ [k in keyof T]: ColumnDefinitionWithDefault
55
+ }
56
+ }
57
+
58
+ // A global variable representing component state tables we should create in the database
59
+ export const componentStateTables: { [key: string]: TableDefinition } = {}
60
+
61
+ export const defineComponentStateSchema = <T>(
62
+ schema: ComponentStateSchema<T>,
63
+ ): ComponentStateSchema<T & { id: string }> => {
64
+ const tablePath = `components__${schema.componentType}`
65
+ if (Object.keys(componentStateTables).includes(tablePath)) {
66
+ // throw new Error(`Can't register duplicate component: ${name}`)
67
+ console.error(`Can't register duplicate component: ${tablePath}`)
68
+ }
69
+
70
+ const schemaWithId = schema as ComponentStateSchema<T & { id: string }>
71
+
72
+ schemaWithId.columns.id = { type: 'text', primaryKey: true } as any
73
+
74
+ componentStateTables[tablePath] = schemaWithId as any
75
+
76
+ return schemaWithId
77
+ }
78
+
79
+ type SQLWriteStatement = {
80
+ sql: string
81
+
82
+ /** Tables written by the statement */
83
+ writeTables: string[]
84
+ // TODO refactor this
85
+ argsAlreadyBound?: boolean
86
+ }
87
+
88
+ export type ActionDefinition<TArgs = any> = {
89
+ statement: SQLWriteStatement | ((args: TArgs) => SQLWriteStatement)
90
+ prepareBindValues?: (args: TArgs) => any
91
+ }
92
+
93
+ export type Schema = {
94
+ tables: TableDefinitions
95
+ materializedViews: MaterializedViewDefinitions
96
+ actions: ActionDefinitions<any>
97
+ }
98
+
99
+ export type TableDefinitions = { [key: string]: TableDefinition }
100
+ export type MaterializedViewDefinitions = { [key: string]: {} }
101
+ export type ActionDefinitions<TArgsMap extends Record<string, any>> = {
102
+ [key in keyof TArgsMap]: ActionDefinition<TArgsMap[key]>
103
+ }
104
+
105
+ export const EVENT_CURSOR_TABLE = 'livestore__event_cursor'
106
+
107
+ const systemTables = {
108
+ [EVENTS_TABLE_NAME]: {
109
+ columns: {
110
+ id: { type: 'text', primaryKey: true },
111
+ type: { type: 'text', nullable: false },
112
+ args: { type: 'text', nullable: false },
113
+ },
114
+ },
115
+ [EVENT_CURSOR_TABLE]: {
116
+ columns: {
117
+ id: { type: 'text', primaryKey: true },
118
+ cursor: { type: 'text', nullable: false },
119
+ },
120
+ },
121
+ } as const
122
+
123
+ export const defineSchema = <S extends Schema>(schema: S) => mergeSystemSchema(schema)
124
+
125
+ export const defineTables = <T extends TableDefinitions>(tables: T) => tables
126
+
127
+ export const defineMaterializedViews = <M extends MaterializedViewDefinitions>(materializedViews: M) =>
128
+ materializedViews
129
+
130
+ export const defineActions = <A extends ActionDefinitions<any>>(actions: A) => actions
131
+ export const defineAction = <TArgs extends Record<string, any>>(
132
+ action: ActionDefinition<TArgs>,
133
+ ): ActionDefinition<TArgs> => action
134
+
135
+ export type GetApplyEventArgs<TActionDefinitionsMap> = RecordValues<{
136
+ [eventType in keyof TActionDefinitionsMap]: {
137
+ eventType: eventType
138
+ args: GetActionArgs<TActionDefinitionsMap[eventType]>
139
+ }
140
+ }>
141
+
142
+ type RecordValues<T> = T extends Record<string, infer V> ? V : never
143
+
144
+ export type GetActionArgs<A> = A extends ActionDefinition<infer TArgs> ? TArgs : never
145
+
146
+ declare global {
147
+ // NOTE Can be extended
148
+ interface LiveStoreActionDefinitionsTypes {
149
+ [key: string]: ActionDefinition
150
+ }
151
+ }
152
+
153
+ const mergeSystemSchema = <S extends Schema>(schema: S) => {
154
+ return {
155
+ ...schema,
156
+ tables: {
157
+ ...schema.tables,
158
+ ...systemTables,
159
+ },
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Destructively load a schema into a database,
165
+ * dropping any existing tables and creating new ones.
166
+ */
167
+ export const loadSchema = async (backend: InMemoryDatabase | Backend, schema: Schema) => {
168
+ const fullSchemaWithComponents = { ...schema, tables: { ...schema.tables, ...componentStateTables } }
169
+
170
+ // Loop through all the tables and create them in the SQLite database
171
+ for (const [tableName, tableDefinition] of Object.entries(fullSchemaWithComponents.tables)) {
172
+ const primaryKeys = Object.entries(tableDefinition.columns)
173
+ .filter(([_, columnDef]) => columnDef.primaryKey)
174
+ .map(([columnName, _]) => columnName)
175
+ const columnDefStrs = Object.entries(tableDefinition.columns).map(([columnName, column]) =>
176
+ toSqliteColumnSpec(columnName, column),
177
+ )
178
+ if (primaryKeys.length > 0) {
179
+ columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
180
+ }
181
+ const mapColumns = tableDefinition.columnsRaw ?? ((_) => _)
182
+ const columnSpec = mapColumns(columnDefStrs.join(', '))
183
+
184
+ backend.execute(sql`drop table if exists ${tableName}`)
185
+
186
+ backend.execute(sql`create table if not exists ${tableName} (${columnSpec});`)
187
+ }
188
+
189
+ await createIndexes(backend, schema)
190
+ }
191
+
192
+ const toSqliteColumnSpec = (columnName: string, column: ColumnDefinition) => {
193
+ const columnType = column.type === 'boolean' ? 'integer' : column.type
194
+ // const primaryKey = column.primaryKey ? 'primary key' : ''
195
+ const nullable = column.nullable === false ? 'not null' : ''
196
+ const defaultValue =
197
+ column.default === undefined
198
+ ? ''
199
+ : column.type === 'text'
200
+ ? `default '${column.default}'`
201
+ : `default ${column.default}`
202
+
203
+ return `${columnName} ${columnType} ${nullable} ${defaultValue}`
204
+ }
205
+
206
+ const createIndexFromDefinition = (tableName: string, index: Index) => {
207
+ const uniqueStr = index.isUnique ? 'UNIQUE' : ''
208
+ return sql`create ${uniqueStr} index ${index.name} on ${tableName} (${index.columns.join(', ')})`
209
+ }
210
+
211
+ const createIndexes = async (db: Backend | InMemoryDatabase, schema: Schema) => {
212
+ for (const [tableName, tableDefinition] of Object.entries(schema.tables)) {
213
+ if (tableDefinition.indexes !== undefined) {
214
+ for (const index of tableDefinition.indexes) {
215
+ db.execute(createIndexFromDefinition(tableName, index))
216
+ }
217
+ }
218
+ }
219
+ }