@livestore/livestore 0.0.12 → 0.0.13

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 (197) hide show
  1. package/README.md +7 -7
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/QueryCache.d.ts +20 -0
  4. package/dist/QueryCache.d.ts.map +1 -0
  5. package/dist/QueryCache.js +71 -0
  6. package/dist/QueryCache.js.map +1 -0
  7. package/dist/__tests__/react/fixture.d.ts +25 -0
  8. package/dist/__tests__/react/fixture.d.ts.map +1 -0
  9. package/dist/__tests__/react/fixture.js +61 -0
  10. package/dist/__tests__/react/fixture.js.map +1 -0
  11. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +2 -0
  12. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
  13. package/dist/__tests__/react/useLiveStoreComponent.test.js +78 -0
  14. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
  15. package/dist/__tests__/reactive.test.d.ts +2 -0
  16. package/dist/__tests__/reactive.test.d.ts.map +1 -0
  17. package/dist/__tests__/reactive.test.js +198 -0
  18. package/dist/__tests__/reactive.test.js.map +1 -0
  19. package/dist/backends/base.d.ts +13 -0
  20. package/dist/backends/base.d.ts.map +1 -0
  21. package/dist/backends/base.js +53 -0
  22. package/dist/backends/base.js.map +1 -0
  23. package/dist/backends/in-memory/index.d.ts +22 -0
  24. package/dist/backends/in-memory/index.d.ts.map +1 -0
  25. package/dist/backends/in-memory/index.js +45 -0
  26. package/dist/backends/in-memory/index.js.map +1 -0
  27. package/dist/backends/index.d.ts +41 -0
  28. package/dist/backends/index.d.ts.map +1 -0
  29. package/dist/backends/index.js +16 -0
  30. package/dist/backends/index.js.map +1 -0
  31. package/dist/backends/tauri/index.d.ts +21 -0
  32. package/dist/backends/tauri/index.d.ts.map +1 -0
  33. package/dist/backends/tauri/index.js +48 -0
  34. package/dist/backends/tauri/index.js.map +1 -0
  35. package/dist/backends/utils/idb.d.ts +10 -0
  36. package/dist/backends/utils/idb.d.ts.map +1 -0
  37. package/dist/backends/utils/idb.js +58 -0
  38. package/dist/backends/utils/idb.js.map +1 -0
  39. package/dist/backends/web-worker/index.d.ts +26 -0
  40. package/dist/backends/web-worker/index.d.ts.map +1 -0
  41. package/dist/backends/web-worker/index.js +63 -0
  42. package/dist/backends/web-worker/index.js.map +1 -0
  43. package/dist/backends/web-worker/worker.d.ts +17 -0
  44. package/dist/backends/web-worker/worker.d.ts.map +1 -0
  45. package/dist/backends/web-worker/worker.js +139 -0
  46. package/dist/backends/web-worker/worker.js.map +1 -0
  47. package/dist/bounded-collections.d.ts +34 -0
  48. package/dist/bounded-collections.d.ts.map +1 -0
  49. package/dist/bounded-collections.js +103 -0
  50. package/dist/bounded-collections.js.map +1 -0
  51. package/dist/componentKey.d.ts +20 -0
  52. package/dist/componentKey.d.ts.map +1 -0
  53. package/dist/componentKey.js +3 -0
  54. package/dist/componentKey.js.map +1 -0
  55. package/dist/effect/LiveStore.d.ts +42 -0
  56. package/dist/effect/LiveStore.d.ts.map +1 -0
  57. package/dist/effect/LiveStore.js +37 -0
  58. package/dist/effect/LiveStore.js.map +1 -0
  59. package/dist/effect/index.d.ts +2 -0
  60. package/dist/effect/index.d.ts.map +1 -0
  61. package/dist/effect/index.js +2 -0
  62. package/dist/effect/index.js.map +1 -0
  63. package/dist/events.d.ts +7 -0
  64. package/dist/events.d.ts.map +1 -0
  65. package/dist/events.js +2 -0
  66. package/dist/events.js.map +1 -0
  67. package/dist/inMemoryDatabase.d.ts +60 -0
  68. package/dist/inMemoryDatabase.d.ts.map +1 -0
  69. package/dist/inMemoryDatabase.js +230 -0
  70. package/dist/inMemoryDatabase.js.map +1 -0
  71. package/dist/index.d.ts +20 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +9 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/migrations.d.ts +9 -0
  76. package/dist/migrations.d.ts.map +1 -0
  77. package/dist/migrations.js +62 -0
  78. package/dist/migrations.js.map +1 -0
  79. package/dist/otel.d.ts +4 -0
  80. package/dist/otel.d.ts.map +1 -0
  81. package/dist/otel.js +6 -0
  82. package/dist/otel.js.map +1 -0
  83. package/dist/react/LiveStoreContext.d.ts +11 -0
  84. package/dist/react/LiveStoreContext.d.ts.map +1 -0
  85. package/dist/react/LiveStoreContext.js +10 -0
  86. package/dist/react/LiveStoreContext.js.map +1 -0
  87. package/dist/react/LiveStoreProvider.d.ts +22 -0
  88. package/dist/react/LiveStoreProvider.d.ts.map +1 -0
  89. package/dist/react/LiveStoreProvider.js +49 -0
  90. package/dist/react/LiveStoreProvider.js.map +1 -0
  91. package/dist/react/index.d.ts +8 -0
  92. package/dist/react/index.d.ts.map +1 -0
  93. package/dist/react/index.js +6 -0
  94. package/dist/react/index.js.map +1 -0
  95. package/dist/react/useGlobalQuery.d.ts +3 -0
  96. package/dist/react/useGlobalQuery.d.ts.map +1 -0
  97. package/dist/react/useGlobalQuery.js +23 -0
  98. package/dist/react/useGlobalQuery.js.map +1 -0
  99. package/dist/react/useGraphQL.d.ts +11 -0
  100. package/dist/react/useGraphQL.d.ts.map +1 -0
  101. package/dist/react/useGraphQL.js +67 -0
  102. package/dist/react/useGraphQL.js.map +1 -0
  103. package/dist/react/useLiveStoreComponent.d.ts +75 -0
  104. package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
  105. package/dist/react/useLiveStoreComponent.js +301 -0
  106. package/dist/react/useLiveStoreComponent.js.map +1 -0
  107. package/dist/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
  108. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
  109. package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
  110. package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
  111. package/dist/reactive.d.ts +140 -0
  112. package/dist/reactive.d.ts.map +1 -0
  113. package/dist/reactive.js +302 -0
  114. package/dist/reactive.js.map +1 -0
  115. package/dist/reactiveQueries/base-class.d.ts +24 -0
  116. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  117. package/dist/reactiveQueries/base-class.js +22 -0
  118. package/dist/reactiveQueries/base-class.js.map +1 -0
  119. package/dist/reactiveQueries/graphql.d.ts +25 -0
  120. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  121. package/dist/reactiveQueries/graphql.js +18 -0
  122. package/dist/reactiveQueries/graphql.js.map +1 -0
  123. package/dist/reactiveQueries/js.d.ts +19 -0
  124. package/dist/reactiveQueries/js.d.ts.map +1 -0
  125. package/dist/reactiveQueries/js.js +13 -0
  126. package/dist/reactiveQueries/js.js.map +1 -0
  127. package/dist/reactiveQueries/sql.d.ts +31 -0
  128. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  129. package/dist/reactiveQueries/sql.js +32 -0
  130. package/dist/reactiveQueries/sql.js.map +1 -0
  131. package/dist/schema.d.ts +83 -0
  132. package/dist/schema.d.ts.map +1 -0
  133. package/dist/schema.js +49 -0
  134. package/dist/schema.js.map +1 -0
  135. package/dist/storage/base.d.ts +10 -0
  136. package/dist/storage/base.d.ts.map +1 -0
  137. package/dist/storage/base.js +14 -0
  138. package/dist/storage/base.js.map +1 -0
  139. package/dist/storage/in-memory/index.d.ts +15 -0
  140. package/dist/storage/in-memory/index.d.ts.map +1 -0
  141. package/dist/storage/in-memory/index.js +14 -0
  142. package/dist/storage/in-memory/index.js.map +1 -0
  143. package/dist/storage/index.d.ts +14 -0
  144. package/dist/storage/index.d.ts.map +1 -0
  145. package/dist/storage/index.js +9 -0
  146. package/dist/storage/index.js.map +1 -0
  147. package/dist/storage/tauri/index.d.ts +19 -0
  148. package/dist/storage/tauri/index.d.ts.map +1 -0
  149. package/dist/storage/tauri/index.js +38 -0
  150. package/dist/storage/tauri/index.js.map +1 -0
  151. package/dist/storage/utils/idb.d.ts +10 -0
  152. package/dist/storage/utils/idb.d.ts.map +1 -0
  153. package/dist/storage/utils/idb.js +58 -0
  154. package/dist/storage/utils/idb.js.map +1 -0
  155. package/dist/storage/web-worker/index.d.ts +27 -0
  156. package/dist/storage/web-worker/index.d.ts.map +1 -0
  157. package/dist/storage/web-worker/index.js +76 -0
  158. package/dist/storage/web-worker/index.js.map +1 -0
  159. package/dist/storage/web-worker/worker.d.ts +13 -0
  160. package/dist/storage/web-worker/worker.d.ts.map +1 -0
  161. package/dist/storage/web-worker/worker.js +110 -0
  162. package/dist/storage/web-worker/worker.js.map +1 -0
  163. package/dist/store.d.ts +192 -0
  164. package/dist/store.d.ts.map +1 -0
  165. package/dist/store.js +569 -0
  166. package/dist/store.js.map +1 -0
  167. package/dist/util.d.ts +26 -0
  168. package/dist/util.d.ts.map +1 -0
  169. package/dist/util.js +53 -0
  170. package/dist/util.js.map +1 -0
  171. package/package.json +46 -19
  172. package/src/__tests__/react/fixture.tsx +19 -28
  173. package/src/effect/LiveStore.ts +8 -13
  174. package/src/events.ts +1 -1
  175. package/src/inMemoryDatabase.ts +100 -117
  176. package/src/index.ts +10 -16
  177. package/src/migrations.ts +101 -0
  178. package/src/otel.ts +0 -11
  179. package/src/react/LiveStoreProvider.tsx +12 -8
  180. package/src/react/index.ts +9 -0
  181. package/src/react/useGlobalQuery.ts +0 -3
  182. package/src/react/useLiveStoreComponent.ts +95 -37
  183. package/src/schema.ts +72 -145
  184. package/src/storage/in-memory/index.ts +21 -0
  185. package/src/storage/index.ts +27 -0
  186. package/src/{backends/tauri.ts → storage/tauri/index.ts} +13 -27
  187. package/src/storage/web-worker/index.ts +118 -0
  188. package/src/{backends/web-worker.ts → storage/web-worker/worker.ts} +17 -52
  189. package/src/store.ts +112 -79
  190. package/src/util.ts +5 -1
  191. package/tsconfig.json +1 -3
  192. package/src/backends/base.ts +0 -67
  193. package/src/backends/index.ts +0 -98
  194. package/src/backends/noop.ts +0 -32
  195. package/src/backends/web-in-memory.ts +0 -65
  196. package/src/backends/web.ts +0 -97
  197. /package/src/{backends → storage}/utils/idb.ts +0 -0
