diff --git a/libs/isograph-react-disposable-state/src/CacheItem.test.ts b/libs/isograph-disposable-types/src/CacheItem.test.ts similarity index 99% rename from libs/isograph-react-disposable-state/src/CacheItem.test.ts rename to libs/isograph-disposable-types/src/CacheItem.test.ts index aa155f3cd..11b919b1f 100644 --- a/libs/isograph-react-disposable-state/src/CacheItem.test.ts +++ b/libs/isograph-disposable-types/src/CacheItem.test.ts @@ -1,4 +1,3 @@ -import { ItemCleanupPair } from '@isograph/disposable-types'; import { afterEach, assert, @@ -8,6 +7,7 @@ import { test, vi, } from 'vitest'; +import { ItemCleanupPair } from '.'; import { CacheItem, CacheItemState, diff --git a/libs/isograph-react-disposable-state/src/CacheItem.ts b/libs/isograph-disposable-types/src/CacheItem.ts similarity index 99% rename from libs/isograph-react-disposable-state/src/CacheItem.ts rename to libs/isograph-disposable-types/src/CacheItem.ts index dce7dd95d..9626a5ad1 100644 --- a/libs/isograph-react-disposable-state/src/CacheItem.ts +++ b/libs/isograph-disposable-types/src/CacheItem.ts @@ -1,8 +1,4 @@ -import { - CleanupFn, - Factory, - ItemCleanupPair, -} from '@isograph/disposable-types'; +import { CleanupFn, Factory, ItemCleanupPair } from '.'; const DEFAULT_TEMPORARY_RETAIN_TIME = 5000; diff --git a/libs/isograph-react-disposable-state/src/ParentCache.test.ts b/libs/isograph-disposable-types/src/ParentCache.test.ts similarity index 97% rename from libs/isograph-react-disposable-state/src/ParentCache.test.ts rename to libs/isograph-disposable-types/src/ParentCache.test.ts index d7e617457..82a797135 100644 --- a/libs/isograph-react-disposable-state/src/ParentCache.test.ts +++ b/libs/isograph-disposable-types/src/ParentCache.test.ts @@ -1,5 +1,5 @@ -import { ItemCleanupPair } from '@isograph/disposable-types'; import { assert, describe, expect, test, vi } from 'vitest'; +import { ItemCleanupPair } from '.'; import { CacheItem } from './CacheItem'; import { ParentCache } from './ParentCache'; diff --git a/libs/isograph-react-disposable-state/src/ParentCache.ts b/libs/isograph-disposable-types/src/ParentCache.ts similarity index 97% rename from libs/isograph-react-disposable-state/src/ParentCache.ts rename to libs/isograph-disposable-types/src/ParentCache.ts index 1467e4cae..763d535af 100644 --- a/libs/isograph-react-disposable-state/src/ParentCache.ts +++ b/libs/isograph-disposable-types/src/ParentCache.ts @@ -1,8 +1,4 @@ -import { - CleanupFn, - Factory, - ItemCleanupPair, -} from '@isograph/disposable-types'; +import { CleanupFn, Factory, ItemCleanupPair } from '.'; import { CacheItem, createTemporarilyRetainedCacheItem } from './CacheItem'; // TODO convert cache impl to a getter and setter and free functions diff --git a/libs/isograph-disposable-types/src/index.ts b/libs/isograph-disposable-types/src/index.ts index ec8bd3092..73ccfd15d 100644 --- a/libs/isograph-disposable-types/src/index.ts +++ b/libs/isograph-disposable-types/src/index.ts @@ -1,3 +1,6 @@ export type CleanupFn = () => void; export type ItemCleanupPair = [T, CleanupFn]; export type Factory = () => ItemCleanupPair; + +export * from './CacheItem'; +export * from './ParentCache'; diff --git a/libs/isograph-react-disposable-state/src/index.ts b/libs/isograph-react-disposable-state/src/index.ts index 704983393..1d9791522 100644 --- a/libs/isograph-react-disposable-state/src/index.ts +++ b/libs/isograph-react-disposable-state/src/index.ts @@ -1,7 +1,5 @@ export * from '@isograph/disposable-types'; -export * from './CacheItem'; -export * from './ParentCache'; export * from './useCachedResponsivePrecommitValue'; export * from './useDisposableState'; export * from './useHasCommittedRef'; diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx index 96e758e6a..4ecaa4026 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx @@ -1,9 +1,12 @@ -import { ItemCleanupPair } from '@isograph/disposable-types'; +import { + CacheItem, + CacheItemState, + ItemCleanupPair, + ParentCache, +} from '@isograph/disposable-types'; import React from 'react'; import { create } from 'react-test-renderer'; import { assert, describe, expect, test, vi } from 'vitest'; -import { CacheItem, CacheItemState } from './CacheItem'; -import { ParentCache } from './ParentCache'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; function getItem(cache: ParentCache): CacheItem | null { diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts index 23585328a..e6d97a9eb 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts @@ -1,8 +1,7 @@ 'use strict'; -import { ItemCleanupPair } from '@isograph/disposable-types'; +import { ItemCleanupPair, ParentCache } from '@isograph/disposable-types'; import { useEffect, useRef, useState } from 'react'; -import { ParentCache } from './ParentCache'; /** * useCachedResponsivePrecommitValue diff --git a/libs/isograph-react-disposable-state/src/useDisposableState.ts b/libs/isograph-react-disposable-state/src/useDisposableState.ts index 92c1e57cf..1f5bb668b 100644 --- a/libs/isograph-react-disposable-state/src/useDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useDisposableState.ts @@ -1,6 +1,5 @@ -import { ItemCleanupPair } from '@isograph/disposable-types'; +import { ItemCleanupPair, ParentCache } from '@isograph/disposable-types'; import { useEffect, useRef } from 'react'; -import { ParentCache } from './ParentCache'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; import { UNASSIGNED_STATE, diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx b/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx index bdeb73818..1c04bc973 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx @@ -1,8 +1,7 @@ -import { ItemCleanupPair } from '@isograph/disposable-types'; +import { ItemCleanupPair, ParentCache } from '@isograph/disposable-types'; import React, { useEffect, useState } from 'react'; import { create } from 'react-test-renderer'; import { describe, expect, test, vi } from 'vitest'; -import { ParentCache } from './ParentCache'; import { useLazyDisposableState } from './useLazyDisposableState'; function createCache(value: T) { diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index f0ffa18ce..b51e92393 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -1,8 +1,10 @@ 'use strict'; -import type { ItemCleanupPair } from '@isograph/isograph-disposable-types'; +import { + ParentCache, + type ItemCleanupPair, +} from '@isograph/isograph-disposable-types'; import { useEffect, useRef } from 'react'; -import { ParentCache } from './ParentCache'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; import { type UnassignedState } from './useUpdatableDisposableState'; diff --git a/libs/isograph-react/src/core/FragmentReference.ts b/libs/isograph-react/src/core/FragmentReference.ts index 4d306e336..8a25a32d3 100644 --- a/libs/isograph-react/src/core/FragmentReference.ts +++ b/libs/isograph-react/src/core/FragmentReference.ts @@ -1,5 +1,5 @@ -import { ReaderWithRefetchQueries } from '../core/entrypoint'; import { stableCopy } from './cache'; +import { ReaderWithRefetchQueries } from './entrypoint'; import { type Link } from './IsographEnvironment'; import { PromiseWrapper } from './PromiseWrapper'; import type { StartUpdate } from './reader'; diff --git a/libs/isograph-react/src/core/IsographEnvironment.ts b/libs/isograph-react/src/core/IsographEnvironment.ts index 0f2b2199d..54d9676ca 100644 --- a/libs/isograph-react/src/core/IsographEnvironment.ts +++ b/libs/isograph-react/src/core/IsographEnvironment.ts @@ -1,6 +1,7 @@ -import { ParentCache } from '@isograph/react-disposable-state'; +import { ParentCache } from '@isograph/disposable-types'; import { IsographEntrypoint } from './entrypoint'; import { + ExtractStartUpdate, FragmentReference, Variables, type StableIdForFragmentReference, @@ -9,7 +10,7 @@ import { import { RetainedQuery } from './garbageCollection'; import { LogFunction, WrappedLogFunction } from './logging'; import { PromiseWrapper, wrapPromise } from './PromiseWrapper'; -import { WithEncounteredRecords } from './read'; +import { NetworkRequestReaderOptions, WithEncounteredRecords } from './read'; import type { ReaderAst, StartUpdate } from './reader'; export type ComponentOrFieldName = string; @@ -50,11 +51,12 @@ export type Subscriptions = Set; // Should this be a map? export type CacheMap = { [index: string]: ParentCache }; -export type IsographEnvironment = { +export type IsographEnvironment = { readonly store: IsographStore; readonly networkFunction: IsographNetworkFunction; + readonly componentFunction: IsographComponentFunction; readonly missingFieldHandler: MissingFieldHandler | null; - readonly componentCache: FieldCache>; + readonly componentCache: FieldCache; readonly eagerReaderCache: FieldCache | undefined>; readonly subscriptions: Subscriptions; // N.B. this must be , but all *usages* of this should go through @@ -84,6 +86,17 @@ export type IsographNetworkFunction = ( variables: Variables, ) => Promise; +export type IsographComponentFunction< + TComponent, + TReadFromStore extends UnknownTReadFromStore = any, +> = ( + environment: IsographEnvironment, + componentName: string, + fragmentReference: FragmentReference, + networkRequestOptions: NetworkRequestReaderOptions, + startUpdate: ExtractStartUpdate, +) => TComponent; + export type Link = { readonly __link: DataId; readonly __typename: TypeName; @@ -125,18 +138,20 @@ export type IsographStore = { }; const DEFAULT_GC_BUFFER_SIZE = 10; -export function createIsographEnvironment( +export function createIsographEnvironment( store: IsographStore, networkFunction: IsographNetworkFunction, + componentFunction: IsographComponentFunction, missingFieldHandler?: MissingFieldHandler | null, logFunction?: LogFunction | null, -): IsographEnvironment { +): IsographEnvironment { logFunction?.({ kind: 'EnvironmentCreated', }); return { store, networkFunction, + componentFunction, missingFieldHandler: missingFieldHandler ?? null, componentCache: {}, eagerReaderCache: {}, diff --git a/libs/isograph-react/src/core/cache.ts b/libs/isograph-react/src/core/cache.ts index a88099cb7..290cb0210 100644 --- a/libs/isograph-react/src/core/cache.ts +++ b/libs/isograph-react/src/core/cache.ts @@ -2,7 +2,9 @@ import { Factory, ItemCleanupPair, ParentCache, -} from '@isograph/react-disposable-state'; +} from '@isograph/disposable-types'; +import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison'; +import { FetchOptions } from './check'; import { IsographEntrypoint, NormalizationInlineFragment, @@ -12,9 +14,7 @@ import { type NormalizationAst, type NormalizationAstLoader, type NormalizationAstNodes, -} from '../core/entrypoint'; -import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison'; -import { FetchOptions } from './check'; +} from './entrypoint'; import { ExtractParameters, FragmentReference, diff --git a/libs/isograph-react/src/core/componentCache.ts b/libs/isograph-react/src/core/componentCache.ts index 7669def2e..99166395c 100644 --- a/libs/isograph-react/src/core/componentCache.ts +++ b/libs/isograph-react/src/core/componentCache.ts @@ -1,55 +1,27 @@ -import { useReadAndSubscribe } from '../react/useReadAndSubscribe'; import { FragmentReference, stableIdForFragmentReference, } from './FragmentReference'; import { IsographEnvironment } from './IsographEnvironment'; -import { logMessage } from './logging'; -import { readPromise } from './PromiseWrapper'; import { NetworkRequestReaderOptions } from './read'; import { createStartUpdate } from './startUpdate'; -export function getOrCreateCachedComponent( - environment: IsographEnvironment, +export function getOrCreateCachedComponent( + environment: IsographEnvironment, componentName: string, fragmentReference: FragmentReference, networkRequestOptions: NetworkRequestReaderOptions, -): React.FC { +): TComponent { // We create startUpdate outside of component to make it stable const startUpdate = createStartUpdate(environment, fragmentReference); return (environment.componentCache[ stableIdForFragmentReference(fragmentReference, componentName) - ] ??= (() => { - function Component(additionalRuntimeProps: { [key: string]: any }) { - const readerWithRefetchQueries = readPromise( - fragmentReference.readerWithRefetchQueries, - ); - - const data = useReadAndSubscribe( - fragmentReference, - networkRequestOptions, - readerWithRefetchQueries.readerArtifact.readerAst, - ); - - logMessage(environment, () => ({ - kind: 'ComponentRerendered', - componentName, - rootLink: fragmentReference.root, - })); - - return readerWithRefetchQueries.readerArtifact.resolver( - { - data, - parameters: fragmentReference.variables, - startUpdate: readerWithRefetchQueries.readerArtifact.hasUpdatable - ? startUpdate - : undefined, - }, - additionalRuntimeProps, - ); - } - Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`; - return Component; - })()); + ] ??= environment.componentFunction( + environment, + componentName, + fragmentReference, + networkRequestOptions, + startUpdate, + )); } diff --git a/libs/isograph-react/src/index.ts b/libs/isograph-react/src/index.ts index 3e879d1f4..cd581504d 100644 --- a/libs/isograph-react/src/index.ts +++ b/libs/isograph-react/src/index.ts @@ -30,14 +30,14 @@ export { ROOT_ID, type DataId, type DataTypeValue, - type IsographEnvironment, + type IsographEnvironment as RawIsographEnvironment, type IsographNetworkFunction, type IsographStore, type MissingFieldHandler, type Link, type StoreRecord, type CacheMap, - createIsographEnvironment, + createIsographEnvironment as createRawIsographEnvironment, createIsographStore, type FieldCache, type Subscriptions, @@ -127,6 +127,10 @@ export { type RequiredShouldFetch, } from './core/check'; +export { + IsographReactEnvironment as IsographEnvironment, + createIsographReactEnvironment as createIsographEnvironment, +} from './react/IsographReactEnvironment'; export { IsographEnvironmentProvider, useIsographEnvironment, diff --git a/libs/isograph-react/src/react/IsographEnvironmentProvider.tsx b/libs/isograph-react/src/react/IsographEnvironmentProvider.tsx index dadacf209..8d7dff8c9 100644 --- a/libs/isograph-react/src/react/IsographEnvironmentProvider.tsx +++ b/libs/isograph-react/src/react/IsographEnvironmentProvider.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import { createContext, ReactNode, useContext } from 'react'; -import { type IsographEnvironment } from '../core/IsographEnvironment'; +import { IsographReactEnvironment } from './IsographReactEnvironment'; export const IsographEnvironmentContext = - createContext(null); + createContext(null); export type IsographEnvironmentProviderProps = { - readonly environment: IsographEnvironment; + readonly environment: IsographReactEnvironment; readonly children: ReactNode; }; @@ -21,7 +21,7 @@ export function IsographEnvironmentProvider({ ); } -export function useIsographEnvironment(): IsographEnvironment { +export function useIsographEnvironment(): IsographReactEnvironment { const context = useContext(IsographEnvironmentContext); if (context == null) { throw new Error( diff --git a/libs/isograph-react/src/react/IsographReactEnvironment.ts b/libs/isograph-react/src/react/IsographReactEnvironment.ts new file mode 100644 index 000000000..a2fa744f6 --- /dev/null +++ b/libs/isograph-react/src/react/IsographReactEnvironment.ts @@ -0,0 +1,67 @@ +import { + createIsographEnvironment, + IsographComponentFunction, + IsographEnvironment, + IsographNetworkFunction, + IsographStore, + MissingFieldHandler, +} from '../core/IsographEnvironment'; +import { LogFunction, logMessage } from '../core/logging'; +import { readPromise } from '../core/PromiseWrapper'; +import { useReadAndSubscribe } from './useReadAndSubscribe'; + +export type IsographReactEnvironment = IsographEnvironment>; + +export function createIsographReactEnvironment( + store: IsographStore, + networkFunction: IsographNetworkFunction, + missingFieldHandler?: MissingFieldHandler | null, + logFunction?: LogFunction | null, +): IsographReactEnvironment { + return createIsographEnvironment( + store, + networkFunction, + componentFunction, + missingFieldHandler, + logFunction, + ); +} + +const componentFunction: IsographComponentFunction> = ( + environment, + componentName, + fragmentReference, + networkRequestOptions, + startUpdate, +) => { + function Component(additionalRuntimeProps: { [key: string]: any }) { + const readerWithRefetchQueries = readPromise( + fragmentReference.readerWithRefetchQueries, + ); + + const data = useReadAndSubscribe( + fragmentReference, + networkRequestOptions, + readerWithRefetchQueries.readerArtifact.readerAst, + ); + + logMessage(environment, () => ({ + kind: 'ComponentRerendered', + componentName, + rootLink: fragmentReference.root, + })); + + return readerWithRefetchQueries.readerArtifact.resolver( + { + data, + parameters: fragmentReference.variables, + startUpdate: readerWithRefetchQueries.readerArtifact.hasUpdatable + ? startUpdate + : undefined, + }, + additionalRuntimeProps, + ); + } + Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`; + return Component; +};