@isograph/react 0.0.0-main-945e49cc → 0.0.0-main-1bfd646b

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.
@@ -1,9 +1,5 @@
1
- import { ReactNode, createContext, useContext } from 'react';
2
- import * as React from 'react';
3
1
  import { ParentCache } from '@isograph/isograph-react-disposable-state';
4
-
5
- export const IsographEnvironmentContext =
6
- createContext<IsographEnvironment | null>(null);
2
+ import { RetainedQuery } from './garbageCollection';
7
3
 
8
4
  type ComponentName = string;
9
5
  type StringifiedArgs = string;
@@ -23,6 +19,9 @@ export type IsographEnvironment = {
23
19
  componentCache: ComponentCache;
24
20
  subscriptions: Subscriptions;
25
21
  suspenseCache: SuspenseCache;
22
+ retainedQueries: Set<RetainedQuery>;
23
+ gcBuffer: Array<RetainedQuery>;
24
+ gcBufferSize: number;
26
25
  };
27
26
 
28
27
  export type MissingFieldHandler = (
@@ -41,6 +40,7 @@ export type IsographNetworkFunction = (
41
40
  export type Link = {
42
41
  __link: DataId;
43
42
  };
43
+
44
44
  export type DataTypeValue =
45
45
  // N.B. undefined is here to support optional id's, but
46
46
  // undefined should not *actually* be present in the store.
@@ -71,33 +71,7 @@ export type IsographStore = {
71
71
  __ROOT: StoreRecord;
72
72
  };
73
73
 
74
- export type IsographEnvironmentProviderProps = {
75
- environment: IsographEnvironment;
76
- children: ReactNode;
77
- };
78
-
79
- export function IsographEnvironmentProvider({
80
- environment,
81
- children,
82
- }: IsographEnvironmentProviderProps) {
83
- return (
84
- <IsographEnvironmentContext.Provider value={environment}>
85
- {children}
86
- </IsographEnvironmentContext.Provider>
87
- );
88
- }
89
-
90
- export function useIsographEnvironment(): IsographEnvironment {
91
- const context = useContext(IsographEnvironmentContext);
92
- if (context == null) {
93
- throw new Error(
94
- 'Unexpected null environment context. Make sure to render ' +
95
- 'this component within an IsographEnvironmentProvider component',
96
- );
97
- }
98
- return context;
99
- }
100
-
74
+ const DEFAULT_GC_BUFFER_SIZE = 10;
101
75
  export function createIsographEnvironment(
102
76
  store: IsographStore,
103
77
  networkFunction: IsographNetworkFunction,
@@ -110,6 +84,9 @@ export function createIsographEnvironment(
110
84
  componentCache: {},
111
85
  subscriptions: new Set(),
112
86
  suspenseCache: {},
87
+ retainedQueries: new Set(),
88
+ gcBuffer: [],
89
+ gcBufferSize: DEFAULT_GC_BUFFER_SIZE,
113
90
  };
114
91
  }
115
92
 
@@ -0,0 +1,33 @@
1
+ import * as React from 'react';
2
+ import { ReactNode, createContext, useContext } from 'react';
3
+ import { type IsographEnvironment } from './IsographEnvironment';
4
+
5
+ export const IsographEnvironmentContext =
6
+ createContext<IsographEnvironment | null>(null);
7
+
8
+ export type IsographEnvironmentProviderProps = {
9
+ environment: IsographEnvironment;
10
+ children: ReactNode;
11
+ };
12
+
13
+ export function IsographEnvironmentProvider({
14
+ environment,
15
+ children,
16
+ }: IsographEnvironmentProviderProps) {
17
+ return (
18
+ <IsographEnvironmentContext.Provider value={environment}>
19
+ {children}
20
+ </IsographEnvironmentContext.Provider>
21
+ );
22
+ }
23
+
24
+ export function useIsographEnvironment(): IsographEnvironment {
25
+ const context = useContext(IsographEnvironmentContext);
26
+ if (context == null) {
27
+ throw new Error(
28
+ 'Unexpected null environment context. Make sure to render ' +
29
+ 'this component within an IsographEnvironmentProvider component',
30
+ );
31
+ }
32
+ return context;
33
+ }
package/src/cache.tsx CHANGED
@@ -22,6 +22,12 @@ import {
22
22
  Link,
23
23
  type IsographEnvironment,
24
24
  } from './IsographEnvironment';
25
+ import {
26
+ RetainedQuery,
27
+ garbageCollectEnvironment,
28
+ retainQuery,
29
+ unretainQuery,
30
+ } from './garbageCollection';
25
31
 
26
32
  declare global {
27
33
  interface Window {
@@ -83,6 +89,18 @@ export function getOrCreateCacheForArtifact<T>(
83
89
  return getOrCreateCache<PromiseWrapper<T>>(environment, cacheKey, factory);
84
90
  }
85
91
 
92
+ type NetworkRequestStatus =
93
+ | {
94
+ kind: 'UndisposedIncomplete';
95
+ }
96
+ | {
97
+ kind: 'Disposed';
98
+ }
99
+ | {
100
+ kind: 'UndisposedComplete';
101
+ retainedQuery: RetainedQuery;
102
+ };
103
+
86
104
  export function makeNetworkRequest<T>(
87
105
  environment: IsographEnvironment,
88
106
  artifact: IsoResolver,
@@ -91,20 +109,37 @@ export function makeNetworkRequest<T>(
91
109
  if (typeof window !== 'undefined' && window.__LOG) {
92
110
  console.log('make network request', artifact, variables);
93
111
  }
112
+ let status: NetworkRequestStatus = {
113
+ kind: 'UndisposedIncomplete',
114
+ };
115
+ // This should be an observable, not a promise
94
116
  const promise = environment
95
117
  .networkFunction(artifact.queryText, variables)
96
118
  .then((networkResponse) => {
97
119
  if (typeof window !== 'undefined' && window.__LOG) {
98
120
  console.log('network response', artifact, artifact);
99
121
  }
100
- normalizeData(
101
- environment,
102
- artifact.normalizationAst,
103
- networkResponse.data,
104
- variables,
105
- artifact.nestedRefetchQueries,
106
- );
107
- return networkResponse.data;
122
+
123
+ if (status.kind === 'UndisposedIncomplete') {
124
+ normalizeData(
125
+ environment,
126
+ artifact.normalizationAst,
127
+ networkResponse.data,
128
+ variables,
129
+ artifact.nestedRefetchQueries,
130
+ );
131
+ const retainedQuery = {
132
+ normalizationAst: artifact.normalizationAst,
133
+ variables,
134
+ };
135
+ status = {
136
+ kind: 'UndisposedComplete',
137
+ retainedQuery,
138
+ };
139
+ retainQuery(environment, retainedQuery);
140
+ }
141
+ // TODO return null
142
+ return networkResponse;
108
143
  });
109
144
 
110
145
  const wrapper = wrapPromise(promise);
@@ -112,7 +147,18 @@ export function makeNetworkRequest<T>(
112
147
  const response: ItemCleanupPair<PromiseWrapper<T>> = [
113
148
  wrapper,
114
149
  () => {
115
- // delete from cache
150
+ if (status.kind === 'UndisposedComplete') {
151
+ const didUnretainSomeQuery = unretainQuery(
152
+ environment,
153
+ status.retainedQuery,
154
+ );
155
+ if (didUnretainSomeQuery) {
156
+ garbageCollectEnvironment(environment);
157
+ }
158
+ }
159
+ status = {
160
+ kind: 'Disposed',
161
+ };
116
162
  },
117
163
  ];
118
164
  return response;
@@ -138,7 +184,9 @@ function normalizeData(
138
184
  networkResponse: NetworkResponseObject,
139
185
  variables: Object,
140
186
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
141
- ) {
187
+ ): Set<DataId> {
188
+ const encounteredIds = new Set<DataId>();
189
+
142
190
  if (typeof window !== 'undefined' && window.__LOG) {
143
191
  console.log(
144
192
  'about to normalize',
@@ -155,11 +203,13 @@ function normalizeData(
155
203
  ROOT_ID,
156
204
  variables as any,
157
205
  nestedRefetchQueries,
206
+ encounteredIds,
158
207
  );
159
208
  if (typeof window !== 'undefined' && window.__LOG) {
160
209
  console.log('after normalization', { store: environment.store });
161
210
  }
162
211
  callSubscriptions(environment);
212
+ return encounteredIds;
163
213
  }
164
214
 
165
215
  export function subscribe(
@@ -194,7 +244,9 @@ function normalizeDataIntoRecord(
194
244
  targetParentRecordId: DataId,
195
245
  variables: { [index: string]: string },
196
246
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
247
+ mutableEncounteredIds: Set<DataId>,
197
248
  ) {
249
+ mutableEncounteredIds.add(targetParentRecordId);
198
250
  for (const normalizationNode of normalizationAst) {
199
251
  switch (normalizationNode.kind) {
200
252
  case 'Scalar': {
@@ -215,6 +267,7 @@ function normalizeDataIntoRecord(
215
267
  targetParentRecordId,
216
268
  variables,
217
269
  nestedRefetchQueries,
270
+ mutableEncounteredIds,
218
271
  );
219
272
  break;
220
273
  }
@@ -253,6 +306,7 @@ function normalizeLinkedField(
253
306
  targetParentRecordId: DataId,
254
307
  variables: { [index: string]: string },
255
308
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
309
+ mutableEncounteredIds: Set<DataId>,
256
310
  ) {
257
311
  const networkResponseKey = getNetworkResponseKey(astNode);
258
312
  const networkResponseData = networkResponseParentRecord[networkResponseKey];
@@ -282,7 +336,9 @@ function normalizeLinkedField(
282
336
  variables,
283
337
  i,
284
338
  nestedRefetchQueries,
339
+ mutableEncounteredIds,
285
340
  );
341
+
286
342
  dataIds.push({ __link: newStoreRecordId });
287
343
  }
288
344
  targetParentRecord[parentRecordKey] = dataIds;
@@ -295,6 +351,7 @@ function normalizeLinkedField(
295
351
  variables,
296
352
  null,
297
353
  nestedRefetchQueries,
354
+ mutableEncounteredIds,
298
355
  );
299
356
  targetParentRecord[parentRecordKey] = {
300
357
  __link: newStoreRecordId,
@@ -310,6 +367,7 @@ function normalizeNetworkResponseObject(
310
367
  variables: { [index: string]: string },
311
368
  index: number | null,
312
369
  nestedRefetchQueries: RefetchQueryArtifactWrapper[],
370
+ mutableEncounteredIds: Set<DataId>,
313
371
  ): DataId /* The id of the modified or newly created item */ {
314
372
  const newStoreRecordId = getDataIdOfNetworkResponse(
315
373
  targetParentRecordId,
@@ -330,6 +388,7 @@ function normalizeNetworkResponseObject(
330
388
  newStoreRecordId,
331
389
  variables,
332
390
  nestedRefetchQueries,
391
+ mutableEncounteredIds,
333
392
  );
334
393
 
335
394
  return newStoreRecordId;
@@ -0,0 +1,59 @@
1
+ import { Arguments } from './index';
2
+ import { ReaderArtifact } from './reader';
3
+
4
+ // This type should be treated as an opaque type.
5
+ export type IsographEntrypoint<
6
+ TReadFromStore extends Object,
7
+ TResolverResult,
8
+ > = {
9
+ kind: 'Entrypoint';
10
+ queryText: string;
11
+ normalizationAst: NormalizationAst;
12
+ readerArtifact: ReaderArtifact<TReadFromStore, TResolverResult>;
13
+ nestedRefetchQueries: RefetchQueryArtifactWrapper[];
14
+ };
15
+
16
+ export type NormalizationAstNode =
17
+ | NormalizationScalarField
18
+ | NormalizationLinkedField;
19
+ export type NormalizationAst = NormalizationAstNode[];
20
+
21
+ export type NormalizationScalarField = {
22
+ kind: 'Scalar';
23
+ fieldName: string;
24
+ arguments: Arguments | null;
25
+ };
26
+
27
+ export type NormalizationLinkedField = {
28
+ kind: 'Linked';
29
+ fieldName: string;
30
+ arguments: Arguments | null;
31
+ selections: NormalizationAst;
32
+ };
33
+
34
+ // This is more like an entrypoint, but one specifically for a refetch query/mutation
35
+ export type RefetchQueryArtifact = {
36
+ kind: 'RefetchQuery';
37
+ queryText: string;
38
+ normalizationAst: NormalizationAst;
39
+ };
40
+
41
+ // TODO rename
42
+ export type RefetchQueryArtifactWrapper = {
43
+ artifact: RefetchQueryArtifact;
44
+ allowedVariables: string[];
45
+ };
46
+
47
+ export function assertIsEntrypoint<
48
+ TReadFromStore extends Object,
49
+ TResolverResult,
50
+ >(
51
+ value:
52
+ | IsographEntrypoint<TReadFromStore, TResolverResult>
53
+ | ((_: any) => any)
54
+ // Temporarily, allow any here. Once we automatically provide
55
+ // types to entrypoints, we probably don't need this.
56
+ | any,
57
+ ): asserts value is IsographEntrypoint<TReadFromStore, TResolverResult> {
58
+ if (typeof value === 'function') throw new Error('Not a string');
59
+ }
@@ -0,0 +1,131 @@
1
+ import {
2
+ DataId,
3
+ DataTypeValue,
4
+ IsographEnvironment,
5
+ IsographStore,
6
+ ROOT_ID,
7
+ StoreRecord,
8
+ } from './IsographEnvironment';
9
+ import { NormalizationAst } from './index';
10
+ import { getParentRecordKey } from './cache';
11
+
12
+ export type RetainedQuery = {
13
+ normalizationAst: NormalizationAst;
14
+ variables: {};
15
+ };
16
+
17
+ type DidUnretainSomeQuery = boolean;
18
+ export function unretainQuery(
19
+ environment: IsographEnvironment,
20
+ retainedQuery: RetainedQuery,
21
+ ): DidUnretainSomeQuery {
22
+ environment.retainedQueries.delete(retainedQuery);
23
+ environment.gcBuffer.push(retainedQuery);
24
+
25
+ if (environment.gcBuffer.length > environment.gcBufferSize) {
26
+ environment.gcBuffer.shift();
27
+ return true;
28
+ }
29
+
30
+ return false;
31
+ }
32
+
33
+ export function retainQuery(
34
+ environment: IsographEnvironment,
35
+ queryToRetain: RetainedQuery,
36
+ ) {
37
+ environment.retainedQueries.add(queryToRetain);
38
+ // TODO can we remove this query from the buffer somehow?
39
+ // We are relying on === equality, but we really should be comparing
40
+ // id + variables
41
+ }
42
+
43
+ export function garbageCollectEnvironment(environment: IsographEnvironment) {
44
+ const retainedIds = new Set<DataId>([ROOT_ID]);
45
+
46
+ for (const query of environment.retainedQueries) {
47
+ recordReachableIds(environment.store, query, retainedIds);
48
+ }
49
+ for (const query of environment.gcBuffer) {
50
+ recordReachableIds(environment.store, query, retainedIds);
51
+ }
52
+
53
+ for (const dataId in environment.store) {
54
+ if (!retainedIds.has(dataId)) {
55
+ delete environment.store[dataId];
56
+ }
57
+ }
58
+ }
59
+
60
+ function recordReachableIds(
61
+ store: IsographStore,
62
+ retainedQuery: RetainedQuery,
63
+ mutableRetainedIds: Set<DataId>,
64
+ ) {
65
+ recordReachableIdsFromRecord(
66
+ store,
67
+ store[ROOT_ID],
68
+ mutableRetainedIds,
69
+ retainedQuery.normalizationAst,
70
+ retainedQuery.variables,
71
+ );
72
+ }
73
+
74
+ function getLinkedId(data: Exclude<DataTypeValue, null | void>): string {
75
+ // @ts-expect-error
76
+ if (data.__link != null) {
77
+ // @ts-expect-error
78
+ return data.__link;
79
+ } else {
80
+ throw new Error('Record in an invalid state');
81
+ }
82
+ }
83
+
84
+ function recordReachableIdsFromRecord(
85
+ store: IsographStore,
86
+ currentRecord: StoreRecord,
87
+ mutableRetainedIds: Set<DataId>,
88
+ selections: NormalizationAst,
89
+ variables: { [index: string]: string } | null,
90
+ ) {
91
+ for (const selection of selections) {
92
+ switch (selection.kind) {
93
+ case 'Linked':
94
+ const linkKey = getParentRecordKey(selection, variables ?? {});
95
+ const linkedFieldOrFields = currentRecord[linkKey];
96
+
97
+ const ids = [];
98
+ if (Array.isArray(linkedFieldOrFields)) {
99
+ for (const link of linkedFieldOrFields) {
100
+ if (link != null) {
101
+ const id = getLinkedId(link);
102
+ ids.push(id);
103
+ }
104
+ }
105
+ } else {
106
+ if (linkedFieldOrFields != null) {
107
+ const id = getLinkedId(linkedFieldOrFields);
108
+ ids.push(id);
109
+ }
110
+ }
111
+
112
+ for (const nextRecordId of ids) {
113
+ const nextRecord = store[nextRecordId];
114
+ if (nextRecord != null) {
115
+ mutableRetainedIds.add(nextRecordId);
116
+ recordReachableIdsFromRecord(
117
+ store,
118
+ nextRecord,
119
+ mutableRetainedIds,
120
+ selection.selections,
121
+ variables,
122
+ );
123
+ }
124
+ }
125
+
126
+ continue;
127
+ case 'Scalar':
128
+ continue;
129
+ }
130
+ }
131
+ }
package/src/index.tsx CHANGED
@@ -14,141 +14,68 @@ import {
14
14
  Link,
15
15
  ROOT_ID,
16
16
  StoreRecord,
17
- useIsographEnvironment,
18
17
  } from './IsographEnvironment';
19
18
  import { useEffect, useState } from 'react';
19
+ import { useIsographEnvironment } from './IsographEnvironmentProvider';
20
+ import { ReaderArtifact, ReaderAst } from './reader';
21
+ import {
22
+ IsographEntrypoint,
23
+ RefetchQueryArtifactWrapper,
24
+ assertIsEntrypoint,
25
+ } from './entrypoint';
20
26
 
27
+ export {
28
+ retainQuery,
29
+ unretainQuery,
30
+ type RetainedQuery,
31
+ garbageCollectEnvironment,
32
+ } from './garbageCollection';
21
33
  export { type PromiseWrapper } from './PromiseWrapper';
22
34
  export { makeNetworkRequest, subscribe } from './cache';
23
35
  export {
24
- IsographEnvironmentContext,
25
36
  ROOT_ID,
26
37
  type DataId,
27
38
  type DataTypeValue,
28
39
  type IsographEnvironment,
29
- IsographEnvironmentProvider,
30
- type IsographEnvironmentProviderProps,
31
40
  type IsographNetworkFunction,
32
41
  type IsographStore,
33
42
  type Link,
34
43
  type StoreRecord,
35
- useIsographEnvironment,
36
44
  createIsographEnvironment,
37
45
  createIsographStore,
38
46
  } from './IsographEnvironment';
47
+ export {
48
+ IsographEnvironmentProvider,
49
+ useIsographEnvironment,
50
+ type IsographEnvironmentProviderProps,
51
+ } from './IsographEnvironmentProvider';
39
52
  export { useImperativeReference } from './useImperativeReference';
40
53
  export { EntrypointReader } from './EntrypointReader';
41
-
42
- // This type should be treated as an opaque type.
43
- export type IsographEntrypoint<
44
- TReadFromStore extends Object,
45
- TResolverResult,
46
- > = {
47
- kind: 'Entrypoint';
48
- queryText: string;
49
- normalizationAst: NormalizationAst;
50
- readerArtifact: ReaderArtifact<TReadFromStore, TResolverResult>;
51
- nestedRefetchQueries: RefetchQueryArtifactWrapper[];
52
- };
53
-
54
- // TODO this should probably be at least three distinct types, for @component,
55
- // non-@component and refetch resolvers
56
- export type ReaderArtifact<TReadFromStore extends Object, TResolverResult> = {
57
- kind: 'ReaderArtifact';
58
- readerAst: ReaderAst<TReadFromStore>;
59
- resolver: (data: TReadFromStore, runtimeProps: any) => TResolverResult;
60
- variant: ReaderResolverVariant;
61
- };
62
-
63
- export type ReaderAstNode =
64
- | ReaderScalarField
65
- | ReaderLinkedField
66
- | ReaderResolverField
67
- | ReaderRefetchField
68
- | ReaderMutationField;
69
-
70
- // @ts-ignore
71
- export type ReaderAst<TReadFromStore> = ReaderAstNode[];
54
+ export {
55
+ type ReaderArtifact,
56
+ ReaderAst,
57
+ ReaderAstNode,
58
+ ReaderLinkedField,
59
+ ReaderMutationField,
60
+ ReaderRefetchField,
61
+ ReaderResolverField,
62
+ ReaderResolverVariant,
63
+ ReaderScalarField,
64
+ } from './reader';
65
+ export {
66
+ NormalizationAst,
67
+ NormalizationAstNode,
68
+ NormalizationLinkedField,
69
+ NormalizationScalarField,
70
+ IsographEntrypoint,
71
+ assertIsEntrypoint,
72
+ RefetchQueryArtifact,
73
+ RefetchQueryArtifactWrapper,
74
+ } from './entrypoint';
72
75
 
73
76
  export type ExtractSecondParam<T extends (arg1: any, arg2: any) => any> =
74
77
  T extends (arg1: any, arg2: infer P) => any ? P : never;
75
78
 
76
- export type ReaderScalarField = {
77
- kind: 'Scalar';
78
- fieldName: string;
79
- alias: string | null;
80
- arguments: Arguments | null;
81
- };
82
- export type ReaderLinkedField = {
83
- kind: 'Linked';
84
- fieldName: string;
85
- alias: string | null;
86
- selections: ReaderAst<unknown>;
87
- arguments: Arguments | null;
88
- };
89
-
90
- export type ReaderResolverVariant =
91
- | { kind: 'Eager' }
92
- // componentName is the component's cacheKey for getRefReaderByName
93
- // and is the type + field concatenated
94
- | { kind: 'Component'; componentName: string };
95
-
96
- export type ReaderResolverField = {
97
- kind: 'Resolver';
98
- alias: string;
99
- readerArtifact: ReaderArtifact<any, any>;
100
- arguments: Arguments | null;
101
- usedRefetchQueries: number[];
102
- };
103
-
104
- export type ReaderRefetchField = {
105
- kind: 'RefetchField';
106
- alias: string;
107
- // TODO this bad modeling. A refetch field cannot have variant: "Component" (I think)
108
- readerArtifact: ReaderArtifact<any, any>;
109
- refetchQuery: number;
110
- };
111
-
112
- export type ReaderMutationField = {
113
- kind: 'MutationField';
114
- alias: string;
115
- // TODO this bad modeling. A mutation field cannot have variant: "Component" (I think)
116
- readerArtifact: ReaderArtifact<any, any>;
117
- refetchQuery: number;
118
- };
119
-
120
- export type NormalizationAstNode =
121
- | NormalizationScalarField
122
- | NormalizationLinkedField;
123
- // @ts-ignore
124
- export type NormalizationAst = NormalizationAstNode[];
125
-
126
- export type NormalizationScalarField = {
127
- kind: 'Scalar';
128
- fieldName: string;
129
- arguments: Arguments | null;
130
- };
131
-
132
- export type NormalizationLinkedField = {
133
- kind: 'Linked';
134
- fieldName: string;
135
- arguments: Arguments | null;
136
- selections: NormalizationAst;
137
- };
138
-
139
- // This is more like an entrypoint, but one specifically for a refetch query/mutation
140
- export type RefetchQueryArtifact = {
141
- kind: 'RefetchQuery';
142
- queryText: string;
143
- normalizationAst: NormalizationAst;
144
- };
145
-
146
- // TODO rename
147
- export type RefetchQueryArtifactWrapper = {
148
- artifact: RefetchQueryArtifact;
149
- allowedVariables: string[];
150
- };
151
-
152
79
  export type Arguments = Argument[];
153
80
  export type Argument = [ArgumentName, ArgumentValue];
154
81
  export type ArgumentName = string;
@@ -177,17 +104,6 @@ export type FragmentReference<
177
104
  nestedRefetchQueries: RefetchQueryArtifactWrapper[];
178
105
  };
179
106
 
180
- function assertIsEntrypoint<TReadFromStore extends Object, TResolverResult>(
181
- value:
182
- | IsographEntrypoint<TReadFromStore, TResolverResult>
183
- | ((_: any) => any)
184
- // Temporarily, allow any here. Once we automatically provide
185
- // types to entrypoints, we probably don't need this.
186
- | any,
187
- ): asserts value is IsographEntrypoint<TReadFromStore, TResolverResult> {
188
- if (typeof value === 'function') throw new Error('Not a string');
189
- }
190
-
191
107
  export type ExtractReadFromStore<Type> =
192
108
  Type extends IsographEntrypoint<infer X, any> ? X : never;
193
109
  export type ExtractResolverResult<Type> =