package/dist/util.js ADDED
@@ -0,0 +1,53 @@
1
+ /// <reference lib="es2022" />
2
+ /**
3
+ * This is a tag function for tagged literals.
4
+ * it lets us get syntax highlighting on SQL queries in VSCode, but
5
+ * doesn't do anything at runtime.
6
+ * Code copied from: https://esdiscuss.org/topic/string-identity-template-tag
7
+ */
8
+ export const sql = (template, ...args) => {
9
+ let str = '';
10
+ for (const [i, arg] of args.entries()) {
11
+ str += template[i] + String(arg);
12
+ }
13
+ return str + template.at(-1);
14
+ };
15
+ /** Prepare bind values to send to SQLite
16
+ /* Add $ to the beginning of keys; which we use as our interpolation syntax
17
+ /* We also strip out any params that aren't used in the statement,
18
+ /* because rusqlite doesn't allow unused named params
19
+ /* TODO: Search for unused params via proper parsing, not string search
20
+ **/
21
+ export const prepareBindValues = (values, statement) => {
22
+ const result = {};
23
+ for (const [key, value] of Object.entries(values)) {
24
+ if (statement.includes(key)) {
25
+ result[`$${key}`] = value;
26
+ }
27
+ }
28
+ return result;
29
+ };
30
+ /**
31
+ * Use this to make assertion at end of if-else chain that all members of a
32
+ * union have been accounted for.
33
+ */
34
+ /* eslint-disable-next-line prefer-arrow/prefer-arrow-functions */
35
+ export function casesHandled(x) {
36
+ throw new Error(`A case was not handled for value: ${objectToString(x)}`);
37
+ }
38
+ export const objectToString = (error) => {
39
+ const stack = typeof process !== 'undefined' && process.env.CL_DEBUG ? error.stack : undefined;
40
+ const str = error.toString();
41
+ const stackStr = stack ? `\n${stack}` : '';
42
+ if (str !== '[object Object]')
43
+ return str + stackStr;
44
+ try {
45
+ return JSON.stringify({ ...error, stack }, null, 2);
46
+ }
47
+ catch (e) {
48
+ console.log(error);
49
+ return 'Error while printing error: ' + e;
50
+ }
51
+ };
52
+ export const isPromise = (value) => typeof value?.then === 'function';
53
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAO9B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,QAA8B,EAAE,GAAG,IAAe,EAAU,EAAE;IAChF,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE;QACrC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;KACjC;IACD,OAAO,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9B,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAoB,EAAE,SAAiB,EAAgB,EAAE;IACzF,MAAM,MAAM,GAAiB,EAAE,CAAA;IAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QACjD,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;YAC3B,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,KAAK,CAAA;SAC1B;KACF;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED;;;GAGG;AACH,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,MAAM,IAAI,KAAK,CAAC,qCAAqC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC3E,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAU,EAAU,EAAE;IACnD,MAAM,KAAK,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;IAC9F,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAA;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1C,IAAI,GAAG,KAAK,iBAAiB;QAAE,OAAO,GAAG,GAAG,QAAQ,CAAA;IAEpD,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;KACpD;IAAC,OAAO,CAAM,EAAE;QACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAElB,OAAO,8BAA8B,GAAG,CAAC,CAAA;KAC1C;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAU,EAA6B,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,KAAK,UAAU,CAAA"}
package/package.json CHANGED
@@ -1,15 +1,36 @@
1
1
  {
2
2
  "name": "@livestore/livestore",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
5
  "exports": {
9
- ".": "./dist/index.js",
10
- "./react": "./dist/react/index.js",
11
- "./effect": "./dist/effect/index.js",
12
- "./util": "./dist/util.js"
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ },
10
+ "./storage/web-worker": {
11
+ "types": "./dist/storage/web-worker/index.d.ts",
12
+ "default": "./dist/storage/web-worker/index.js"
13
+ },
14
+ "./storage/in-memory": {
15
+ "types": "./dist/storage/in-memory/index.d.ts",
16
+ "default": "./dist/storage/in-memory/index.js"
17
+ },
18
+ "./storage/tauri": {
19
+ "types": "./dist/storage/tauri/index.d.ts",
20
+ "default": "./dist/storage/tauri/index.js"
21
+ },
22
+ "./react": {
23
+ "types": "./dist/react/index.d.ts",
24
+ "default": "./dist/react/index.js"
25
+ },
26
+ "./effect": {
27
+ "types": "./dist/effect/index.d.ts",
28
+ "default": "./dist/effect/index.js"
29
+ },
30
+ "./util": {
31
+ "types": "./dist/util.d.ts",
32
+ "default": "./dist/util.js"
33
+ }
13
34
  },
