@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.
- package/README.md +7 -7
- package/dist/.tsbuildinfo +1 -0
- package/dist/QueryCache.d.ts +20 -0
- package/dist/QueryCache.d.ts.map +1 -0
- package/dist/QueryCache.js +71 -0
- package/dist/QueryCache.js.map +1 -0
- package/dist/__tests__/react/fixture.d.ts +25 -0
- package/dist/__tests__/react/fixture.d.ts.map +1 -0
- package/dist/__tests__/react/fixture.js +61 -0
- package/dist/__tests__/react/fixture.js.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +2 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +78 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
- package/dist/__tests__/reactive.test.d.ts +2 -0
- package/dist/__tests__/reactive.test.d.ts.map +1 -0
- package/dist/__tests__/reactive.test.js +198 -0
- package/dist/__tests__/reactive.test.js.map +1 -0
- package/dist/backends/base.d.ts +13 -0
- package/dist/backends/base.d.ts.map +1 -0
- package/dist/backends/base.js +53 -0
- package/dist/backends/base.js.map +1 -0
- package/dist/backends/in-memory/index.d.ts +22 -0
- package/dist/backends/in-memory/index.d.ts.map +1 -0
- package/dist/backends/in-memory/index.js +45 -0
- package/dist/backends/in-memory/index.js.map +1 -0
- package/dist/backends/index.d.ts +41 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +16 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/backends/tauri/index.d.ts +21 -0
- package/dist/backends/tauri/index.d.ts.map +1 -0
- package/dist/backends/tauri/index.js +48 -0
- package/dist/backends/tauri/index.js.map +1 -0
- package/dist/backends/utils/idb.d.ts +10 -0
- package/dist/backends/utils/idb.d.ts.map +1 -0
- package/dist/backends/utils/idb.js +58 -0
- package/dist/backends/utils/idb.js.map +1 -0
- package/dist/backends/web-worker/index.d.ts +26 -0
- package/dist/backends/web-worker/index.d.ts.map +1 -0
- package/dist/backends/web-worker/index.js +63 -0
- package/dist/backends/web-worker/index.js.map +1 -0
- package/dist/backends/web-worker/worker.d.ts +17 -0
- package/dist/backends/web-worker/worker.d.ts.map +1 -0
- package/dist/backends/web-worker/worker.js +139 -0
- package/dist/backends/web-worker/worker.js.map +1 -0
- package/dist/bounded-collections.d.ts +34 -0
- package/dist/bounded-collections.d.ts.map +1 -0
- package/dist/bounded-collections.js +103 -0
- package/dist/bounded-collections.js.map +1 -0
- package/dist/componentKey.d.ts +20 -0
- package/dist/componentKey.d.ts.map +1 -0
- package/dist/componentKey.js +3 -0
- package/dist/componentKey.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +42 -0
- package/dist/effect/LiveStore.d.ts.map +1 -0
- package/dist/effect/LiveStore.js +37 -0
- package/dist/effect/LiveStore.js.map +1 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +2 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +60 -0
- package/dist/inMemoryDatabase.d.ts.map +1 -0
- package/dist/inMemoryDatabase.js +230 -0
- package/dist/inMemoryDatabase.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +62 -0
- package/dist/migrations.js.map +1 -0
- package/dist/otel.d.ts +4 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +6 -0
- package/dist/otel.js.map +1 -0
- package/dist/react/LiveStoreContext.d.ts +11 -0
- package/dist/react/LiveStoreContext.d.ts.map +1 -0
- package/dist/react/LiveStoreContext.js +10 -0
- package/dist/react/LiveStoreContext.js.map +1 -0
- package/dist/react/LiveStoreProvider.d.ts +22 -0
- package/dist/react/LiveStoreProvider.d.ts.map +1 -0
- package/dist/react/LiveStoreProvider.js +49 -0
- package/dist/react/LiveStoreProvider.js.map +1 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useGlobalQuery.d.ts +3 -0
- package/dist/react/useGlobalQuery.d.ts.map +1 -0
- package/dist/react/useGlobalQuery.js +23 -0
- package/dist/react/useGlobalQuery.js.map +1 -0
- package/dist/react/useGraphQL.d.ts +11 -0
- package/dist/react/useGraphQL.d.ts.map +1 -0
- package/dist/react/useGraphQL.js +67 -0
- package/dist/react/useGraphQL.js.map +1 -0
- package/dist/react/useLiveStoreComponent.d.ts +75 -0
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
- package/dist/react/useLiveStoreComponent.js +301 -0
- package/dist/react/useLiveStoreComponent.js.map +1 -0
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
- package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
- package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
- package/dist/reactive.d.ts +140 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +302 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +24 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +22 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +25 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +18 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +19 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +13 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +31 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +32 -0
- package/dist/reactiveQueries/sql.js.map +1 -0
- package/dist/schema.d.ts +83 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +49 -0
- package/dist/schema.js.map +1 -0
- package/dist/storage/base.d.ts +10 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/base.js +14 -0
- package/dist/storage/base.js.map +1 -0
- package/dist/storage/in-memory/index.d.ts +15 -0
- package/dist/storage/in-memory/index.d.ts.map +1 -0
- package/dist/storage/in-memory/index.js +14 -0
- package/dist/storage/in-memory/index.js.map +1 -0
- package/dist/storage/index.d.ts +14 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +9 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/tauri/index.d.ts +19 -0
- package/dist/storage/tauri/index.d.ts.map +1 -0
- package/dist/storage/tauri/index.js +38 -0
- package/dist/storage/tauri/index.js.map +1 -0
- package/dist/storage/utils/idb.d.ts +10 -0
- package/dist/storage/utils/idb.d.ts.map +1 -0
- package/dist/storage/utils/idb.js +58 -0
- package/dist/storage/utils/idb.js.map +1 -0
- package/dist/storage/web-worker/index.d.ts +27 -0
- package/dist/storage/web-worker/index.d.ts.map +1 -0
- package/dist/storage/web-worker/index.js +76 -0
- package/dist/storage/web-worker/index.js.map +1 -0
- package/dist/storage/web-worker/worker.d.ts +13 -0
- package/dist/storage/web-worker/worker.d.ts.map +1 -0
- package/dist/storage/web-worker/worker.js +110 -0
- package/dist/storage/web-worker/worker.js.map +1 -0
- package/dist/store.d.ts +192 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +569 -0
- package/dist/store.js.map +1 -0
- package/dist/util.d.ts +26 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +53 -0
- package/dist/util.js.map +1 -0
- package/package.json +46 -19
- package/src/__tests__/react/fixture.tsx +19 -28
- package/src/effect/LiveStore.ts +8 -13
- package/src/events.ts +1 -1
- package/src/inMemoryDatabase.ts +100 -117
- package/src/index.ts +10 -16
- package/src/migrations.ts +101 -0
- package/src/otel.ts +0 -11
- package/src/react/LiveStoreProvider.tsx +12 -8
- package/src/react/index.ts +9 -0
- package/src/react/useGlobalQuery.ts +0 -3
- package/src/react/useLiveStoreComponent.ts +95 -37
- package/src/schema.ts +72 -145
- package/src/storage/in-memory/index.ts +21 -0
- package/src/storage/index.ts +27 -0
- package/src/{backends/tauri.ts → storage/tauri/index.ts} +13 -27
- package/src/storage/web-worker/index.ts +118 -0
- package/src/{backends/web-worker.ts → storage/web-worker/worker.ts} +17 -52
- package/src/store.ts +112 -79
- package/src/util.ts +5 -1
- package/tsconfig.json +1 -3
- package/src/backends/base.ts +0 -67
- package/src/backends/index.ts +0 -98
- package/src/backends/noop.ts +0 -32
- package/src/backends/web-in-memory.ts +0 -65
- package/src/backends/web.ts +0 -97
- /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
|
package/dist/util.js.map
ADDED
|
@@ -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.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"publishConfig": {
|
|
6
|
-
"access": "public"
|
|
7
|
-
},
|
|
8
5
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
"
|
|
53
|
-
"
|
|
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.
|
|
29
|
+
export const schema = LiveStore.makeSchema({
|
|
29
30
|
tables: {
|
|
30
|
-
todos: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
boot: async (
|
|
79
|
-
|
|
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
|
-
//
|
|
82
|
-
//
|
|
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
|
|
package/src/effect/LiveStore.ts
CHANGED
|
@@ -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
|
-
|
|
25
|
+
loadStorage: () => StorageInit | Promise<StorageInit>
|
|
26
26
|
graphQLOptions?: GraphQLOptions<GraphQLContext>
|
|
27
27
|
otelTracer?: otel.Tracer
|
|
28
28
|
otelRootSpanContext?: otel.Context
|
|
29
|
-
boot?: (
|
|
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
|
-
|
|
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?: (
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
package/src/inMemoryDatabase.ts
CHANGED
|
@@ -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
|
|
65
|
+
static load(
|
|
70
66
|
data: Uint8Array | undefined,
|
|
71
67
|
otelTracer: otel.Tracer,
|
|
72
68
|
otelRootSpanContext: otel.Context,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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 {
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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'
|