@livestore/livestore 0.3.2-dev.8 → 0.4.0-dev.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 (57) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SqliteDbWrapper.d.ts +5 -5
  3. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  4. package/dist/SqliteDbWrapper.js.map +1 -1
  5. package/dist/live-queries/base-class.d.ts +5 -0
  6. package/dist/live-queries/base-class.d.ts.map +1 -1
  7. package/dist/live-queries/base-class.js +1 -1
  8. package/dist/live-queries/base-class.js.map +1 -1
  9. package/dist/live-queries/computed.d.ts.map +1 -1
  10. package/dist/live-queries/computed.js +7 -0
  11. package/dist/live-queries/computed.js.map +1 -1
  12. package/dist/live-queries/db-query.d.ts.map +1 -1
  13. package/dist/live-queries/db-query.js +12 -2
  14. package/dist/live-queries/db-query.js.map +1 -1
  15. package/dist/live-queries/signal.d.ts.map +1 -1
  16. package/dist/live-queries/signal.js +7 -0
  17. package/dist/live-queries/signal.js.map +1 -1
  18. package/dist/mod.d.ts +15 -56
  19. package/dist/mod.d.ts.map +1 -1
  20. package/dist/mod.js +1 -0
  21. package/dist/mod.js.map +1 -1
  22. package/dist/reactive.d.ts.map +1 -1
  23. package/dist/reactive.js +2 -0
  24. package/dist/reactive.js.map +1 -1
  25. package/dist/store/create-store.d.ts +5 -7
  26. package/dist/store/create-store.d.ts.map +1 -1
  27. package/dist/store/create-store.js +4 -4
  28. package/dist/store/create-store.js.map +1 -1
  29. package/dist/store/store-shutdown.test.d.ts +2 -0
  30. package/dist/store/store-shutdown.test.d.ts.map +1 -0
  31. package/dist/store/store-shutdown.test.js +103 -0
  32. package/dist/store/store-shutdown.test.js.map +1 -0
  33. package/dist/store/store-types.d.ts +4 -4
  34. package/dist/store/store-types.d.ts.map +1 -1
  35. package/dist/store/store-types.js.map +1 -1
  36. package/dist/store/store.d.ts +11 -2
  37. package/dist/store/store.d.ts.map +1 -1
  38. package/dist/store/store.js +76 -43
  39. package/dist/store/store.js.map +1 -1
  40. package/dist/utils/dev.d.ts +1 -1
  41. package/dist/utils/dev.d.ts.map +1 -1
  42. package/dist/utils/dev.js +14 -8
  43. package/dist/utils/dev.js.map +1 -1
  44. package/dist/utils/tests/fixture.d.ts +12 -0
  45. package/dist/utils/tests/fixture.d.ts.map +1 -1
  46. package/package.json +9 -9
  47. package/src/SqliteDbWrapper.ts +4 -4
  48. package/src/live-queries/base-class.ts +5 -1
  49. package/src/live-queries/computed.ts +7 -0
  50. package/src/live-queries/db-query.ts +12 -3
  51. package/src/live-queries/signal.ts +7 -0
  52. package/src/mod.ts +2 -1
  53. package/src/reactive.ts +2 -0
  54. package/src/store/create-store.ts +18 -16
  55. package/src/store/store-types.ts +9 -5
  56. package/src/store/store.ts +68 -9
  57. package/src/utils/dev.ts +15 -8
package/dist/utils/dev.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isDevEnv } from '@livestore/utils';
1
+ import { prettyBytes } from '@livestore/utils';
2
2
  import { Effect } from '@livestore/utils/effect';