14
35
  "types": "./dist/index.d.ts",
15
36
  "typesVersions": {
@@ -34,11 +55,26 @@
34
55
  "@livestore/utils": "workspace:*",
35
56
  "@opentelemetry/api": "^1.6.0",
36
57
  "comlink": "^4.4.1",
58
+ "effect-db-schema": "workspace:*",
37
59
  "graphql": "^16.8.1",
38
60
  "lodash-es": "^4.17.21",
39
61
  "sqlite-esm": "3.42.0-build6",
40
62
  "uuid": "^9.0.1"
41
63
  },
64
+ "devDependencies": {
65
+ "@tauri-apps/api": "^1.5.0",
66
+ "@testing-library/react": "^14.0.0",
67
+ "@types/lodash-es": "^4.17.9",
68
+ "@types/react": "^18.2.28",
69
+ "@types/react-dom": "^18.2.13",
70
+ "@types/uuid": "^9.0.5",
71
+ "jsdom": "^22.1.0",
72
+ "react": "^18.2.0",
73
+ "react-dom": "^18.2.0",
74
+ "typescript": "5.2.2",
75
+ "vite": "4.4.11",
76
+ "vitest": "^0.34.6"
77
+ },
42
78
  "peerDependencies": {
43
79
  "@tauri-apps/api": "^1.4.0",
44
80
  "react": "^18",
@@ -49,16 +85,7 @@
49
85
  "optional": true
50
86
  }
