@livestore/react 0.0.58-dev.13 → 0.0.58-dev.14

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 (45) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/LiveStoreContext.d.ts +1 -1
  3. package/dist/LiveStoreContext.d.ts.map +1 -1
  4. package/dist/LiveStoreProvider.d.ts +4 -4
  5. package/dist/LiveStoreProvider.d.ts.map +1 -1
  6. package/dist/LiveStoreProvider.js.map +1 -1
  7. package/dist/LiveStoreProvider.test.js +2 -2
  8. package/dist/LiveStoreProvider.test.js.map +1 -1
  9. package/dist/__tests__/fixture.d.ts +10 -10
  10. package/dist/__tests__/fixture.d.ts.map +1 -1
  11. package/dist/__tests__/fixture.js +2 -4
  12. package/dist/__tests__/fixture.js.map +1 -1
  13. package/dist/experimental/components/LiveList.js +2 -2
  14. package/dist/experimental/components/LiveList.js.map +1 -1
  15. package/dist/mod.d.ts +1 -1
  16. package/dist/mod.d.ts.map +1 -1
  17. package/dist/mod.js +1 -1
  18. package/dist/mod.js.map +1 -1
  19. package/dist/useRow.js +2 -2
  20. package/dist/useRow.js.map +1 -1
  21. package/dist/useRow.test.js +1 -1
  22. package/dist/useRow.test.js.map +1 -1
  23. package/dist/useScopedQuery.d.ts +22 -0
  24. package/dist/useScopedQuery.d.ts.map +1 -0
  25. package/dist/useScopedQuery.js +75 -0
  26. package/dist/useScopedQuery.js.map +1 -0
  27. package/dist/useScopedQuery.test.d.ts +2 -0
  28. package/dist/useScopedQuery.test.d.ts.map +1 -0
  29. package/dist/useScopedQuery.test.js +59 -0
  30. package/dist/useScopedQuery.test.js.map +1 -0
  31. package/dist/useTemporaryQuery.d.ts +3 -3
  32. package/dist/useTemporaryQuery.d.ts.map +1 -1
  33. package/dist/useTemporaryQuery.js +5 -5
  34. package/dist/useTemporaryQuery.js.map +1 -1
  35. package/package.json +6 -6
  36. package/src/LiveStoreProvider.test.tsx +5 -6
  37. package/src/LiveStoreProvider.tsx +7 -3
  38. package/src/__tests__/fixture.tsx +9 -7
  39. package/src/experimental/components/LiveList.tsx +2 -2
  40. package/src/mod.ts +1 -1
  41. package/src/useRow.test.tsx +1 -1
  42. package/src/useRow.ts +2 -2
  43. package/src/{useTemporaryQuery.test.tsx → useScopedQuery.test.tsx} +3 -6
  44. package/src/useScopedQuery.ts +131 -0
  45. package/src/useTemporaryQuery.ts +6 -6