3
3
  export const downloadBlob = (data, fileName, mimeType = 'application/octet-stream') => {
4
4
  const blob = data instanceof Blob ? data : new Blob([data], { type: mimeType });
@@ -16,12 +16,18 @@ export const downloadURL = (data, fileName) => {
16
16
  a.remove();
17
17
  };
18
18
  export const exposeDebugUtils = () => {
19
- if (isDevEnv()) {
20
- globalThis.__debugLiveStoreUtils = {
21
- downloadBlob,
22
- runSync: (effect) => Effect.runSync(effect),
23
- runFork: (effect) => Effect.runFork(effect),
24
- };
25
- }
19
+ globalThis.__debugLiveStoreUtils = {
20
+ downloadBlob,
21
+ runSync: (effect) => Effect.runSync(effect),
22
+ runFork: (effect) => Effect.runFork(effect),
23
+ dumpDb: (db) => {
24
+ const tables = db.select(`SELECT name FROM sqlite_master WHERE type='table'`);
25
+ for (const table of tables) {
26
+ const rows = db.select(`SELECT * FROM ${table.name}`);
27
+ console.log(`Table: ${table.name} (${prettyBytes(table.name.length)}, ${rows.length} rows)`);
28
+ console.table(rows);
29
+ }
30
+ },
31
+ };
26
32
  };
27
33
  //# sourceMappingURL=dev.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/utils/dev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAgC,EAChC,QAAgB,EAChB,QAAQ,GAAG,0BAA0B,EACrC,EAAE;IACF,MAAM,IAAI,GAAG,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IAE/E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAE5C,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAE1B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;AACzD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAE,EAAE;IAC5D,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IACrC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAA;IACb,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACvB,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;IACxB,CAAC,CAAC,KAAK,EAAE,CAAA;IACT,CAAC,CAAC,MAAM,EAAE,CAAA;AACZ,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,IAAI,QAAQ,EAAE,EAAE,CAAC;QACf,UAAU,CAAC,qBAAqB,GAAG;YACjC,YAAY;YACZ,OAAO,EAAE,CAAC,MAAsC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3E,OAAO,EAAE,CAAC,MAAsC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SAC5E,CAAA;IACH,CAAC;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/utils/dev.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAA6C,EAC7C,QAAgB,EAChB,QAAQ,GAAG,0BAA0B,EACrC,EAAE;IACF,MAAM,IAAI,GAAG,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IAE/E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAE5C,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAE1B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;AACzD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAE,EAAE;IAC5D,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IACrC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAA;IACb,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACvB,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;IACxB,CAAC,CAAC,KAAK,EAAE,CAAA;IACT,CAAC,CAAC,MAAM,EAAE,CAAA;AACZ,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,UAAU,CAAC,qBAAqB,GAAG;QACjC,YAAY;QACZ,OAAO,EAAE,CAAC,MAAsC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAC3E,OAAO,EAAE,CAAC,MAAsC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAC3E,MAAM,EAAE,CAAC,EAAY,EAAE,EAAE;YACvB,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAmB,mDAAmD,CAAC,CAAA;YAC/F,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,CAAM,iBAAiB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;gBAC1D,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAA;gBAC5F,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
@@ -18,6 +18,7 @@ export declare const todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefFor
18
18
  default: import("effect/Option").None<never>;
19
19
  nullable: false;
20
20
  primaryKey: true;
21
+ autoIncrement: false;
21
22
  };
22
23
  readonly text: {
23
24
  columnType: "text";
@@ -25,6 +26,7 @@ export declare const todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefFor
25
26
  default: import("effect/Option").Some<"">;
26
27
  nullable: false;
27
28
  primaryKey: false;
29
+ autoIncrement: false;
28
30
  };
29
31
  readonly completed: {
30
32
  columnType: "integer";
@@ -32,6 +34,7 @@ export declare const todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefFor
32
34
  default: import("effect/Option").Some<false>;
33
35
  nullable: false;
34
36
  primaryKey: false;
37
+ autoIncrement: false;
35
38
  };
