@livestore/livestore 0.0.13 → 0.0.16
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/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +8 -9
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/reactive.test.js +3 -4
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +3 -9
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +11 -7
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/inMemoryDatabase.d.ts +15 -19
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/inMemoryDatabase.js +2 -9
- package/dist/inMemoryDatabase.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +7 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +18 -13
- package/dist/migrations.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +1 -3
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +13 -10
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/useGraphQL.d.ts +3 -1
- package/dist/react/useGraphQL.d.ts.map +1 -1
- package/dist/react/useGraphQL.js +19 -1
- package/dist/react/useGraphQL.js.map +1 -1
- package/dist/react/useLiveStoreComponent.d.ts +12 -12
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
- package/dist/react/useLiveStoreComponent.js +23 -7
- package/dist/react/useLiveStoreComponent.js.map +1 -1
- package/dist/react/useQuery.d.ts +3 -0
- package/dist/react/useQuery.d.ts.map +1 -0
- package/dist/react/useQuery.js +38 -0
- package/dist/react/useQuery.js.map +1 -0
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +3 -3
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +6 -3
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +1 -0
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +4 -4
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +2 -2
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +5 -5
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +2 -2
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/schema.d.ts +0 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -6
- package/dist/schema.js.map +1 -1
- package/dist/storage/in-memory/index.d.ts +2 -2
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/in-memory/index.js.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.d.ts +2 -2
- package/dist/storage/tauri/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.js.map +1 -1
- package/dist/storage/web-worker/index.d.ts +4 -4
- package/dist/storage/web-worker/index.d.ts.map +1 -1
- package/dist/storage/web-worker/index.js +3 -5
- package/dist/storage/web-worker/index.js.map +1 -1
- package/dist/storage/web-worker/worker.js +2 -2
- package/dist/storage/web-worker/worker.js.map +1 -1
- package/dist/store.d.ts +14 -7
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +80 -46
- package/dist/store.js.map +1 -1
- package/dist/util.d.ts +3 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +2 -0
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/react/fixture.tsx +9 -9
- package/src/__tests__/reactive.test.ts +3 -4
- package/src/effect/LiveStore.ts +14 -18
- package/src/inMemoryDatabase.ts +20 -28
- package/src/index.ts +10 -4
- package/src/migrations.ts +39 -21
- package/src/react/LiveStoreProvider.tsx +13 -16
- package/src/react/index.ts +1 -1
- package/src/react/useGraphQL.ts +28 -2
- package/src/react/useLiveStoreComponent.ts +50 -24
- package/src/react/useQuery.ts +56 -0
- package/src/reactive.ts +6 -4
- package/src/reactiveQueries/base-class.ts +9 -3
- package/src/reactiveQueries/graphql.ts +4 -4
- package/src/reactiveQueries/js.ts +2 -2
- package/src/reactiveQueries/sql.ts +6 -6
- package/src/schema.ts +2 -5
- package/src/storage/in-memory/index.ts +2 -2
- package/src/storage/index.ts +2 -2
- package/src/storage/tauri/index.ts +2 -2
- package/src/storage/web-worker/index.ts +6 -8
- package/src/storage/web-worker/worker.ts +2 -2
- package/src/store.ts +99 -59
- package/src/util.ts +8 -2
- package/dist/backends/base.d.ts +0 -13
- package/dist/backends/base.d.ts.map +0 -1
- package/dist/backends/base.js +0 -53
- package/dist/backends/base.js.map +0 -1
- package/dist/backends/in-memory/index.d.ts +0 -22
- package/dist/backends/in-memory/index.d.ts.map +0 -1
- package/dist/backends/in-memory/index.js +0 -45
- package/dist/backends/in-memory/index.js.map +0 -1
- package/dist/backends/index.d.ts +0 -41
- package/dist/backends/index.d.ts.map +0 -1
- package/dist/backends/index.js +0 -16
- package/dist/backends/index.js.map +0 -1
- package/dist/backends/tauri/index.d.ts +0 -21
- package/dist/backends/tauri/index.d.ts.map +0 -1
- package/dist/backends/tauri/index.js +0 -48
- package/dist/backends/tauri/index.js.map +0 -1
- package/dist/backends/utils/idb.d.ts +0 -10
- package/dist/backends/utils/idb.d.ts.map +0 -1
- package/dist/backends/utils/idb.js +0 -58
- package/dist/backends/utils/idb.js.map +0 -1
- package/dist/backends/web-worker/index.d.ts +0 -26
- package/dist/backends/web-worker/index.d.ts.map +0 -1
- package/dist/backends/web-worker/index.js +0 -63
- package/dist/backends/web-worker/index.js.map +0 -1
- package/dist/backends/web-worker/worker.d.ts +0 -17
- package/dist/backends/web-worker/worker.d.ts.map +0 -1
- package/dist/backends/web-worker/worker.js +0 -139
- package/dist/backends/web-worker/worker.js.map +0 -1
- package/dist/react/useGlobalQuery.d.ts +0 -3
- package/dist/react/useGlobalQuery.d.ts.map +0 -1
- package/dist/react/useGlobalQuery.js +0 -23
- package/dist/react/useGlobalQuery.js.map +0 -1
- package/dist/storage/base.d.ts +0 -10
- package/dist/storage/base.d.ts.map +0 -1
- package/dist/storage/base.js +0 -14
- package/dist/storage/base.js.map +0 -1
- package/src/react/useGlobalQuery.ts +0 -37
package/src/inMemoryDatabase.ts
CHANGED
|
@@ -2,28 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
4
4
|
import type * as otel from '@opentelemetry/api'
|
|
5
|
-
import type * as
|
|
5
|
+
import type * as Sqlite from 'sqlite-esm'
|
|
6
6
|
|
|
7
7
|
import BoundMap, { BoundArray } from './bounded-collections.js'
|
|
8
8
|
// import { EVENTS_TABLE_NAME } from './events.js'
|
|
9
9
|
import { sql } from './index.js'
|
|
10
10
|
import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './otel.js'
|
|
11
11
|
import QueryCache from './QueryCache.js'
|
|
12
|
-
import type { Bindable,
|
|
13
|
-
import { prepareBindValues } from './util.js'
|
|
12
|
+
import type { Bindable, PreparedBindValues } from './util.js'
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
Basic = 'Basic',
|
|
17
|
-
FullText = 'FullText',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface Index {
|
|
21
|
-
indexType: IndexType
|
|
22
|
-
name: string
|
|
23
|
-
columns: string[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
declare type DatabaseWithCAPI = SqliteWasm.Database & { capi: SqliteWasm.CAPI }
|
|
14
|
+
type DatabaseWithCAPI = Sqlite.Database & { capi: Sqlite.CAPI }
|
|
27
15
|
|
|
28
16
|
export interface DebugInfo {
|
|
29
17
|
slowQueries: BoundArray<SlowQueryInfo>
|
|
@@ -34,7 +22,7 @@ export interface DebugInfo {
|
|
|
34
22
|
|
|
35
23
|
export type SlowQueryInfo = [
|
|
36
24
|
queryStr: string,
|
|
37
|
-
bindValues:
|
|
25
|
+
bindValues: PreparedBindValues | undefined,
|
|
38
26
|
durationMs: number,
|
|
39
27
|
rowsCount: number | undefined,
|
|
40
28
|
queriedTables: string[],
|
|
@@ -50,7 +38,7 @@ export const emptyDebugInfo = (): DebugInfo => ({
|
|
|
50
38
|
|
|
51
39
|
export class InMemoryDatabase {
|
|
52
40
|
// TODO: how many unique active statements are expected?
|
|
53
|
-
private cachedStmts = new BoundMap<string,
|
|
41
|
+
private cachedStmts = new BoundMap<string, Sqlite.PreparedStatement>(200)
|
|
54
42
|
private tablesUsedCache = new BoundMap<string, string[]>(200)
|
|
55
43
|
private resultCache = new QueryCache()
|
|
56
44
|
public debugInfo: DebugInfo = emptyDebugInfo()
|
|
@@ -59,15 +47,20 @@ export class InMemoryDatabase {
|
|
|
59
47
|
private db: DatabaseWithCAPI,
|
|
60
48
|
private otelTracer: otel.Tracer,
|
|
61
49
|
private otelRootSpanContext: otel.Context,
|
|
62
|
-
public SQL:
|
|
50
|
+
public SQL: Sqlite.Sqlite3Static,
|
|
63
51
|
) {}
|
|
64
52
|
|
|
65
|
-
static load(
|
|
66
|
-
data
|
|
67
|
-
otelTracer
|
|
68
|
-
otelRootSpanContext
|
|
69
|
-
sqlite3
|
|
70
|
-
|
|
53
|
+
static load({
|
|
54
|
+
data,
|
|
55
|
+
otelTracer,
|
|
56
|
+
otelRootSpanContext,
|
|
57
|
+
sqlite3,
|
|
58
|
+
}: {
|
|
59
|
+
data: Uint8Array | undefined
|
|
60
|
+
otelTracer: otel.Tracer
|
|
61
|
+
otelRootSpanContext: otel.Context
|
|
62
|
+
sqlite3: Sqlite.Sqlite3Static
|
|
63
|
+
}): InMemoryDatabase {
|
|
71
64
|
// TODO move WASM init higher up in the init process (to do some other work while it's loading)
|
|
72
65
|
|
|
73
66
|
const db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' }) as DatabaseWithCAPI
|
|
@@ -138,7 +131,7 @@ export class InMemoryDatabase {
|
|
|
138
131
|
|
|
139
132
|
execute(
|
|
140
133
|
query: string,
|
|
141
|
-
bindValues?:
|
|
134
|
+
bindValues?: PreparedBindValues,
|
|
142
135
|
writeTables?: string[],
|
|
143
136
|
options?: { hasNoEffects?: boolean; otelContext: otel.Context },
|
|
144
137
|
): { durationMs: number } {
|
|
@@ -155,9 +148,8 @@ export class InMemoryDatabase {
|
|
|
155
148
|
this.cachedStmts.set(query, stmt)
|
|
156
149
|
}
|
|
157
150
|
|
|
158
|
-
// TODO check whether we can remove the extra `prepareBindValues` call here (e.g. enforce proper type in API)
|
|
159
151
|
if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
|
|
160
|
-
stmt.bind(
|
|
152
|
+
stmt.bind(bindValues)
|
|
161
153
|
}
|
|
162
154
|
|
|
163
155
|
if (import.meta.env.DEV) {
|
|
@@ -211,7 +203,7 @@ export class InMemoryDatabase {
|
|
|
211
203
|
query: string,
|
|
212
204
|
options?: {
|
|
213
205
|
queriedTables?: string[]
|
|
214
|
-
bindValues?:
|
|
206
|
+
bindValues?: PreparedBindValues
|
|
215
207
|
skipCache?: boolean
|
|
216
208
|
otelContext?: otel.Context
|
|
217
209
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
export { Store, createStore
|
|
2
|
-
export type {
|
|
1
|
+
export { Store, createStore } from './store.js'
|
|
2
|
+
export type {
|
|
3
|
+
LiveStoreQuery,
|
|
4
|
+
GetAtomResult,
|
|
5
|
+
BaseGraphQLContext,
|
|
6
|
+
QueryResult,
|
|
7
|
+
QueryDebugInfo,
|
|
8
|
+
RefreshReason,
|
|
9
|
+
} from './store.js'
|
|
3
10
|
|
|
4
11
|
export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
|
|
5
12
|
|
|
6
13
|
export {
|
|
7
14
|
defineComponentStateSchema,
|
|
8
|
-
EVENT_CURSOR_TABLE,
|
|
9
15
|
defineAction,
|
|
10
16
|
defineActions,
|
|
11
17
|
defineTables,
|
|
@@ -37,5 +43,5 @@ export type TableDefinition = SqliteAst.Table
|
|
|
37
43
|
|
|
38
44
|
export { SqliteDsl as DbSchema } from 'effect-db-schema'
|
|
39
45
|
|
|
40
|
-
export { sql, type Bindable } from './util.js'
|
|
46
|
+
export { prepareBindValues, sql, type Bindable, type PreparedBindValues } from './util.js'
|
|
41
47
|
export { isEqual } from 'lodash-es'
|
package/src/migrations.ts
CHANGED
|
@@ -5,8 +5,11 @@ import { memoize, omit } from 'lodash-es'
|
|
|
5
5
|
import type { InMemoryDatabase } from './index.js'
|
|
6
6
|
import type { Schema, SchemaMetaRow } from './schema.js'
|
|
7
7
|
import { componentStateTables, SCHEMA_META_TABLE, systemTables } from './schema.js'
|
|
8
|
+
import type { PreparedBindValues } from './util.js'
|
|
8
9
|
import { sql } from './util.js'
|
|
9
10
|
|
|
11
|
+
const getMemoizedTimestamp = memoize(() => new Date().toISOString())
|
|
12
|
+
|
|
10
13
|
// TODO more graceful DB migration (e.g. backup DB before destructive migrations)
|
|
11
14
|
export const migrateDb = ({
|
|
12
15
|
db,
|
|
@@ -31,7 +34,6 @@ export const migrateDb = ({
|
|
|
31
34
|
schemaMetaRows.map(({ tableName, schemaHash }) => [tableName, schemaHash]),
|
|
32
35
|
)
|
|
33
36
|
|
|
34
|
-
const getMemoizedTimestamp = memoize(() => new Date().toISOString())
|
|
35
37
|
const tableDefs = {
|
|
36
38
|
// NOTE it's important the `SCHEMA_META_TABLE` comes first since we're writing to it below
|
|
37
39
|
[SCHEMA_META_TABLE]: systemTables[SCHEMA_META_TABLE],
|
|
@@ -47,30 +49,46 @@ export const migrateDb = ({
|
|
|
47
49
|
`Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
|
|
48
50
|
)
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// TODO need to possibly handle cascading deletes due to foreign keys
|
|
53
|
-
db.execute(sql`drop table if exists ${tableName}`, undefined, [], { otelContext })
|
|
54
|
-
db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, [], { otelContext })
|
|
55
|
-
|
|
56
|
-
for (const index of tableDef.indexes) {
|
|
57
|
-
db.execute(createIndexFromDefinition(tableName, index), undefined, [], { otelContext })
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const updatedAt = getMemoizedTimestamp()
|
|
61
|
-
db.execute(
|
|
62
|
-
sql`
|
|
63
|
-
INSERT INTO ${SCHEMA_META_TABLE} (tableName, schemaHash, updatedAt) VALUES ($tableName, $schemaHash, $updatedAt)
|
|
64
|
-
ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
|
|
65
|
-
`,
|
|
66
|
-
{ tableName, schemaHash, updatedAt },
|
|
67
|
-
[],
|
|
68
|
-
{ otelContext },
|
|
69
|
-
)
|
|
52
|
+
migrateTable({ db, tableDef, otelContext, schemaHash })
|
|
70
53
|
}
|
|
71
54
|
}
|
|
72
55
|
}
|
|
73
56
|
|
|
57
|
+
export const migrateTable = ({
|
|
58
|
+
db,
|
|
59
|
+
tableDef,
|
|
60
|
+
otelContext,
|
|
61
|
+
schemaHash,
|
|
62
|
+
}: {
|
|
63
|
+
db: InMemoryDatabase
|
|
64
|
+
tableDef: SqliteAst.Table
|
|
65
|
+
otelContext: otel.Context
|
|
66
|
+
schemaHash: number
|
|
67
|
+
}) => {
|
|
68
|
+
console.log(`Migrating table '${tableDef.name}'...`)
|
|
69
|
+
const tableName = tableDef.name
|
|
70
|
+
const columnSpec = makeColumnSpec(tableDef)
|
|
71
|
+
|
|
72
|
+
// TODO need to possibly handle cascading deletes due to foreign keys
|
|
73
|
+
db.execute(sql`drop table if exists ${tableName}`, undefined, [], { otelContext })
|
|
74
|
+
db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, [], { otelContext })
|
|
75
|
+
|
|
76
|
+
for (const index of tableDef.indexes) {
|
|
77
|
+
db.execute(createIndexFromDefinition(tableName, index), undefined, [], { otelContext })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const updatedAt = getMemoizedTimestamp()
|
|
81
|
+
db.execute(
|
|
82
|
+
sql`
|
|
83
|
+
INSERT INTO ${SCHEMA_META_TABLE} (tableName, schemaHash, updatedAt) VALUES ($tableName, $schemaHash, $updatedAt)
|
|
84
|
+
ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
|
|
85
|
+
`,
|
|
86
|
+
{ $tableName: tableName, $schemaHash: schemaHash, $updatedAt: updatedAt } as unknown as PreparedBindValues,
|
|
87
|
+
[],
|
|
88
|
+
{ otelContext },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
|
|
75
93
|
const uniqueStr = index.unique ? 'UNIQUE' : ''
|
|
76
94
|
return sql`create ${uniqueStr} index ${index.name} on ${tableName} (${index.columns.join(', ')})`
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import type * as otel from '@opentelemetry/api'
|
|
2
|
-
import { mapValues } from 'lodash-es'
|
|
3
2
|
import type { ReactElement, ReactNode } from 'react'
|
|
4
3
|
import React from 'react'
|
|
4
|
+
import initSqlite3Wasm from 'sqlite-esm'
|
|
5
5
|
|
|
6
6
|
// TODO refactor so the `react` module doesn't depend on `effect` module
|
|
7
|
-
import type {
|
|
8
|
-
GlobalQueryDefs,
|
|
9
|
-
LiveStoreContext as StoreContext_,
|
|
10
|
-
LiveStoreCreateStoreOptions,
|
|
11
|
-
} from '../effect/LiveStore.js'
|
|
7
|
+
import type { LiveStoreContext as StoreContext_, LiveStoreCreateStoreOptions } from '../effect/LiveStore.js'
|
|
12
8
|
import type { InMemoryDatabase } from '../inMemoryDatabase.js'
|
|
13
9
|
import type { Schema } from '../schema.js'
|
|
14
10
|
import type { StorageInit } from '../storage/index.js'
|
|
@@ -16,11 +12,17 @@ import type { BaseGraphQLContext, GraphQLOptions } from '../store.js'
|
|
|
16
12
|
import { createStore } from '../store.js'
|
|
17
13
|
import { LiveStoreContext } from './LiveStoreContext.js'
|
|
18
14
|
|
|
15
|
+
// NOTE we're starting to initialize the sqlite wasm binary here (already before calling `createStore`),
|
|
16
|
+
// so that it's ready when we need it
|
|
17
|
+
const sqlite3Promise = initSqlite3Wasm({
|
|
18
|
+
print: (message) => console.log(`[livestore sqlite] ${message}`),
|
|
19
|
+
printErr: (message) => console.error(`[livestore sqlite] ${message}`),
|
|
20
|
+
})
|
|
21
|
+
|
|
19
22
|
interface LiveStoreProviderProps<GraphQLContext> {
|
|
20
23
|
schema: Schema
|
|
21
24
|
loadStorage: () => StorageInit | Promise<StorageInit>
|
|
22
25
|
boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
23
|
-
globalQueryDefs: GlobalQueryDefs
|
|
24
26
|
graphQLOptions?: GraphQLOptions<GraphQLContext>
|
|
25
27
|
otelTracer?: otel.Tracer
|
|
26
28
|
otelRootSpanContext?: otel.Context
|
|
@@ -29,7 +31,6 @@ interface LiveStoreProviderProps<GraphQLContext> {
|
|
|
29
31
|
|
|
30
32
|
export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
|
|
31
33
|
fallback,
|
|
32
|
-
globalQueryDefs,
|
|
33
34
|
loadStorage,
|
|
34
35
|
graphQLOptions,
|
|
35
36
|
otelTracer,
|
|
@@ -40,7 +41,6 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
40
41
|
}: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): JSX.Element => {
|
|
41
42
|
const store = useCreateStore({
|
|
42
43
|
schema,
|
|
43
|
-
globalQueryDefs,
|
|
44
44
|
loadStorage,
|
|
45
45
|
graphQLOptions,
|
|
46
46
|
otelTracer,
|
|
@@ -59,7 +59,6 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
59
59
|
|
|
60
60
|
const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
61
61
|
schema,
|
|
62
|
-
globalQueryDefs,
|
|
63
62
|
loadStorage,
|
|
64
63
|
graphQLOptions,
|
|
65
64
|
otelTracer,
|
|
@@ -71,6 +70,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
71
70
|
React.useEffect(() => {
|
|
72
71
|
void (async () => {
|
|
73
72
|
try {
|
|
73
|
+
const sqlite3 = await sqlite3Promise
|
|
74
74
|
const store = await createStore({
|
|
75
75
|
schema,
|
|
76
76
|
loadStorage,
|
|
@@ -78,12 +78,9 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
78
78
|
otelTracer,
|
|
79
79
|
otelRootSpanContext,
|
|
80
80
|
boot,
|
|
81
|
+
sqlite3,
|
|
81
82
|
})
|
|
82
|
-
|
|
83
|
-
const globalQueries = mapValues(globalQueryDefs, (queryDef) => queryDef(store))
|
|
84
|
-
setCtxValue({ store, globalQueries })
|
|
85
|
-
span.end()
|
|
86
|
-
})
|
|
83
|
+
setCtxValue({ store })
|
|
87
84
|
} catch (e) {
|
|
88
85
|
console.error(`Error creating LiveStore store:`, e)
|
|
89
86
|
throw e
|
|
@@ -91,7 +88,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
91
88
|
})()
|
|
92
89
|
|
|
93
90
|
// TODO: do we need to return any cleanup function here?
|
|
94
|
-
}, [schema, loadStorage,
|
|
91
|
+
}, [schema, loadStorage, graphQLOptions, otelTracer, otelRootSpanContext, boot])
|
|
95
92
|
|
|
96
93
|
return ctxValue
|
|
97
94
|
}
|
package/src/react/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { LiveStoreContext, useStore } from './LiveStoreContext.js'
|
|
|
14
14
|
export { LiveStoreProvider } from './LiveStoreProvider.js'
|
|
15
15
|
export { useLiveStoreComponent } from './useLiveStoreComponent.js'
|
|
16
16
|
export { useGraphQL } from './useGraphQL.js'
|
|
17
|
-
export {
|
|
17
|
+
export { useQuery } from './useQuery.js'
|
|
18
18
|
|
|
19
19
|
// Needed to make TS happy
|
|
20
20
|
export type { TypedDocumentNode } from '@graphql-typed-document-node/core'
|
package/src/react/useGraphQL.ts
CHANGED
|
@@ -15,6 +15,12 @@ export type UseLiveStoreComponentProps<TResult extends Record<string, any>, TVar
|
|
|
15
15
|
reactDeps?: React.DependencyList
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
type Variables = Record<string, any>
|
|
19
|
+
|
|
20
|
+
// TODO get rid of the query cache in favour of the new side-effect-free query definition approach https://www.notion.so/schickling/New-query-definition-approach-1097a78ef0e9495bac25f90417374756?pvs=4
|
|
21
|
+
// NOTE we're using a nested map here since we need to resolve 2 levels of object identities (query + variables)
|
|
22
|
+
// const queryCache = new Map<DocumentNode<any, any>, Map<Variables, LiveStoreGraphQLQuery<any, any, any>>>()
|
|
23
|
+
|
|
18
24
|
/**
|
|
19
25
|
* This is needed because the `React.useMemo` call below, can sometimes be called multiple times 🤷,
|
|
20
26
|
* so we need to "cache" the fact that we've already started a span for this component.
|
|
@@ -24,7 +30,7 @@ const spanAlreadyStartedCache = new Map<string, { span: otel.Span; otelContext:
|
|
|
24
30
|
|
|
25
31
|
// TODO 1) figure out a way to make `variables` optional if the query doesn't have any variables (probably requires positional args)
|
|
26
32
|
// TODO 2) allow `.pipe` on the resulting query (possibly as a separate optional prop)
|
|
27
|
-
export const useGraphQL = <TResult extends Record<string, any>, TVariables extends
|
|
33
|
+
export const useGraphQL = <TResult extends Record<string, any>, TVariables extends Variables = {}>({
|
|
28
34
|
query,
|
|
29
35
|
variables,
|
|
30
36
|
componentKey: componentKeyConfig,
|
|
@@ -62,7 +68,27 @@ export const useGraphQL = <TResult extends Record<string, any>, TVariables exten
|
|
|
62
68
|
)
|
|
63
69
|
|
|
64
70
|
const makeLiveStoreQuery = React.useCallback(
|
|
65
|
-
() =>
|
|
71
|
+
() => {
|
|
72
|
+
return store.queryGraphQL(query, () => variables ?? ({} as TVariables), { componentKey, otelContext })
|
|
73
|
+
|
|
74
|
+
// NOTE I had to disable the caching below as still led to many problems
|
|
75
|
+
// We should just implement the new query definition approach instead
|
|
76
|
+
|
|
77
|
+
// const queryCacheForQuery = queryCache.get(query)
|
|
78
|
+
// if (queryCacheForQuery && queryCacheForQuery.has(variables)) {
|
|
79
|
+
// return queryCacheForQuery.get(variables)!
|
|
80
|
+
// }
|
|
81
|
+
|
|
82
|
+
// const newQuery = store.queryGraphQL(query, () => variables ?? ({} as TVariables), { componentKey, otelContext })
|
|
83
|
+
|
|
84
|
+
// if (queryCacheForQuery) {
|
|
85
|
+
// queryCacheForQuery.set(variables, newQuery)
|
|
86
|
+
// } else {
|
|
87
|
+
// queryCache.set(query, new Map([[variables, newQuery]]))
|
|
88
|
+
// }
|
|
89
|
+
|
|
90
|
+
// return newQuery
|
|
91
|
+
},
|
|
66
92
|
// NOTE: we don't include the queries function passed in by the user here;
|
|
67
93
|
// the reason is that we don't want to force them to memoize that function.
|
|
68
94
|
// Instead, we just assume that the function always has the same contents.
|
|
@@ -3,7 +3,7 @@ import type { LiteralUnion, PrettifyFlat } from '@livestore/utils'
|
|
|
3
3
|
import { omit, shouldNeverHappen } from '@livestore/utils'
|
|
4
4
|
import { Schema } from '@livestore/utils/effect'
|
|
5
5
|
import * as otel from '@opentelemetry/api'
|
|
6
|
-
import { SqliteDsl } from 'effect-db-schema'
|
|
6
|
+
import { SqliteAst, SqliteDsl } from 'effect-db-schema'
|
|
7
7
|
import { isEqual, mapValues } from 'lodash-es'
|
|
8
8
|
import type { DependencyList } from 'react'
|
|
9
9
|
import React from 'react'
|
|
@@ -11,11 +11,12 @@ import { v4 as uuid } from 'uuid'
|
|
|
11
11
|
|
|
12
12
|
import type { ComponentKey } from '../componentKey.js'
|
|
13
13
|
import { labelForKey, tableNameForComponentKey } from '../componentKey.js'
|
|
14
|
-
import
|
|
14
|
+
import { migrateTable } from '../migrations.js'
|
|
15
15
|
import type { LiveStoreGraphQLQuery } from '../reactiveQueries/graphql.js'
|
|
16
16
|
import type { LiveStoreJSQuery } from '../reactiveQueries/js.js'
|
|
17
17
|
import type { LiveStoreSQLQuery } from '../reactiveQueries/sql.js'
|
|
18
|
-
import
|
|
18
|
+
import { SCHEMA_META_TABLE } from '../schema.js'
|
|
19
|
+
import type { BaseGraphQLContext, GetAtomResult, LiveStoreQuery, QueryResult, Store } from '../store.js'
|
|
19
20
|
import type { Bindable } from '../util.js'
|
|
20
21
|
import { sql } from '../util.js'
|
|
21
22
|
import { useStore } from './LiveStoreContext.js'
|
|
@@ -28,17 +29,20 @@ export interface QueryDefinitions {
|
|
|
28
29
|
export type QueryResults<TQuery> = { [queryName in keyof TQuery]: PrettifyFlat<QueryResult<TQuery[queryName]>> }
|
|
29
30
|
|
|
30
31
|
export type ReactiveSQL = <TResult>(
|
|
31
|
-
|
|
32
|
+
query: string | ((get: GetAtomResult) => string),
|
|
32
33
|
queriedTables: string[],
|
|
33
34
|
bindValues?: Bindable | undefined,
|
|
34
35
|
) => LiveStoreSQLQuery<TResult>
|
|
36
|
+
|
|
37
|
+
export type ReactiveJS = <TResult>(query: (get: GetAtomResult) => TResult) => LiveStoreJSQuery<TResult>
|
|
38
|
+
|
|
35
39
|
export type ReactiveGraphQL = <
|
|
36
40
|
TResult extends Record<string, any>,
|
|
37
41
|
TVariables extends Record<string, any>,
|
|
38
42
|
TContext extends BaseGraphQLContext,
|
|
39
43
|
>(
|
|
40
44
|
query: DocumentNode<TResult, TVariables>,
|
|
41
|
-
|
|
45
|
+
variableValues: TVariables | ((get: GetAtomResult) => TVariables),
|
|
42
46
|
label?: string,
|
|
43
47
|
) => LiveStoreGraphQLQuery<TResult, TVariables, TContext>
|
|
44
48
|
|
|
@@ -51,7 +55,7 @@ type RegisterSubscription = <TQuery extends LiveStoreQuery>(
|
|
|
51
55
|
type GenQueries<TQueries, TStateResult> = (args: {
|
|
52
56
|
rxSQL: ReactiveSQL
|
|
53
57
|
rxGraphQL: ReactiveGraphQL
|
|
54
|
-
|
|
58
|
+
rxJS: ReactiveJS
|
|
55
59
|
state$: LiveStoreJSQuery<TStateResult>
|
|
56
60
|
/**
|
|
57
61
|
* Registers a subscription.
|
|
@@ -62,9 +66,9 @@ type GenQueries<TQueries, TStateResult> = (args: {
|
|
|
62
66
|
isTemporaryQuery: boolean
|
|
63
67
|
}) => TQueries
|
|
64
68
|
|
|
65
|
-
export type UseLiveStoreComponentProps<TQueries,
|
|
66
|
-
stateSchema?: SqliteDsl.TableDefinition<string,
|
|
67
|
-
queries?: GenQueries<TQueries, SqliteDsl.FromColumns.RowDecoded<
|
|
69
|
+
export type UseLiveStoreComponentProps<TQueries, TStateColumns extends ComponentColumns> = {
|
|
70
|
+
stateSchema?: SqliteDsl.TableDefinition<string, TStateColumns>
|
|
71
|
+
queries?: GenQueries<TQueries, SqliteDsl.FromColumns.RowDecoded<TStateColumns>>
|
|
68
72
|
reactDeps?: React.DependencyList
|
|
69
73
|
componentKey: ComponentKeyConfig
|
|
70
74
|
}
|
|
@@ -115,18 +119,18 @@ export type GetStateTypeEncoded<TTableDef extends SqliteDsl.TableDefinition<any,
|
|
|
115
119
|
* @param config.componentKey A function that returns a unique key for this component.
|
|
116
120
|
* @param config.reactDeps A list of React-level dependencies that will refresh the queries.
|
|
117
121
|
*/
|
|
118
|
-
export const useLiveStoreComponent = <
|
|
122
|
+
export const useLiveStoreComponent = <TStateColumns extends ComponentColumns, TQueries extends QueryDefinitions>({
|
|
119
123
|
stateSchema: stateSchema_,
|
|
120
124
|
queries = () => ({}) as TQueries,
|
|
121
125
|
componentKey: componentKeyConfig,
|
|
122
126
|
reactDeps = [],
|
|
123
|
-
}: UseLiveStoreComponentProps<TQueries,
|
|
127
|
+
}: UseLiveStoreComponentProps<TQueries, TStateColumns>): {
|
|
124
128
|
queryResults: QueryResults<TQueries>
|
|
125
|
-
state: SqliteDsl.FromColumns.RowDecoded<
|
|
126
|
-
setState: Setters<SqliteDsl.FromColumns.RowDecoded<
|
|
127
|
-
useLiveStoreJsonState: UseLiveStoreJsonState<SqliteDsl.FromColumns.RowDecoded<
|
|
129
|
+
state: SqliteDsl.FromColumns.RowDecoded<TStateColumns>
|
|
130
|
+
setState: Setters<SqliteDsl.FromColumns.RowDecoded<TStateColumns>>
|
|
131
|
+
useLiveStoreJsonState: UseLiveStoreJsonState<SqliteDsl.FromColumns.RowDecoded<TStateColumns>>
|
|
128
132
|
} => {
|
|
129
|
-
type TComponentState = SqliteDsl.FromColumns.RowDecoded<
|
|
133
|
+
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TStateColumns>
|
|
130
134
|
|
|
131
135
|
// TODO validate schema to make sure each column has a default value
|
|
132
136
|
// TODO we should clean up the state schema handling to remove this special handling for the `id` column
|
|
@@ -137,7 +141,7 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
|
|
|
137
141
|
|
|
138
142
|
// performance.mark('useLiveStoreComponent:start')
|
|
139
143
|
const componentKey = useComponentKey(componentKeyConfig, reactDeps)
|
|
140
|
-
const { store
|
|
144
|
+
const { store } = useStore()
|
|
141
145
|
|
|
142
146
|
const componentKeyLabel = React.useMemo(() => labelForKey(componentKey), [componentKey])
|
|
143
147
|
|
|
@@ -180,14 +184,17 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
|
|
|
180
184
|
isTemporaryQuery: boolean
|
|
181
185
|
}) =>
|
|
182
186
|
queries({
|
|
183
|
-
rxSQL: <T>(
|
|
184
|
-
|
|
187
|
+
rxSQL: <T>(
|
|
188
|
+
genQuery: string | ((get: GetAtomResult) => string),
|
|
189
|
+
queriedTables: string[],
|
|
190
|
+
bindValues?: Bindable,
|
|
191
|
+
) => store.querySQL<T>(genQuery, { queriedTables, bindValues, otelContext, componentKey }),
|
|
185
192
|
rxGraphQL: <Result extends Record<string, any>, Variables extends Record<string, any>>(
|
|
186
193
|
query: DocumentNode<Result, Variables>,
|
|
187
|
-
genVariableValues: (get:
|
|
194
|
+
genVariableValues: Variables | ((get: GetAtomResult) => Variables),
|
|
188
195
|
label?: string,
|
|
189
196
|
) => store.queryGraphQL(query, genVariableValues, { componentKey, label, otelContext }),
|
|
190
|
-
|
|
197
|
+
rxJS: <T>(genQuery: (get: GetAtomResult) => T) => store.queryJS(genQuery, { componentKey, otelContext }),
|
|
191
198
|
state$,
|
|
192
199
|
subscribe: registerSubscription,
|
|
193
200
|
isTemporaryQuery,
|
|
@@ -199,7 +206,7 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
|
|
|
199
206
|
// This makes sense for LiveStore because the component config should be static.
|
|
200
207
|
// TODO: document this and consider whether it's the right API surface.
|
|
201
208
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
202
|
-
[store, componentKey
|
|
209
|
+
[store, componentKey],
|
|
203
210
|
)
|
|
204
211
|
|
|
205
212
|
const defaultComponentState = React.useMemo(() => {
|
|
@@ -226,6 +233,7 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
|
|
|
226
233
|
return store.otel.tracer.startActiveSpan('LiveStore:useLiveStoreComponent:initial', {}, otelContext, (span) => {
|
|
227
234
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
228
235
|
|
|
236
|
+
// NOTE `inTempQueryContext` automatically destroys the queries once the callback is done
|
|
229
237
|
return store.inTempQueryContext(() => {
|
|
230
238
|
try {
|
|
231
239
|
// create state query
|
|
@@ -239,6 +247,24 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
|
|
|
239
247
|
} else {
|
|
240
248
|
const componentTableName = tableNameForComponentKey(componentKey)
|
|
241
249
|
const whereClause = componentKey._tag === 'singleton' ? '' : `where id = '${componentKey.id}'`
|
|
250
|
+
|
|
251
|
+
// TODO find a better solution for this
|
|
252
|
+
if (store.tableRefs[componentTableName] === undefined) {
|
|
253
|
+
const schemaHash = SqliteAst.hash(stateSchema.ast)
|
|
254
|
+
const res = store.inMemoryDB.select<{ schemaHash: number }>(
|
|
255
|
+
sql`SELECT schemaHash FROM ${SCHEMA_META_TABLE} WHERE tableName = '${componentTableName}'`,
|
|
256
|
+
)
|
|
257
|
+
if (res.length === 0 || res[0]!.schemaHash !== schemaHash) {
|
|
258
|
+
migrateTable({ db: store._proxyDb, tableDef: stateSchema.ast, otelContext, schemaHash })
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
store.tableRefs[componentTableName] = store.graph.makeRef(null, {
|
|
262
|
+
equal: () => false,
|
|
263
|
+
label: componentTableName,
|
|
264
|
+
meta: { liveStoreRefType: 'table' },
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
242
268
|
state$ = store
|
|
243
269
|
.querySQL(() => sql`select * from ${componentTableName} ${whereClause} limit 1`, {
|
|
244
270
|
queriedTables: [componentTableName],
|
|
@@ -380,15 +406,15 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
|
|
|
380
406
|
),
|
|
381
407
|
)
|
|
382
408
|
|
|
383
|
-
const registerSubscription: RegisterSubscription = (query
|
|
409
|
+
const registerSubscription: RegisterSubscription = (query$, callback, onUnsubscribe) => {
|
|
384
410
|
unsubs.push(
|
|
385
411
|
store.subscribe(
|
|
386
|
-
query
|
|
412
|
+
query$,
|
|
387
413
|
(results) => {
|
|
388
414
|
callback(results)
|
|
389
415
|
},
|
|
390
416
|
onUnsubscribe,
|
|
391
|
-
{ label: `useLiveStoreComponent:query:manual-subscribe:${query
|
|
417
|
+
{ label: `useLiveStoreComponent:query:manual-subscribe:${query$.label}` },
|
|
392
418
|
),
|
|
393
419
|
)
|
|
394
420
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { labelForKey } from '../componentKey.js'
|
|
4
|
+
import type { QueryDefinition } from '../effect/LiveStore.js'
|
|
5
|
+
import type { LiveStoreQuery, QueryResult, Store } from '../store.js'
|
|
6
|
+
import { useStore } from './LiveStoreContext.js'
|
|
7
|
+
|
|
8
|
+
// TODO get rid of the query cache in favour of the new side-effect-free query definition approach https://www.notion.so/schickling/New-query-definition-approach-1097a78ef0e9495bac25f90417374756?pvs=4
|
|
9
|
+
const queryCache = new Map<QueryDefinition, LiveStoreQuery>()
|
|
10
|
+
|
|
11
|
+
export const useQuery = <Q extends LiveStoreQuery>(queryDef: (store: Store) => Q): QueryResult<Q> => {
|
|
12
|
+
const { store } = useStore()
|
|
13
|
+
const query = React.useMemo(() => {
|
|
14
|
+
if (queryCache.has(queryDef)) return queryCache.get(queryDef) as Q
|
|
15
|
+
|
|
16
|
+
const query = queryDef(store)
|
|
17
|
+
queryCache.set(queryDef, query)
|
|
18
|
+
return query
|
|
19
|
+
}, [store, queryDef])
|
|
20
|
+
|
|
21
|
+
// We know the query has a result by the time we use it; so we can synchronously populate a default state
|
|
22
|
+
const [value, setValue] = React.useState<QueryResult<Q>>(query.results$.result)
|
|
23
|
+
|
|
24
|
+
// Subscribe to future updates for this query
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
return store.otel.tracer.startActiveSpan(
|
|
27
|
+
`LiveStore:useQuery:${labelForKey(query.componentKey)}:${query.label}`,
|
|
28
|
+
{ attributes: { label: query.label } },
|
|
29
|
+
query.otelContext,
|
|
30
|
+
(span) => {
|
|
31
|
+
const cancel = store.subscribe(
|
|
32
|
+
query,
|
|
33
|
+
(v) => {
|
|
34
|
+
// NOTE: we return a reference to the result object within LiveStore;
|
|
35
|
+
// this implies that app code must not mutate the results, or else
|
|
36
|
+
// there may be weird reactivity bugs.
|
|
37
|
+
return setValue(v)
|
|
38
|
+
},
|
|
39
|
+
undefined,
|
|
40
|
+
{ label: query.label },
|
|
41
|
+
)
|
|
42
|
+
return () => {
|
|
43
|
+
// // NOTE destroying the whole query will also unsubscribe it
|
|
44
|
+
// query.destroy()
|
|
45
|
+
|
|
46
|
+
// TODO for now we'll still `cancel` manually, but we should remove this once we have some kind of
|
|
47
|
+
// ARC-based system
|
|
48
|
+
cancel()
|
|
49
|
+
span.end()
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
}, [query, store])
|
|
54
|
+
|
|
55
|
+
return value
|
|
56
|
+
}
|
package/src/reactive.ts
CHANGED
|
@@ -30,6 +30,9 @@ import { isEqual, max, uniqueId } from 'lodash-es'
|
|
|
30
30
|
|
|
31
31
|
import { BoundArray } from './bounded-collections.js'
|
|
32
32
|
|
|
33
|
+
const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
|
|
34
|
+
type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
|
|
35
|
+
|
|
33
36
|
export type GetAtom = <T>(atom: Atom<T>) => T
|
|
34
37
|
|
|
35
38
|
export type Ref<T> = {
|
|
@@ -59,7 +62,7 @@ type BaseThunk<T> = {
|
|
|
59
62
|
equal: (a: T, b: T) => boolean
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
type UnevaluatedThunk<T> = BaseThunk<T> & { result:
|
|
65
|
+
type UnevaluatedThunk<T> = BaseThunk<T> & { result: NOT_REFRESHED_YET }
|
|
63
66
|
export type Thunk<T> = BaseThunk<T> & { result: T }
|
|
64
67
|
|
|
65
68
|
export type Atom<T> = Ref<T> | Thunk<T>
|
|
@@ -201,7 +204,7 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
201
204
|
const thunk: UnevaluatedThunk<T> = {
|
|
202
205
|
_tag: 'thunk',
|
|
203
206
|
id: uniqueNodeId(),
|
|
204
|
-
result:
|
|
207
|
+
result: NOT_REFRESHED_YET,
|
|
205
208
|
height: 0,
|
|
206
209
|
getResult,
|
|
207
210
|
sub: new Set(),
|
|
@@ -342,7 +345,7 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
342
345
|
this.addEdge(context, atom)
|
|
343
346
|
|
|
344
347
|
const dependencyMightBeStale = context._tag !== 'effect' && context.height <= atom.height
|
|
345
|
-
const dependencyNotRefreshedYet = atom.result ===
|
|
348
|
+
const dependencyNotRefreshedYet = atom.result === NOT_REFRESHED_YET
|
|
346
349
|
|
|
347
350
|
if (dependencyMightBeStale || dependencyNotRefreshedYet) {
|
|
348
351
|
throw new DependencyNotReadyError(
|
|
@@ -350,7 +353,6 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
350
353
|
)
|
|
351
354
|
}
|
|
352
355
|
|
|
353
|
-
// TODO handle case when `atom.result` is undefined
|
|
354
356
|
return atom.result
|
|
355
357
|
}
|
|
356
358
|
|