@@ -0,0 +1,59 @@
1
+ import * as LiveStore from '@livestore/livestore';
2
+ import { querySQL } from '@livestore/livestore';
3
+ import { Effect, Schema } from '@livestore/utils/effect';
4
+ import { render, renderHook } from '@testing-library/react';
5
+ import React from 'react';
6
+ // @ts-expect-error no types
7
+ import * as ReactWindow from 'react-window';
8
+ import { describe, expect, it } from 'vitest';
9
+ import { makeTodoMvcReact, tables, todos } from './__tests__/fixture.js';
10
+ import * as LiveStoreReact from './mod.js';
11
+ describe('useScopedQuery', () => {
12
+ it('simple', () => Effect.gen(function* () {
13
+ const { wrapper, store, makeRenderCount } = yield* makeTodoMvcReact();
14
+ const renderCount = makeRenderCount();
15
+ store.mutate(todos.insert({ id: 't1', text: 'buy milk', completed: false }), todos.insert({ id: 't2', text: 'buy bread', completed: false }));
16
+ const queryMap = new Map();
17
+ const { rerender, result, unmount } = renderHook((id) => {
18
+ renderCount.inc();
19
+ return LiveStoreReact.useScopedQuery(() => {
20
+ const query$ = querySQL(`select * from todos where id = '${id}'`, {
21
+ schema: Schema.Array(tables.todos.schema),
22
+ });
23
+ queryMap.set(id, query$);
24
+ return query$;
25
+ }, id);
26
+ }, { wrapper, initialProps: 't1' });
27
+ expect(result.current.length).toBe(1);
28
+ expect(result.current[0].text).toBe('buy milk');
29
+ expect(renderCount.val).toBe(1);
30
+ expect(queryMap.get('t1').runs).toBe(1);
31
+ rerender('t2');
32
+ expect(result.current.length).toBe(1);
33
+ expect(result.current[0].text).toBe('buy bread');
34
+ expect(renderCount.val).toBe(2);
35
+ expect(queryMap.get('t1').runs).toBe(1);
36
+ expect(queryMap.get('t2').runs).toBe(1);
37
+ unmount();
38
+ expect(queryMap.get('t2').runs).toBe(1);
39
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise));
40
+ // NOTE this test covers some special react lifecyle paths which I couldn't easily reproduce without react-window
41
+ // it basically causes a "query swap" in the `useMemo` and both a `useEffect` cleanup call.
42
+ // To handle this properly we introduced the `_tag: 'destroyed'` state in the `spanAlreadyStartedCache`.
43
+ it('should work for a list with react-window', () => Effect.gen(function* () {
44
+ const { wrapper } = yield* makeTodoMvcReact();
45
+ const ListWrapper = ({ numItems }) => {
46
+ return (React.createElement(ReactWindow.FixedSizeList, { height: 100, width: 100, itemSize: 10, itemCount: numItems, itemData: Array.from({ length: numItems }, (_, i) => i).reverse() }, ListItem));
47
+ };
48
+ const ListItem = ({ data: ids, index }) => {
49
+ const id = ids[index];
50
+ const res = LiveStoreReact.useScopedQuery(() => LiveStore.computed(() => id, { label: `ListItem.${id}` }), id);
51
+ return React.createElement("div", { role: "listitem" }, res);
52
+ };
53
+ const renderResult = render(React.createElement(ListWrapper, { numItems: 1 }), { wrapper });
54
+ expect(renderResult.container.textContent).toBe('0');
55
+ renderResult.rerender(React.createElement(ListWrapper, { numItems: 2 }));
56
+ expect(renderResult.container.textContent).toBe('10');
57
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise));
58
+ });
59
+ //# sourceMappingURL=useScopedQuery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useScopedQuery.test.js","sourceRoot":"","sources":["../src/useScopedQuery.test.tsx"],"names":[],"mappings":"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"}
@@ -8,9 +8,9 @@ export type DepKey = string | number | ReadonlyArray<string | number>;
8
8
  *
9
9
  * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
10
10
  */
11
- export declare const useTemporaryQuery: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => TResult;
12
- export declare const useTemporaryQueryRef: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => React.MutableRefObject<TResult>;
13
- export declare const useMakeTemporaryQuery: <TResult, TQueryInfo extends QueryInfo>(makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>, key: DepKey, options?: {
11
+ export declare const useScopedQuery: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => TResult;
12
+ export declare const useScopedQueryRef: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => React.MutableRefObject<TResult>;
13
+ export declare const useMakeScopedQuery: <TResult, TQueryInfo extends QueryInfo>(makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>, key: DepKey, options?: {
14
14
  otel?: {
15
15
  spanName?: string;
16
16
  attributes?: otel.Attributes;
@@ -1 +1 @@
1
- {"version":3,"file":"useTemporaryQuery.d.ts","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AAuBzB,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;AAErE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,aAAa,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,MAAM,KAAG,OAChD,CAAA;AAE9C,eAAO,MAAM,oBAAoB,GAAI,OAAO,aAC/B,MAAM,SAAS,CAAC,OAAO,CAAC,OAC9B,MAAM,KACV,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAIhC,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,OAAO,EAAE,UAAU,SAAS,SAAS,aAC9D,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,OACnE,MAAM,YACD;IACR,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;KAC7B,CAAA;CACF,KACA;IAAE,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAA;CA4ErE,CAAA"}
1
+ {"version":3,"file":"useTemporaryQuery.d.ts","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AAuBzB,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;AAErE;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,aAAa,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,MAAM,KAAG,OAChD,CAAA;AAE3C,eAAO,MAAM,iBAAiB,GAAI,OAAO,aAC5B,MAAM,SAAS,CAAC,OAAO,CAAC,OAC9B,MAAM,KACV,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAIhC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,EAAE,UAAU,SAAS,SAAS,aAC3D,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,OACnE,MAAM,YACD;IACR,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;KAC7B,CAAA;CACF,KACA;IAAE,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAA;CA4ErE,CAAA"}
@@ -12,12 +12,12 @@ const cache = new Map();
12
12
  *
13
13
  * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
14
14
  */
15
- export const useTemporaryQuery = (makeQuery, key) => useTemporaryQueryRef(makeQuery, key).current;
16
- export const useTemporaryQueryRef = (makeQuery, key) => {
17
- const { query$ } = useMakeTemporaryQuery(makeQuery, key);
15
+ export const useScopedQuery = (makeQuery, key) => useScopedQueryRef(makeQuery, key).current;
16
+ export const useScopedQueryRef = (makeQuery, key) => {
17
+ const { query$ } = useMakeScopedQuery(makeQuery, key);
18
18
  return useQueryRef(query$);
19
19
  };
20
- export const useMakeTemporaryQuery = (makeQuery, key, options) => {
20
+ export const useMakeScopedQuery = (makeQuery, key, options) => {
21
21
  const { store } = useStore();
22
22
  const fullKey = React.useMemo(
23
23
  // NOTE We're using the `makeQuery` function body string to make sure the key is unique across the app
@@ -44,7 +44,7 @@ export const useMakeTemporaryQuery = (makeQuery, key, options) => {
44
44
  cachedItem.rc++;
45
45
  return cachedItem;
46
46
  }
47
- const spanName = options?.otel?.spanName ?? `LiveStore:useTemporaryQuery:${key}`;
47
+ const spanName = options?.otel?.spanName ?? `LiveStore:useScopedQuery:${key}`;
48
48
  const span = store.otel.tracer.startSpan(spanName, { attributes: options?.otel?.attributes }, store.otel.queriesSpanContext);
49
49
  const otelContext = otel.trace.setSpan(otel.context.active(), span);
50
50
  const query$ = makeQuery(otelContext);
@@ -1 +1 @@
1
- {"version":3,"file":"useTemporaryQuery.js","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useTemporaryQuery.js","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/react",
3
- "version": "0.0.58-dev.13",
3
+ "version": "0.0.58-dev.14",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -22,10 +22,10 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@opentelemetry/api": "^1.9.0",
25
- "@livestore/common": "0.0.58-dev.13",
26
- "@livestore/utils": "0.0.58-dev.13",
27
- "@livestore/db-schema": "0.0.58-dev.13",
28
- "@livestore/livestore": "0.0.58-dev.13"
25
+ "@livestore/common": "0.0.58-dev.14",
26
+ "@livestore/db-schema": "0.0.58-dev.14",
27
+ "@livestore/livestore": "0.0.58-dev.14",
28
+ "@livestore/utils": "0.0.58-dev.14"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@opentelemetry/sdk-trace-base": "1.27.0",
@@ -39,7 +39,7 @@
39
39
  "typescript": "5.6.3",
40
40
  "vite": "5.4.10",
41
41
  "vitest": "^2.1.4",
42
- "@livestore/web": "0.0.58-dev.13"
42
+ "@livestore/web": "0.0.58-dev.14"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": "^18"
@@ -1,6 +1,5 @@
1
- import type { BootDb } from '@livestore/common'
2
1
  import { sql } from '@livestore/common'
3
- import { querySQL } from '@livestore/livestore'
2
+ import { querySQL, type Store } from '@livestore/livestore'
4
3
  import { Schema } from '@livestore/utils/effect'
5
4
  import { makeInMemoryAdapter } from '@livestore/web'
6
5
  import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'
@@ -30,8 +29,8 @@ describe('LiveStoreProvider', () => {
30
29
 
31
30
  const Root = ({ forceUpdate }: { forceUpdate: number }) => {
32
31
  const bootCb = React.useCallback(
33
- (db: BootDb) =>
34
- db.execute(sql`INSERT OR IGNORE INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
32
+ (store: Store) =>
33
+ store.__execute(sql`INSERT OR IGNORE INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
35
34
  [],
36
35
  )
37
36
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -81,8 +80,8 @@ describe('LiveStoreProvider', () => {
81
80
 
82
81
  const Root = ({ forceUpdate }: { forceUpdate: number }) => {
83
82
  const bootCb = React.useCallback(
84
- (db: BootDb) =>
85
- db.execute(sql`INSERT INTO todos_mising_table (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
83
+ (store: Store) =>
84
+ store.__execute(sql`INSERT INTO todos_mising_table (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
86
85
  [],
87
86
  )
88
87
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -1,4 +1,4 @@
1
- import type { Adapter, BootDb, BootStatus, IntentionalShutdownCause } from '@livestore/common'
1
+ import type { Adapter, BootStatus, IntentionalShutdownCause } from '@livestore/common'
2
2
  import { UnexpectedError } from '@livestore/common'
3
3
  import type { LiveStoreSchema } from '@livestore/common/schema'
4
4
  import type {
@@ -7,6 +7,7 @@ import type {
7
7
  GraphQLOptions,
8
8
  LiveStoreContext as StoreContext_,
9
9
  OtelOptions,
10
+ Store,
10
11
  } from '@livestore/livestore'
11
12
  import { createStore, StoreAbort, StoreInterrupted } from '@livestore/livestore'
12
13
  import { errorToString } from '@livestore/utils'
@@ -17,7 +18,7 @@ import React from 'react'
17
18
 
18
19
  import { LiveStoreContext } from './LiveStoreContext.js'
19
20
 
20
- interface LiveStoreProviderProps<GraphQLContext> {
21
+ interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
21
22
  schema: LiveStoreSchema
22
23
  /**
23
24
  * The `storeId` can be used to isolate multiple stores from each other.
@@ -30,7 +31,10 @@ interface LiveStoreProviderProps<GraphQLContext> {
30
31
  * @default 'default'
31
32
  */
32
33
  storeId?: string
33
- boot?: (db: BootDb, parentSpan: otel.Span) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
34
+ boot?: (
35
+ store: Store<GraphQLContext, LiveStoreSchema>,
36
+ parentSpan: otel.Span,
37
+ ) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
34
38
  graphQLOptions?: GraphQLOptions<GraphQLContext>
35
39
  otelOptions?: OtelOptions
36
40
  renderLoading: (status: BootStatus) => ReactElement
@@ -1,4 +1,3 @@
1
- import { sql } from '@livestore/common'
2
1
  import { DbSchema, makeSchema } from '@livestore/common/schema'
3
2
  import type { LiveStoreContextRunning } from '@livestore/livestore'
4
3
  import { createStore, globalReactivityGraph, makeReactivityGraph } from '@livestore/livestore'
@@ -32,11 +31,15 @@ export const todos = DbSchema.table(
32
31
  { deriveMutations: true, isSingleton: false },
33
32
  )
34
33
 
35
- export const app = DbSchema.table('app', {
36
- id: DbSchema.text({ primaryKey: true }),
37
- newTodoText: DbSchema.text({ default: '', nullable: true }),
38
- filter: DbSchema.text({ default: 'all', nullable: false }),
39
- })
34
+ export const app = DbSchema.table(
35
+ 'app',
36
+ {
37
+ id: DbSchema.text({ primaryKey: true, default: 'static' }),
38
+ newTodoText: DbSchema.text({ default: '', nullable: true }),
39
+ filter: DbSchema.text({ default: 'all', nullable: false }),
40
+ },
41
+ { isSingleton: true },
42
+ )
40
43
 
41
44
  export const AppComponentSchema = DbSchema.table(
42
45
  'UserInfo',
@@ -92,7 +95,6 @@ export const makeTodoMvcReact = ({
92
95
  const store = yield* createStore({
93
96
  schema,
94
97
  storeId: 'default',
95
- boot: (db) => db.execute(sql`INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');`),
96
98
  adapter: makeInMemoryAdapter(),
97
99
  reactivityGraph,
98
100
  otelOptions: {
@@ -3,7 +3,7 @@ import { computed } from '@livestore/livestore'
3
3
  import React from 'react'
4
4
 
5
5
  import { useQuery } from '../../useQuery.js'
6
- import { useTemporaryQuery } from '../../useTemporaryQuery.js'
6
+ import { useScopedQuery } from '../../useScopedQuery.js'
7
7
 
8
8
  /*
9
9
  TODO:
@@ -33,7 +33,7 @@ export const LiveList = <TItem,>({ items$, renderItem, getKey }: LiveListProps<T
33
33
  React.useEffect(() => setHasMounted(true), [])
34
34
 
35
35
  const keysCb = React.useCallback(() => computed((get) => get(items$).map(getKey)), [getKey, items$])
36
- const keys = useTemporaryQuery(keysCb, 'fixed')
36
+ const keys = useScopedQuery(keysCb, 'fixed')
37
37
  const arr = React.useMemo(
38
38
  () =>
39
39
  keys.map(
package/src/mod.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { LiveStoreContext, useStore } from './LiveStoreContext.js'
2
2
  export { LiveStoreProvider } from './LiveStoreProvider.js'
3
3
  export { useQuery } from './useQuery.js'
4
- export { useTemporaryQuery } from './useTemporaryQuery.js'
4
+ export { useScopedQuery } from './useScopedQuery.js'
5
5
  export { useStackInfo } from './utils/stack-info.js'
6
6
  export {
7
7
  useRow,
@@ -221,7 +221,7 @@ describe('useRow', () => {
221
221
  renderCount.inc()
222
222
 
223
223
  const [_row, _setRow, rowState$] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
224
- const todos = LiveStoreReact.useTemporaryQuery(
224
+ const todos = LiveStoreReact.useScopedQuery(
225
225
  () =>
226
226
  LiveStore.querySQL(
227
227
  (get) => LiveStore.sql`select * from todos where text like '%${get(rowState$).text}%'`,
package/src/useRow.ts CHANGED
@@ -10,7 +10,7 @@ import React from 'react'
10
10
 
11
11
  import { useStore } from './LiveStoreContext.js'
12
12
  import { useQueryRef } from './useQuery.js'
13
- import { useMakeTemporaryQuery } from './useTemporaryQuery.js'
13
+ import { useMakeScopedQuery } from './useScopedQuery.js'
14
14
 
15
15
  export type UseRowResult<TTableDef extends DbSchema.TableDef> = [
16
16
  row: RowResult<TTableDef>,
@@ -96,7 +96,7 @@ export const useRow: {
96
96
 
97
97
  const idStr = id === SessionIdSymbol ? 'session' : id
98
98
 
99
- const { query$, otelContext } = useMakeTemporaryQuery(
99
+ const { query$, otelContext } = useMakeScopedQuery(
100
100
  (otelContext) =>
101
101
  DbSchema.tableIsSingleton(table)
102
102
  ? (rowQuery(table, { otelContext, reactivityGraph }) as LiveQuery<RowResult<TTableDef>, QueryInfo>)
@@ -10,7 +10,7 @@ import { describe, expect, it } from 'vitest'
10
10
  import { makeTodoMvcReact, tables, todos } from './__tests__/fixture.js'
11
11
  import * as LiveStoreReact from './mod.js'
12
12
 
13
- describe('useTemporaryQuery', () => {
13
+ describe('useScopedQuery', () => {
14
14
  it('simple', () =>
15
15
  Effect.gen(function* () {
16
16
  const { wrapper, store, makeRenderCount } = yield* makeTodoMvcReact()
@@ -28,7 +28,7 @@ describe('useTemporaryQuery', () => {
28
28
  (id: string) => {
29
29
  renderCount.inc()
30
30
 
31
- return LiveStoreReact.useTemporaryQuery(() => {
31
+ return LiveStoreReact.useScopedQuery(() => {
32
32
  const query$ = querySQL(`select * from todos where id = '${id}'`, {
33
33
  schema: Schema.Array(tables.todos.schema),
34
34
  })
@@ -80,10 +80,7 @@ describe('useTemporaryQuery', () => {
80
80
 
81
81
  const ListItem: React.FC<{ data: ReadonlyArray<number>; index: number }> = ({ data: ids, index }) => {
82
82
  const id = ids[index]!
83
- const res = LiveStoreReact.useTemporaryQuery(
84
- () => LiveStore.computed(() => id, { label: `ListItem.${id}` }),
85
- id,
86
- )
83
+ const res = LiveStoreReact.useScopedQuery(() => LiveStore.computed(() => id, { label: `ListItem.${id}` }), id)
87
84
  return <div role="listitem">{res}</div>
88
85
  }
89
86
 
@@ -0,0 +1,131 @@
1
+ import type { QueryInfo } from '@livestore/common'
2
+ import type { LiveQuery } from '@livestore/livestore'
3
+ import * as otel from '@opentelemetry/api'
4
+ import React from 'react'
5
+
6
+ import { useStore } from './LiveStoreContext.js'
7
+ import { useQueryRef } from './useQuery.js'
8
+
9
+ // NOTE Given `useMemo` will be called multiple times (e.g. when using React Strict mode or Fast Refresh),
10
+ // we are using this cache to avoid starting multiple queries/spans for the same component.
11
+ // This is somewhat against some recommended React best practices, but it should be fine in our case below.
12
+ // Please definitely open an issue if you see or run into any problems with this approach!
13
+ const cache = new Map<
14
+ string,
15
+ | {
16
+ _tag: 'active'
17
+ rc: number
18
+ query$: LiveQuery<any, any>
19
+ span: otel.Span
20
+ otelContext: otel.Context
21
+ }
22
+ | {
23
+ _tag: 'destroyed'
24
+ }
25
+ >()
26
+
27
+ export type DepKey = string | number | ReadonlyArray<string | number>
28
+
29
+ /**
30
+ * Creates a query, subscribes and destroys it when the component unmounts.
31
+ *
32
+ * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
33
+ */
34
+ export const useScopedQuery = <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey): TResult =>
35
+ useScopedQueryRef(makeQuery, key).current
36
+
37
+ export const useScopedQueryRef = <TResult>(
38
+ makeQuery: () => LiveQuery<TResult>,
39
+ key: DepKey,
40
+ ): React.MutableRefObject<TResult> => {
41
+ const { query$ } = useMakeScopedQuery(makeQuery, key)
42
+
43
+ return useQueryRef(query$)
44
+ }
45
+
46
+ export const useMakeScopedQuery = <TResult, TQueryInfo extends QueryInfo>(
47
+ makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>,
48
+ key: DepKey,
49
+ options?: {
50
+ otel?: {
51
+ spanName?: string
52
+ attributes?: otel.Attributes
53
+ }
54
+ },
55
+ ): { query$: LiveQuery<TResult, TQueryInfo>; otelContext: otel.Context } => {
56
+ const { store } = useStore()
57
+ const fullKey = React.useMemo(
58
+ // NOTE We're using the `makeQuery` function body string to make sure the key is unique across the app
59
+ // TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
60
+ () => (Array.isArray(key) ? key.join('-') : key) + '-' + store.reactivityGraph.id + '-' + makeQuery.toString(),
61
+ [key, makeQuery, store.reactivityGraph.id],
62
+ )
63
+ const fullKeyRef = React.useRef<string>()
64
+
65
+ const { query$, otelContext } = React.useMemo(() => {
66
+ if (fullKeyRef.current !== undefined && fullKeyRef.current !== fullKey) {
67
+ // console.debug('fullKey changed', 'prev', fullKeyRef.current.split('-')[0]!, '-> new', fullKey.split('-')[0]!)
68
+
69
+ const cachedItem = cache.get(fullKeyRef.current)
70
+ if (cachedItem !== undefined && cachedItem._tag === 'active') {
71
+ cachedItem.rc--
72
+
73
+ if (cachedItem.rc === 0) {
74
+ // console.debug('rc=0-changed', cachedItem.query$.id, cachedItem.query$.label)
75
+ cachedItem.query$.destroy()
76
+ cachedItem.span.end()
77
+ cache.set(fullKeyRef.current, { _tag: 'destroyed' })
78
+ }
79
+ }
80
+ }
81
+
82
+ const cachedItem = cache.get(fullKey)
83
+ if (cachedItem !== undefined && cachedItem._tag === 'active') {
84
+ // console.debug('rc++', cachedItem.query$.id, cachedItem.query$.label)
85
+ cachedItem.rc++
86
+
87
+ return cachedItem
88
+ }
89
+
90
+ const spanName = options?.otel?.spanName ?? `LiveStore:useScopedQuery:${key}`
91
+
92
+ const span = store.otel.tracer.startSpan(
93
+ spanName,
94
+ { attributes: options?.otel?.attributes },
95
+ store.otel.queriesSpanContext,
96
+ )
97
+
98
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
99
+
100
+ const query$ = makeQuery(otelContext)
101
+
102
+ cache.set(fullKey, { _tag: 'active', rc: 1, query$, span, otelContext })
103
+
104
+ return { query$, otelContext }
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps
106
+ }, [fullKey])
107
+
108
+ fullKeyRef.current = fullKey
109
+
110
+ React.useEffect(() => {
111
+ return () => {
112
+ const fullKey = fullKeyRef.current!
113
+ const cachedItem = cache.get(fullKey)
114
+ // NOTE in case the fullKey changed then the query was already destroyed in the useMemo above
115
+ if (cachedItem === undefined || cachedItem._tag === 'destroyed') return
116
+
117
+ // console.debug('rc--', cachedItem.query$.id, cachedItem.query$.label)
118
+
119
+ cachedItem.rc--
120
+
121
+ if (cachedItem.rc === 0) {
122
+ // console.debug('rc=0', cachedItem.query$.id, cachedItem.query$.label)
123
+ cachedItem.query$.destroy()
124
+ cachedItem.span.end()
125
+ cache.delete(fullKey)
126
+ }
127
+ }
128
+ }, [])
129
+
130
+ return { query$, otelContext }
131
+ }
@@ -31,19 +31,19 @@ export type DepKey = string | number | ReadonlyArray<string | number>
31
31
  *
32
32
  * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
33
33
  */
34
- export const useTemporaryQuery = <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey): TResult =>
35
- useTemporaryQueryRef(makeQuery, key).current
34
+ export const useScopedQuery = <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey): TResult =>
35
+ useScopedQueryRef(makeQuery, key).current
36
36
 
37
- export const useTemporaryQueryRef = <TResult>(
37
+ export const useScopedQueryRef = <TResult>(
38
38
  makeQuery: () => LiveQuery<TResult>,
39
39
  key: DepKey,
40
40
  ): React.MutableRefObject<TResult> => {
41
- const { query$ } = useMakeTemporaryQuery(makeQuery, key)
41
+ const { query$ } = useMakeScopedQuery(makeQuery, key)
42
42
 
43
43
  return useQueryRef(query$)
44
44
  }
45
45
 
46
- export const useMakeTemporaryQuery = <TResult, TQueryInfo extends QueryInfo>(
46
+ export const useMakeScopedQuery = <TResult, TQueryInfo extends QueryInfo>(
47
47
  makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>,
48
48
  key: DepKey,
49
49
  options?: {
@@ -87,7 +87,7 @@ export const useMakeTemporaryQuery = <TResult, TQueryInfo extends QueryInfo>(
87
87
  return cachedItem
88
88
  }
89
89
 
90
- const spanName = options?.otel?.spanName ?? `LiveStore:useTemporaryQuery:${key}`
90
+ const spanName = options?.otel?.spanName ?? `LiveStore:useScopedQuery:${key}`
91
91
 
92
92
  const span = store.otel.tracer.startSpan(
93
93
  spanName,