51
87
  },
52
- "devDependencies": {
53
- "@tauri-apps/api": "^1.4.0",
54
- "@testing-library/react": "^14.0.0",
55
- "@types/lodash-es": "^4.17.9",
56
- "@types/uuid": "^9.0.4",
57
- "jsdom": "^22.1.0",
58
- "react": "^18.2.0",
59
- "react-dom": "^18.2.0",
60
- "typescript": "5.2.2",
61
- "vite": "4.4.9",
62
- "vitest": "^0.34.6"
88
+ "publishConfig": {
89
+ "access": "public"
63
90
  }
64
91
  }
@@ -4,6 +4,7 @@ import React from 'react'
4
4
  import * as LiveStore from '../../index.js'
5
5
  import { sql } from '../../index.js'
6
6
  import * as LiveStoreReact from '../../react/index.js'
7
+ import { InMemoryStorage } from '../../storage/in-memory/index.js'
7
8
 
8
9
  export type Todo = {
9
10
  id: string
@@ -25,24 +26,19 @@ export const globalQueryDefs = {
25
26
  appState,
26
27
  }
27
28
 
28
- export const schema = LiveStore.defineSchema({
29
+ export const schema = LiveStore.makeSchema({
29
30
  tables: {
30
- todos: {
31
- columns: {
32
- id: { type: 'text', primaryKey: true },
33
- text: { type: 'text', default: '', nullable: false },
34
- completed: { type: 'boolean', default: false, nullable: false },
35
- },
36
- },
37
- app: {
38
- columns: {
39
- id: { type: 'text', primaryKey: true },
40
- newTodoText: { type: 'text', default: '', nullable: true },
41
- filter: { type: 'text', default: 'all', nullable: false },
42
- },
43
- },
31
+ todos: LiveStore.DbSchema.table('todos', {
32
+ id: LiveStore.DbSchema.text({ primaryKey: true }),
33
+ text: LiveStore.DbSchema.text({ default: '', nullable: false }),
34
+ completed: LiveStore.DbSchema.boolean({ default: false, nullable: false }),
35
+ }),
36
+ app: LiveStore.DbSchema.table('app', {
37
+ id: LiveStore.DbSchema.text({ primaryKey: true }),
38
+ newTodoText: LiveStore.DbSchema.text({ default: '', nullable: true }),
39
+ filter: LiveStore.DbSchema.text({ default: 'all', nullable: false }),
40
+ }),
44
41
  },
45
- materializedViews: {},
46
42
  actions: {
47
43
  // TODO: fix these actions to make them have write annotatinos
48
44
  addTodo: {
@@ -63,23 +59,18 @@ export const schema = LiveStore.defineSchema({
63
59
  })
64
60
 
65
61
  export const makeTodoMvc = async () => {
66
- type UserInfoComponentState = { username: string }
67
-
68
- const AppSchema = LiveStore.defineComponentStateSchema<UserInfoComponentState>({
69
- componentType: 'UserInfo',
70
- columns: {
71
- username: { type: 'text', default: '' },
72
- },
62
+ const AppSchema = LiveStore.defineComponentStateSchema('UserInfo', {
63
+ username: LiveStore.DbSchema.text({ default: '' }),
73
64
  })
74
65
 
75
66
  const store = await LiveStore.createStore({
76
67
  schema,
77
- backendOptions: { type: 'web-in-memory' },
78
- boot: async (backend) => {
79
- backend.execute(sql`INSERT INTO app (newTodoText, filter) VALUES ('', 'all');`)
68
+ loadStorage: () => InMemoryStorage.load(),
69
+ boot: async (storage) => {
70
+ storage.execute(sql`INSERT INTO app (newTodoText, filter) VALUES ('', 'all');`)
80
71
  // NOTE we can't insert into components__UserInfo yet because the table doesn't exist yet
81
- // backend.execute(sql`INSERT INTO components__UserInfo (id, username) VALUES ('u1', 'username_u1');`)
82
- // backend.execute(sql`INSERT INTO components__UserInfo (id, username) VALUES ('u2', 'username_u2');`)
72
+ // storage.execute(sql`INSERT INTO components__UserInfo (id, username) VALUES ('u1', 'username_u1');`)
73
+ // storage.execute(sql`INSERT INTO components__UserInfo (id, username) VALUES ('u2', 'username_u2');`)
83
74
  },
84
75
  })
85
76
 
@@ -4,9 +4,9 @@ import * as otel from '@opentelemetry/api'
4
4
  import type { GraphQLSchema } from 'graphql'
5
5
  import { mapValues } from 'lodash-es'
6
6
 
7
- import type { Backend, BackendOptions } from '../backends/index.js'
8
7
  import type { InMemoryDatabase } from '../inMemoryDatabase.js'
9
8
  import type { Schema } from '../schema.js'
9
+ import type { StorageInit } from '../storage/index.js'
10
10
  import type { BaseGraphQLContext, GraphQLOptions, LiveStoreQuery, Store } from '../store.js'
11
11
  import { createStore } from '../store.js'
12
12
 
@@ -22,11 +22,11 @@ export type GlobalQueryDefs = { [key: string]: QueryDefinition }
22
22
  export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContext> = {
23
23
  schema: Schema
24
24
  globalQueryDefs: GlobalQueryDefs
25
- backendOptions: BackendOptions
25
+ loadStorage: () => StorageInit | Promise<StorageInit>
26
26
  graphQLOptions?: GraphQLOptions<GraphQLContext>
27
27
  otelTracer?: otel.Tracer
28
28
  otelRootSpanContext?: otel.Context
29
- boot?: (backend: Backend, parentSpan: otel.Span) => Promise<void>
29
+ boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
30
30
  }
31
31
 
32
32
  export const LiveStoreContext = Context.Tag<LiveStoreContext>('@livestore/livestore/LiveStoreContext')
@@ -41,12 +41,12 @@ export const DeferredStoreContext = Context.Tag<DeferredStoreContext>(
41
41
  export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
42
42
  schema: Schema
43
43
  globalQueryDefs?: Effect.Effect<never, never, GlobalQueryDefs>
44
- backendOptions: Effect.Effect<never, never, BackendOptions>
44
+ loadStorage: () => StorageInit | Promise<StorageInit>
45
45
  graphQLOptions?: {
46
46
  schema: Effect.Effect<otel.Tracer, never, GraphQLSchema>
47
47
  makeContext: (db: InMemoryDatabase) => GraphQLContext
48
48
  }
49
- boot?: (backend: Backend) => Effect.Effect<never, never, void>
49
+ boot?: (db: InMemoryDatabase) => Effect.Effect<never, never, void>
50
50
  }
51
51
 
52
52
  export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>(
@@ -62,7 +62,7 @@ export const LiveStoreContextDeferred = Layer.effect(DeferredStoreContext, Defer
62
62
  export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>({
63
63
  globalQueryDefs,
64
64
  schema,
65
- backendOptions: backendOptions_,
65
+ loadStorage,
66
66
  graphQLOptions: graphQLOptions_,
67
67
  boot: boot_,
68
68
  }: LiveStoreContextProps<GraphQLContext>): Effect.Effect<
@@ -84,10 +84,8 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
84
84
  : Effect.succeed(undefined),
85
85
  )
86
86
 
87
- const backendOptions = yield* $(backendOptions_)
88
-
89
87
  const boot = boot_
90
- ? (db: Backend) =>
88
+ ? (db: InMemoryDatabase) =>
91
89
  boot_(db).pipe(Effect.withSpan('boot'), Effect.tapCauseLogPretty, Runtime.runPromise(runtime))
92
90
  : undefined
93
91
 
@@ -95,7 +93,7 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
95
93
  Effect.tryPromise(() =>
96
94
  createStore({
97
95
  schema,
98
- backendOptions,
96
+ loadStorage,
99
97
  graphQLOptions,
100
98
  otelTracer,
101
99
  otelRootSpanContext,
@@ -116,9 +114,6 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
116
114
  }),
117
115
  )
118
116
 
119
- // NOTE give main thread a chance to render
120
- yield* $(Effect.yieldNow())
121
-
122
117
  return { store, globalQueries }
123
118
  }),
124
119
  Effect.tap((storeCtx) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.succeed(def, storeCtx))),
package/src/events.ts CHANGED
@@ -5,4 +5,4 @@ export type LiveStoreEvent = {
5
5
  args?: any
6
6
  }
7
7
 
8
- export const EVENTS_TABLE_NAME = '__events'
8
+ export const EVENTS_TABLE_NAME = '__livestore_events'
@@ -1,18 +1,14 @@
1
1
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
2
 
3
3
  import { shouldNeverHappen } from '@livestore/utils'
4
- import { identity } from '@livestore/utils/effect'
5
4
  import type * as otel from '@opentelemetry/api'
6
5
  import type * as SqliteWasm from 'sqlite-esm'
7
- import initSqlJs from 'sqlite-esm'
8
6
 
9
7
  import BoundMap, { BoundArray } from './bounded-collections.js'
10
- import type { LiveStoreEvent } from './events.js'
11
8
  // import { EVENTS_TABLE_NAME } from './events.js'
12
9
  import { sql } from './index.js'
13
10
  import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './otel.js'
14
11
  import QueryCache from './QueryCache.js'
15
- import type { ActionDefinition } from './schema.js'
16
12
  import type { Bindable, ParamsObject } from './util.js'
17
13
  import { prepareBindValues } from './util.js'
18
14
 
@@ -66,18 +62,13 @@ export class InMemoryDatabase {
66
62
  public SQL: SqliteWasm.Sqlite3Static,
67
63
  ) {}
68
64
 
69
- static async load(
65
+ static load(
70
66
  data: Uint8Array | undefined,
71
67
  otelTracer: otel.Tracer,
72
68
  otelRootSpanContext: otel.Context,
73
- ): Promise<InMemoryDatabase> {
74
- const sqlite3 = await initSqlJs({
75
- // Required to load the wasm binary asynchronously. Of course, you can host it wherever you want
76
- // You can omit locateFile completely when running in node
77
- // locateFile: () => `/sql-wasm.wasm`,
78
- print: (message) => console.log(`[sql-client] ${message}`),
79
- printErr: (message) => console.error(`[sql-client] ${message}`),
80
- })
69
+ sqlite3: SqliteWasm.Sqlite3Static,
70
+ ): InMemoryDatabase {
71
+ // TODO move WASM init higher up in the init process (to do some other work while it's loading)
81
72
 
82
73
  const db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' }) as DatabaseWithCAPI
83
74
  db.capi = sqlite3.capi
@@ -97,7 +88,11 @@ export class InMemoryDatabase {
97
88
  )
98
89
  }
99
90
 
100
- return new InMemoryDatabase(db, otelTracer, otelRootSpanContext, sqlite3)
91
+ const inMemoryDatabase = new InMemoryDatabase(db, otelTracer, otelRootSpanContext, sqlite3)
92
+
93
+ configureSQLite(inMemoryDatabase)
94
+
95
+ return inMemoryDatabase
101
96
  }
102
97
 
103
98
  txn<TRes>(callback: () => TRes): TRes {
@@ -141,49 +136,75 @@ export class InMemoryDatabase {
141
136
  return tablesUsed as string[]
142
137
  }
143
138
 
144
- /**
145
- * NOTE `execute` is untraced since it's usually called from `applyEvent` which is traced
146
- */
147
139
  execute(
148
140
  query: string,
149
141
  bindValues?: ParamsObject,
150
142
  writeTables?: string[],
151
- options?: { hasNoEffects?: boolean },
152
- ): void {
153
- try {
154
- let stmt = this.cachedStmts.get(query)
155
- if (stmt === undefined) {
156
- stmt = this.db.prepare(query)
157
- this.cachedStmts.set(query, stmt)
158
- }
143
+ options?: { hasNoEffects?: boolean; otelContext: otel.Context },
144
+ ): { durationMs: number } {
145
+ return this.otelTracer.startActiveSpan(
146
+ 'livestore.in-memory-db:execute',
147
+ // TODO truncate query string
148
+ { attributes: { 'sql.query': query } },
149
+ options?.otelContext ?? this.otelRootSpanContext,
150
+ (span) => {
151
+ try {
152
+ let stmt = this.cachedStmts.get(query)
153
+ if (stmt === undefined) {
154
+ stmt = this.db.prepare(query)
155
+ this.cachedStmts.set(query, stmt)
156
+ }
159
157
 
160
- if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
161
- stmt.bind(prepareBindValues(bindValues, query))
162
- }
158
+ // TODO check whether we can remove the extra `prepareBindValues` call here (e.g. enforce proper type in API)
159
+ if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
160
+ stmt.bind(prepareBindValues(bindValues, query))
161
+ }
163
162
 
164
- try {
165
- stmt.step()
166
- } finally {
167
- stmt.reset() // Reset is needed for next execution
168
- }
169
- } catch (error) {
170
- shouldNeverHappen(
171
- `Error executing query: ${error} \n ${JSON.stringify({
172
- query,
173
- bindValues,
174
- })}`,
175
- )
176
- }
163
+ if (import.meta.env.DEV) {
164
+ this.debugInfo.events.push([query, bindValues])
165
+ }
177
166
 
178
- if (options?.hasNoEffects !== true && !this.resultCache.ignoreQuery(query)) {
179
- // TODO use write tables instead
180
- // check what queries actually end up here.
181
- this.resultCache.invalidate(writeTables ?? this.getTablesUsed(query))
182
- }
167
+ try {
168
+ stmt.step()
169
+ } finally {
170
+ stmt.reset() // Reset is needed for next execution
171
+ }
172
+ } catch (error) {
173
+ shouldNeverHappen(
174
+ `Error executing query: ${error} \n ${JSON.stringify({
175
+ query,
176
+ bindValues,
177
+ })}`,
178
+ )
179
+ }
183
180
 
184
- if (options?.hasNoEffects === true) {
185
- return
186
- }
181
+ if (options?.hasNoEffects !== true && !this.resultCache.ignoreQuery(query)) {
182
+ // TODO use write tables instead
183
+ // check what queries actually end up here.
184
+ this.resultCache.invalidate(writeTables ?? this.getTablesUsed(query))
185
+ }
186
+
187
+ span.end()
188
+
189
+ const durationMs = getDurationMsFromSpan(span)
190
+
191
+ this.debugInfo.queryFrameDuration += durationMs
192
+ this.debugInfo.queryFrameCount++
193
+
194
+ if (durationMs > 5 && import.meta.env.DEV) {
195
+ this.debugInfo.slowQueries.push([
196
+ query,
197
+ bindValues,
198
+ durationMs,
199
+ undefined,
200
+ [],
201
+ getStartTimeHighResFromSpan(span),
202
+ ])
203
+ }
204
+
205
+ return { durationMs }
206
+ },
207
+ )
187
208
  }
188
209
 
189
210
  select<T = any>(
@@ -218,19 +239,27 @@ export class InMemoryDatabase {
218
239
  stmt = this.db.prepare(query)
219
240
  this.cachedStmts.set(query, stmt)
220
241
  }
221
- if (bindValues) {
222
- stmt.bind(bindValues ?? {})
242
+ if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
243
+ stmt.bind(bindValues)
223
244
  }
224
245
 
225
246
  const result: T[] = []
247
+
226
248
  try {
227
- const columns = stmt.getColumnNames()
249
+ // NOTE `getColumnNames` only works for `SELECT` statements, ignoring other statements for now
250
+ let columns = undefined
251
+ try {
252
+ columns = stmt.getColumnNames()
253
+ } catch (_e) {}
254
+
228
255
  while (stmt.step()) {
229
- const obj: { [key: string]: any } = {}
230
- for (const [i, c] of columns.entries()) {
231
- obj[c] = stmt.get(i)
256
+ if (columns !== undefined) {
257
+ const obj: { [key: string]: any } = {}
258
+ for (const [i, c] of columns.entries()) {
259
+ obj[c] = stmt.get(i)
260
+ }
261
+ result.push(obj as unknown as T)
232
262
  }
233
- result.push(obj as unknown as T)
234
263
  }
235
264
  } finally {
236
265
  // we're caching statements in this iteration. do not free.
@@ -275,67 +304,6 @@ export class InMemoryDatabase {
275
304
  )
276
305
  }
277
306
 
278
- // TODO move `applyEvent` logic to Store and only call `execute` here
279
- applyEvent(
280
- event: LiveStoreEvent,
281
- eventDefinition: ActionDefinition,
282
- otelContext: otel.Context,
283
- ): { durationMs: number } {
284
- return this.otelTracer.startActiveSpan('livestore.in-memory-db:applyEvent', {}, otelContext, (span) => {
285
- // TODO: in the future, we'll do more CRDT-style stuff here to decide whether to run effects of the event
286
-
287
- // NOTE: These two updates should happen transactionally;
288
- // we don't create a transaction here because that's handled in the caller.
289
- // The reason for this is that sometimes we want to apply multiple events in a larger transaction.
290
-
291
- // Insert into the events table
292
- // this.execute(sql`insert into ${EVENTS_TABLE_NAME} (id, type, args) values ($id, $type, $args)`, {
293
- // id: event.id,
294
- // type: event.type,
295
- // args: JSON.stringify(event.args ?? {}),
296
- // })
297
-
298
- const statement =
299
- typeof eventDefinition.statement === 'function'
300
- ? eventDefinition.statement(event.args)
301
- : eventDefinition.statement
302
-
303
- const prepareBindValues = eventDefinition.prepareBindValues ?? identity
304
-
305
- const bindValues =
306
- typeof eventDefinition.statement === 'function' && statement.argsAlreadyBound
307
- ? {}
308
- : prepareBindValues(event.args)
309
-
310
- if (import.meta.env.DEV) {
311
- this.debugInfo.events.push([statement.sql, bindValues])
312
- }
313
-
314
- // Run the effects of the event
315
- this.execute(statement.sql, bindValues, statement.writeTables)
316
-
317
- span.end()
318
-
319
- const durationMs = getDurationMsFromSpan(span)
320
-
321
- this.debugInfo.queryFrameDuration += durationMs
322
- this.debugInfo.queryFrameCount++
323
-
324
- if (durationMs > 5 && import.meta.env.DEV) {
325
- this.debugInfo.slowQueries.push([
326
- statement.sql,
327
- bindValues,
328
- durationMs,
329
- undefined,
330
- [],
331
- getStartTimeHighResFromSpan(span),
332
- ])
333
- }
334
-
335
- return { durationMs }
336
- })
337
- }
338
-
339
307
  export() {
340
308
  // Clear statement cache because exporting frees statements
341
309
  for (const key of this.cachedStmts.keys()) {
@@ -345,3 +313,18 @@ export class InMemoryDatabase {
345
313
  return this.db.capi.sqlite3_js_db_export(this.db.pointer)
346
314
  }
347
315
  }
316
+
317
+ /** Set up SQLite performance; hasn't been super carefully optimized yet. */
318
+ const configureSQLite = (db: InMemoryDatabase) => {
319
+ db.execute(
320
+ // TODO: revisit these tuning parameters for max performance
321
+ sql`
322
+ PRAGMA page_size=32768;
323
+ PRAGMA cache_size=10000;
324
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
325
+ PRAGMA synchronous='OFF';
326
+ PRAGMA temp_store='MEMORY';
327
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
328
+ `,
329
+ )
330
+ }
package/src/index.ts CHANGED
@@ -6,18 +6,14 @@ export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } f
6
6
  export {
7
7
  defineComponentStateSchema,
8
8
  EVENT_CURSOR_TABLE,
9
- defineSchema,
10
9
  defineAction,
11
10
  defineActions,
12
11
  defineTables,
13
12
  defineMaterializedViews,
13
+ makeSchema,
14
14
  } from './schema.js'
15
15
  export { InMemoryDatabase, type DebugInfo, emptyDebugInfo } from './inMemoryDatabase.js'
16
- export { createBackend, IndexType } from './backends/index.js'
17
- export type { BackendOptions, Backend, BackendType } from './backends/index.js'
18
- export { isBackendType } from './backends/index.js'
19
- export type { SelectResponse } from './backends/index.js'
20
- export { WebWorkerBackend } from './backends/web.js'
16
+ export type { Storage, StorageType, StorageInit } from './storage/index.js'
21
17
  export type {
22
18
  GetAtom,
23
19
  AtomDebugInfo,
@@ -32,16 +28,14 @@ export type { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
32
28
 
33
29
  export { labelForKey } from './componentKey.js'
34
30
  export type { ComponentKey } from './componentKey.js'
35
- export type {
36
- Schema,
37
- TableDefinition,
38
- GetActionArgs,
39
- GetApplyEventArgs,
40
- ColumnDefinition,
41
- Index,
42
- ActionDefinition,
43
- ActionDefinitions,
44
- } from './schema.js'
31
+ export type { Schema, GetActionArgs, GetApplyEventArgs, Index, ActionDefinition, ActionDefinitions } from './schema.js'
32
+
33
+ export { SqliteAst, SqliteDsl } from 'effect-db-schema'
34
+
35
+ import type { SqliteAst } from 'effect-db-schema'
36
+ export type TableDefinition = SqliteAst.Table
37
+
38
+ export { SqliteDsl as DbSchema } from 'effect-db-schema'
45
39
 
46
40
  export { sql, type Bindable } from './util.js'
47
41
  export { isEqual } from 'lodash-es'