36
39
  }>, State.SQLite.WithDefaults<{
37
40
  readonly id: {
@@ -40,6 +43,7 @@ export declare const todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefFor
40
43
  default: import("effect/Option").None<never>;
41
44
  nullable: false;
42
45
  primaryKey: true;
46
+ autoIncrement: false;
43
47
  };
44
48
  readonly text: {
45
49
  columnType: "text";
@@ -47,6 +51,7 @@ export declare const todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefFor
47
51
  default: import("effect/Option").Some<"">;
48
52
  nullable: false;
49
53
  primaryKey: false;
54
+ autoIncrement: false;
50
55
  };
51
56
  readonly completed: {
52
57
  columnType: "integer";
@@ -54,6 +59,7 @@ export declare const todos: State.SQLite.TableDef<State.SQLite.SqliteTableDefFor
54
59
  default: import("effect/Option").Some<false>;
55
60
  nullable: false;
56
61
  primaryKey: false;
62
+ autoIncrement: false;
57
63
  };
58
64
  }>, Schema.Schema<{
59
65
  readonly id: string;
@@ -88,6 +94,7 @@ export declare const tables: {
88
94
  default: import("effect/Option").None<never>;
89
95
  nullable: false;
90
96
  primaryKey: true;
97
+ autoIncrement: false;
91
98
  };
92
99
  readonly text: {
93
100
  columnType: "text";
@@ -95,6 +102,7 @@ export declare const tables: {
95
102
  default: import("effect/Option").Some<"">;
96
103
  nullable: false;
97
104
  primaryKey: false;
105
+ autoIncrement: false;
98
106
  };
99
107
  readonly completed: {
100
108
  columnType: "integer";
@@ -102,6 +110,7 @@ export declare const tables: {
102
110
  default: import("effect/Option").Some<false>;
103
111
  nullable: false;
104
112
  primaryKey: false;
113
+ autoIncrement: false;
105
114
  };
106
115
  }>, State.SQLite.WithDefaults<{
107
116
  readonly id: {
@@ -110,6 +119,7 @@ export declare const tables: {
110
119
  default: import("effect/Option").None<never>;
111
120
  nullable: false;
112
121
  primaryKey: true;
122
+ autoIncrement: false;
113
123
  };
114
124
  readonly text: {
115
125
  columnType: "text";
@@ -117,6 +127,7 @@ export declare const tables: {
117
127
  default: import("effect/Option").Some<"">;
118
128
  nullable: false;
119
129
  primaryKey: false;
130
+ autoIncrement: false;
120
131
  };
121
132
  readonly completed: {
122
133
  columnType: "integer";
@@ -124,6 +135,7 @@ export declare const tables: {
124
135
  default: import("effect/Option").Some<false>;
125
136
  nullable: false;
126
137
  primaryKey: false;
138
+ autoIncrement: false;
127
139
  };
128
140
  }>, Schema.Schema<{
129
141
  readonly id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../../src/utils/tests/fixture.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmC,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAE/C,MAAM,MAAM,IAAI,GAAG;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAA;AAEnD,MAAM,MAAM,QAAQ,GAAG;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOhB,CAAA;AAEF,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;EAOd,CAAA;AAEF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAiB,CAAA;AAEpC,eAAO,MAAM,MAAM;;;;;;;;;;CASlB,CAAA;AAMD,eAAO,MAAM,KAAK,8CAAoD,CAAA;AACtE,eAAO,MAAM,MAAM;;;;;;;;;;;;;EAAgC,CAAA;AAEnD,eAAO,MAAM,WAAW,GAAI,+BAGzB;IACD,UAAU,CAAC,EAAE,IAAI,CAAC,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,IAAI,CAAC,OAAO,CAAA;CACtB;;;;;;;;;;;;;mFAU4E,CAAA"}
1
+ {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../../src/utils/tests/fixture.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmC,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAE/C,MAAM,MAAM,IAAI,GAAG;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAA;AAEnD,MAAM,MAAM,QAAQ,GAAG;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOhB,CAAA;AAEF,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;EAOd,CAAA;AAEF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAiB,CAAA;AAEpC,eAAO,MAAM,MAAM;;;;;;;;;;CASlB,CAAA;AAMD,eAAO,MAAM,KAAK,8CAAoD,CAAA;AACtE,eAAO,MAAM,MAAM;;;;;;;;;;;;;EAAgC,CAAA;AAEnD,eAAO,MAAM,WAAW,GAAI,+BAGzB;IACD,UAAU,CAAC,EAAE,IAAI,CAAC,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,IAAI,CAAC,OAAO,CAAA;CACtB;;;;;;;;;;;;;mFAU4E,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/livestore",
3
- "version": "0.3.2-dev.8",
3
+ "version": "0.4.0-dev.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -11,17 +11,17 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@opentelemetry/api": "1.9.0",
14
- "@livestore/common": "0.3.2-dev.8",
15
- "@livestore/utils": "0.3.2-dev.8"
14
+ "@livestore/utils": "0.4.0-dev.0",
15
+ "@livestore/common": "0.4.0-dev.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@opentelemetry/sdk-trace-base": "^2.0.0",
19
- "jsdom": "^26.0.0",
20
- "typescript": "^5.8.3",
21
- "vite": "^7.0.0",
18
+ "@opentelemetry/sdk-trace-base": "^2.0.1",
19
+ "jsdom": "^26.1.0",
20
+ "typescript": "^5.9.2",
21
+ "vite": "^7.0.6",
22
22
  "vitest": "3.2.4",
23
- "@livestore/utils-dev": "0.3.2-dev.8",
24
- "@livestore/adapter-web": "0.3.2-dev.8"
23
+ "@livestore/adapter-web": "0.4.0-dev.0",
24
+ "@livestore/utils-dev": "0.4.0-dev.0"
25
25
  },
26
26
  "files": [
27
27
  "package.json",
@@ -73,7 +73,7 @@ export class SqliteDbWrapper implements SqliteDb {
73
73
  prepare(queryStr: string): PreparedStatement {
74
74
  return this.db.prepare(queryStr)
75
75
  }
76
- import(data: Uint8Array<ArrayBufferLike> | SqliteDb<any, any>) {
76
+ import(data: Uint8Array<ArrayBuffer> | SqliteDb<any, any>) {
77
77
  return this.db.import(data)
78
78
  }
79
79
  close(): void {
@@ -85,7 +85,7 @@ export class SqliteDbWrapper implements SqliteDb {
85
85
  session(): SqliteDbSession {
86
86
  return this.db.session()
87
87
  }
88
- makeChangeset(data: Uint8Array): SqliteDbChangeset {
88
+ makeChangeset(data: Uint8Array<ArrayBuffer>): SqliteDbChangeset {
89
89
  return this.db.makeChangeset(data)
90
90
  }
91
91
 
@@ -112,7 +112,7 @@ export class SqliteDbWrapper implements SqliteDb {
112
112
 
113
113
  withChangeset<TRes>(callback: () => TRes): {
114
114
  result: TRes
115
- changeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' }
115
+ changeset: { _tag: 'sessionChangeset'; data: Uint8Array<ArrayBuffer>; debug: any } | { _tag: 'no-op' }
116
116
  } {
117
117
  const session = this.db.session()
118
118
  const result = callback()
@@ -126,7 +126,7 @@ export class SqliteDbWrapper implements SqliteDb {
126
126
  }
127
127
  }
128
128
 
129
- rollback(changeset: Uint8Array) {
129
+ rollback(changeset: Uint8Array<ArrayBuffer>) {
130
130
  const invertedChangeset = this.db.makeChangeset(changeset).invert()
131
131
  invertedChangeset.apply()
132
132
  }
@@ -1,5 +1,5 @@
1
1
  import { isNotNil } from '@livestore/utils'
2
- import { Predicate } from '@livestore/utils/effect'
2
+ import { Equal, Hash, Predicate } from '@livestore/utils/effect'
3
3
  import type * as otel from '@opentelemetry/api'
4
4
 
5
5
  import * as RG from '../reactive.ts'
@@ -41,6 +41,8 @@ export interface SignalDef<T> extends LiveQueryDef<T, 'signal-def'> {
41
41
  hash: string
42
42
  label: string
43
43
  make: (ctx: ReactivityGraphContext) => RcRef<ISignal<T>>
44
+ [Equal.symbol](that: SignalDef<T>): boolean
45
+ [Hash.symbol](): number
44
46
  }
45
47
 
46
48
  export interface ISignal<T> extends LiveQuery<T> {
@@ -77,6 +79,8 @@ export interface LiveQueryDef<TResult, TTag extends string = 'def'> {
77
79
  make: (ctx: ReactivityGraphContext, otelContext?: otel.Context) => RcRef<LiveQuery<TResult> | ISignal<TResult>>
78
80
  label: string
79
81
  hash: string
82
+ [Equal.symbol](that: LiveQueryDef<TResult, TTag>): boolean
83
+ [Hash.symbol](): number
80
84
  }
81
85
 
82
86
  export namespace LiveQueryDef {
@@ -1,4 +1,5 @@
1
1
  import { getDurationMsFromSpan } from '@livestore/common'
2
+ import { Equal, Hash } from '@livestore/utils/effect'
2
3
  import * as otel from '@opentelemetry/api'
3
4
 
4
5
  import type { Thunk } from '../reactive.ts'
@@ -35,6 +36,12 @@ export const computed = <TResult>(
35
36
  // TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
36
37
  // NOTE `fn.toString()` doesn't work in Expo as it always produces `[native code]`
37
38
  hash,
39
+ [Equal.symbol](that: LiveQueryDef<any>): boolean {
40
+ return this.hash === that.hash
41
+ },
42
+ [Hash.symbol](): number {
43
+ return Hash.string(this.hash)
44
+ },
38
45
  }
39
46
 
40
47
  return def
@@ -10,7 +10,7 @@ import {
10
10
  UnexpectedError,
11
11
  } from '@livestore/common'
12
12
  import { deepEqual, shouldNeverHappen } from '@livestore/utils'
13
- import { Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
13
+ import { Equal, Hash, Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
14
14
  import * as otel from '@opentelemetry/api'
15
15
 
16
16
  import type { Thunk } from '../reactive.ts'
@@ -131,6 +131,12 @@ export const queryDb: {
131
131
  }),
132
132
  label,
133
133
  hash,
134
+ [Equal.symbol](that: LiveQueryDef<any>): boolean {
135
+ return this.hash === that.hash
136
+ },
137
+ [Hash.symbol](): number {
138
+ return Hash.string(this.hash)
139
+ },
134
140
  }
135
141
 
136
142
  return def
@@ -314,8 +320,11 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
314
320
 
315
321
  const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
316
322
 
317
- const makeResultsEqual = (resultSchema: Schema.Schema<any, any>) => (a: TResult, b: TResult) =>
318
- a === NOT_REFRESHED_YET || b === NOT_REFRESHED_YET ? false : Schema.equivalence(resultSchema)(a, b)
323
+ const makeResultsEqual = (resultSchema: Schema.Schema<any, any>) => {
324
+ // Creating the equivalence function eagerly in outer scope as it might be expensive
325
+ const eq = Schema.equivalence(resultSchema)
326
+ return (a: TResult, b: TResult) => (a === NOT_REFRESHED_YET || b === NOT_REFRESHED_YET ? false : eq(a, b))
327
+ }
319
328
 
320
329
  // NOTE we try to create the equality function eagerly as it might be expensive
321
330
  // TODO also support derived equality for `map` (probably will depend on having an easy way to transform a schema without an `encode` step)
@@ -1,3 +1,4 @@
1
+ import { Equal, Hash } from '@livestore/utils/effect'
1
2
  import { nanoid } from '@livestore/utils/nanoid'
2
3
 
3
4
  import type * as RG from '../reactive.ts'
@@ -27,6 +28,12 @@ export const signal = <T>(
27
28
  def,
28
29
  }),
29
30
  ),
31
+ [Equal.symbol](that: SignalDef<T>): boolean {
32
+ return this.hash === that.hash
33
+ },
34
+ [Hash.symbol](): number {
35
+ return Hash.string(this.hash)
36
+ },
30
37
  }
31
38
 
32
39
  return def
package/src/mod.ts CHANGED
@@ -35,11 +35,12 @@ export {
35
35
  export { emptyDebugInfo, SqliteDbWrapper } from './SqliteDbWrapper.ts'
36
36
  export { type CreateStoreOptions, createStore, createStorePromise } from './store/create-store.ts'
37
37
  export { Store } from './store/store.ts'
38
- export type { OtelOptions, QueryDebugInfo, RefreshReason } from './store/store-types.ts'
38
+ export type { OtelOptions, QueryDebugInfo, RefreshReason, Unsubscribe } from './store/store-types.ts'
39
39
  export {
40
40
  type LiveStoreContext,
41
41
  type LiveStoreContextRunning,
42
42
  makeShutdownDeferred,
43
43
  type ShutdownDeferred,
44
44
  } from './store/store-types.ts'
45
+ export { exposeDebugUtils } from './utils/dev.ts'
45
46
  export * from './utils/stack-info.ts'
package/src/reactive.ts CHANGED
@@ -211,8 +211,10 @@ export class ReactiveGraph<
211
211
 
212
212
  private refreshCallbacks: Set<() => void> = new Set()
213
213
 
214
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: for debugging
214
215
  private nodeIdCounter = 0
215
216
  private uniqueNodeId = () => `node-${++this.nodeIdCounter}`
217
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: for debugging
216
218
  private refreshInfoIdCounter = 0
217
219
  private uniqueRefreshInfoId = () => `refresh-info-${++this.refreshInfoIdCounter}`
218
220
 
@@ -1,16 +1,17 @@
1
- import type {
2
- Adapter,
3
- BootStatus,
4
- ClientSession,
5
- ClientSessionDevtoolsChannel,
6
- ClientSessionSyncProcessorSimulationParams,
7
- IntentionalShutdownCause,
8
- MigrationsReport,
1
+ import {
2
+ type Adapter,
3
+ type BootStatus,
4
+ type ClientSession,
5
+ type ClientSessionDevtoolsChannel,
6
+ type ClientSessionSyncProcessorSimulationParams,
7
+ type IntentionalShutdownCause,
8
+ type MigrationsReport,
9
+ provideOtel,
10
+ type SyncError,
11
+ UnexpectedError,
9
12
  } from '@livestore/common'
10
- import { provideOtel, UnexpectedError } from '@livestore/common'
11
13
  import type { LiveStoreSchema } from '@livestore/common/schema'
12
14
  import { isDevEnv, LS_DEV } from '@livestore/utils'
13
- import type { Cause, Schema } from '@livestore/utils/effect'
14
15
  import {
15
16
  Context,
16
17
  Deferred,
@@ -24,6 +25,7 @@ import {
24
25
  OtelTracer,
25
26
  Queue,
26
27
  Runtime,
28
+ type Schema,
27
29
  Scope,
28
30
  TaskTracing,
29
31
  } from '@livestore/utils/effect'
@@ -96,7 +98,7 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
96
98
  migrationsReport: MigrationsReport
97
99
  parentSpan: otel.Span
98
100
  },
99
- ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
101
+ ) => Effect.SyncOrPromiseOrEffect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
100
102
  batchUpdates?: (run: () => void) => void
101
103
  /**
102
104
  * Whether to disable devtools.
@@ -131,7 +133,7 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
131
133
  }
132
134
 
133
135
  /** Create a new LiveStore Store */
134
- export const createStorePromise = async <TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}>({
136
+ export const createStorePromise = async <TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>({
135
137
  signal,
136
138
  otelOptions,
137
139
  ...options
@@ -162,7 +164,7 @@ export const createStorePromise = async <TSchema extends LiveStoreSchema = LiveS
162
164
  Effect.runPromise,
163
165
  )
164
166
 
165
- export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}>({
167
+ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}>({
166
168
  schema,
167
169
  adapter,
168
170
  storeId,
@@ -215,9 +217,9 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
215
217
 
216
218
  const runtime = yield* Effect.runtime<Scope.Scope>()
217
219
 
218
- const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
220
+ const shutdown = (exit: Exit.Exit<IntentionalShutdownCause, UnexpectedError | SyncError>) =>
219
221
  Effect.gen(function* () {
220
- yield* Scope.close(lifetimeScope, Exit.failCause(cause)).pipe(
222
+ yield* Scope.close(lifetimeScope, exit).pipe(
221
223
  Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
222
224
  Effect.timeout(1000),
223
225
  Effect.catchTag('TimeoutException', () =>
@@ -226,7 +228,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
226
228
  )
227
229
 
228
230
  if (shutdownDeferred) {
229
- yield* Deferred.failCause(shutdownDeferred, cause)
231
+ yield* Deferred.done(shutdownDeferred, exit)
230
232
  }
231
233
 
232
234
  yield* Effect.logDebug('LiveStore shutdown complete')
@@ -3,6 +3,7 @@ import type {
3
3
  ClientSessionSyncProcessorSimulationParams,
4
4
  IntentionalShutdownCause,
5
5
  StoreInterrupted,
6
+ SyncError,
6
7
  UnexpectedError,
7
8
  } from '@livestore/common'
8
9
  import type { EventSequenceNumber, LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
@@ -22,13 +23,16 @@ export type LiveStoreContext =
22
23
  }
23
24
  | {
24
25
  stage: 'shutdown'
25
- cause: IntentionalShutdownCause | StoreInterrupted
26
+ cause: IntentionalShutdownCause | StoreInterrupted | SyncError
26
27
  }
27
28
 
28
- export type ShutdownDeferred = Deferred.Deferred<void, UnexpectedError | IntentionalShutdownCause | StoreInterrupted>
29
+ export type ShutdownDeferred = Deferred.Deferred<
30
+ IntentionalShutdownCause,
31
+ UnexpectedError | SyncError | StoreInterrupted
32
+ >
29
33
  export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
30
- void,
31
- UnexpectedError | IntentionalShutdownCause | StoreInterrupted
34
+ IntentionalShutdownCause,
35
+ UnexpectedError | SyncError | StoreInterrupted
32
36
  >()
33
37
 
34
38
  export type LiveStoreContextRunning = {
@@ -41,7 +45,7 @@ export type OtelOptions = {
41
45
  rootSpanContext: otel.Context
42
46
  }
43
47
 
44
- export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}> = {
48
+ export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> = {
45
49
  clientSession: ClientSession
46
50
  schema: TSchema
47
51
  storeId: string
@@ -22,7 +22,18 @@ import type { LiveStoreSchema } from '@livestore/common/schema'
22
22
  import { getEventDef, LiveStoreEvent, SystemTables } from '@livestore/common/schema'
23
23
  import { assertNever, isDevEnv, notYetImplemented } from '@livestore/utils'
24
24
  import type { Scope } from '@livestore/utils/effect'
25
- import { Cause, Effect, Fiber, Inspectable, Option, OtelTracer, Runtime, Schema, Stream } from '@livestore/utils/effect'
25
+ import {
26
+ Cause,
27
+ Effect,
28
+ Exit,
29
+ Fiber,
30
+ Inspectable,
31
+ Option,
32
+ OtelTracer,
33
+ Runtime,
34
+ Schema,
35
+ Stream,
36
+ } from '@livestore/utils/effect'
26
37
  import { nanoid } from '@livestore/utils/nanoid'
27
38
  import * as otel from '@opentelemetry/api'
28
39
 
@@ -54,7 +65,7 @@ if (isDevEnv()) {
54
65
  exposeDebugUtils()
55
66
  }
56
67
 
57
- export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}> extends Inspectable.Class {
68
+ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> extends Inspectable.Class {
58
69
  readonly storeId: string
59
70
  reactivityGraph: ReactivityGraph
60
71
  sqliteDbWrapper: SqliteDbWrapper
@@ -68,6 +79,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
68
79
  */
69
80
  tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> }
70
81
 
82
+ /** Tracks whether the store has been shut down */
83
+ private isShutdown = false
84
+
71
85
  private effectContext: {
72
86
  runtime: Runtime.Runtime<Scope.Scope>
73
87
  lifetimeScope: Scope.Scope
@@ -168,7 +182,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
168
182
  }
169
183
 
170
184
  let sessionChangeset:
171
- | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any }
185
+ | { _tag: 'sessionChangeset'; data: Uint8Array<ArrayBuffer>; debug: any }
172
186
  | { _tag: 'no-op' }
173
187
  | { _tag: 'unset' } = { _tag: 'unset' }
174
188
 
@@ -282,6 +296,15 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
282
296
  return this.clientSession.clientId
283
297
  }
284
298
 
299
+ private checkShutdown = (operation: string): void => {
300
+ if (this.isShutdown) {
301
+ throw new UnexpectedError({
302
+ cause: `Store has been shut down (while performing "${operation}").`,
303
+ note: `You cannot perform this operation after the store has been shut down.`,
304
+ })
305
+ }
306
+ }
307
+
285
308
  /**
286
309
  * Subscribe to the results of a query
287
310
  * Returns a function to cancel the subscription.
@@ -309,8 +332,10 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
309
332
  /** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
310
333
  stackInfo?: StackInfo
311
334
  },
312
- ): Unsubscribe =>
313
- this.otel.tracer.startActiveSpan(
335
+ ): Unsubscribe => {
336
+ this.checkShutdown('subscribe')
337
+
338
+ return this.otel.tracer.startActiveSpan(
314
339
  `LiveStore.subscribe`,
315
340
  { attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
316
341
  options?.otelContext ?? this.otel.queriesSpanContext,
@@ -369,6 +394,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
369
394
  return unsubscribe
370
395
  },
371
396
  )
397
+ }
372
398
 
373
399
  subscribeStream = <TResult>(
374
400
  query$: LiveQueryDef<TResult>,
@@ -417,6 +443,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
417
443
  | { query: string; bindValues: Bindable; schema?: Schema.Schema<TResult> },
418
444
  options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
419
445
  ): TResult => {
446
+ this.checkShutdown('query')
447
+
420
448
  if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
421
449
  const res = this.sqliteDbWrapper.cachedSelect(query.query, prepareBindValues(query.bindValues, query.query), {
422
450
  otelContext: options?.otelContext,
@@ -438,6 +466,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
438
466
 
439
467
  const sqlRes = query.asSql()
440
468
  const schema = getResultSchema(query)
469
+
470
+ // Replace SessionIdSymbol in bind values before executing the query
471
+ if (sqlRes.bindValues) {
472
+ replaceSessionIdSymbol(sqlRes.bindValues, this.clientSession.sessionId)
473
+ }
474
+
441
475
  const rawRes = this.sqliteDbWrapper.cachedSelect(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
442
476
  otelContext: options?.otelContext,
443
477
  queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
@@ -473,6 +507,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
473
507
  * ```
474
508
  */
475
509
  setSignal = <T>(signalDef: SignalDef<T>, value: T | ((prev: T) => T)): void => {
510
+ this.checkShutdown('setSignal')
511
+
476
512
  const signalRef = signalDef.make(this.reactivityGraph.context!)
477
513
  const newValue: T = typeof value === 'function' ? (value as any)(signalRef.value.get()) : value
478
514
  signalRef.value.set(newValue)
@@ -557,6 +593,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
557
593
  ) => void,
558
594
  ): void
559
595
  } = (firstEventOrTxnFnOrOptions: any, ...restEvents: any[]) => {
596
+ this.checkShutdown('commit')
597
+
560
598
  const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
561
599
 
562
600
  for (const event of events) {
@@ -660,10 +698,14 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
660
698
  * ```
661
699
  */
662
700
  events = (_options?: StoreEventsOptions<TSchema>): AsyncIterable<LiveStoreEvent.ForSchema<TSchema>> => {
701
+ this.checkShutdown('events')
702
+
663
703
  return notYetImplemented(`store.events() is not yet implemented but planned soon`)
664
704
  }
665
705
 
666
706
  eventsStream = (_options?: StoreEventsOptions<TSchema>): Stream.Stream<LiveStoreEvent.ForSchema<TSchema>> => {
707
+ this.checkShutdown('eventsStream')
708
+
667
709
  return notYetImplemented(`store.eventsStream() is not yet implemented but planned soon`)
668
710
  }
669
711
 
@@ -672,6 +714,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
672
714
  * We might need a better solution for this. Let's see.
673
715
  */
674
716
  manualRefresh = (options?: { label?: string }) => {
717
+ this.checkShutdown('manualRefresh')
718
+
675
719
  const { label } = options ?? {}
676
720
  this.otel.tracer.startActiveSpan(
677
721
  'LiveStore:manualRefresh',
@@ -690,10 +734,25 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
690
734
  *
691
735
  * This is called automatically when the store was created using the React or Effect API.
692
736
  */
693
- shutdown = async (cause?: Cause.Cause<UnexpectedError>) => {
694
- await this.clientSession
695
- .shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
696
- .pipe(this.runEffectFork, Fiber.join, Effect.runPromise)
737
+ shutdownPromise = async (cause?: UnexpectedError) => {
738
+ this.checkShutdown('shutdownPromise')
739
+
740
+ this.isShutdown = true
741
+ await this.shutdown(cause ? Cause.fail(cause) : undefined).pipe(this.runEffectFork, Fiber.join, Effect.runPromise)
742
+ }
743
+
744
+ /**
745
+ * Shuts down the store and closes the client session.
746
+ *
747
+ * This is called automatically when the store was created using the React or Effect API.
748
+ */
749
+ shutdown = (cause?: Cause.Cause<UnexpectedError>): Effect.Effect<void> => {
750
+ this.checkShutdown('shutdown')
751
+
752
+ this.isShutdown = true
753
+ return this.clientSession.shutdown(
754
+ cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
755
+ )
697
756
  }
698
757
 
699
758
  /**