@isograph/react 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/.turbo/turbo-compile-libs.log +5 -0
  2. package/dist/core/FragmentReference.d.ts +17 -8
  3. package/dist/core/FragmentReference.d.ts.map +1 -1
  4. package/dist/core/FragmentReference.js +3 -12
  5. package/dist/core/IsographEnvironment.d.ts +30 -35
  6. package/dist/core/IsographEnvironment.d.ts.map +1 -1
  7. package/dist/core/IsographEnvironment.js +4 -0
  8. package/dist/core/PromiseWrapper.d.ts +6 -7
  9. package/dist/core/PromiseWrapper.d.ts.map +1 -1
  10. package/dist/core/PromiseWrapper.js +6 -12
  11. package/dist/core/areEqualWithDeepComparison.d.ts +1 -3
  12. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
  13. package/dist/core/areEqualWithDeepComparison.js +16 -2
  14. package/dist/core/brand.d.ts +2 -0
  15. package/dist/core/brand.d.ts.map +1 -0
  16. package/dist/core/brand.js +2 -0
  17. package/dist/core/cache.d.ts +16 -24
  18. package/dist/core/cache.d.ts.map +1 -1
  19. package/dist/core/cache.js +105 -72
  20. package/dist/core/check.d.ts +11 -7
  21. package/dist/core/check.d.ts.map +1 -1
  22. package/dist/core/check.js +2 -2
  23. package/dist/core/componentCache.d.ts +1 -1
  24. package/dist/core/componentCache.d.ts.map +1 -1
  25. package/dist/core/componentCache.js +27 -31
  26. package/dist/core/entrypoint.d.ts +43 -28
  27. package/dist/core/entrypoint.d.ts.map +1 -1
  28. package/dist/core/garbageCollection.d.ts +5 -6
  29. package/dist/core/garbageCollection.d.ts.map +1 -1
  30. package/dist/core/garbageCollection.js +1 -1
  31. package/dist/core/logging.d.ts +23 -15
  32. package/dist/core/logging.d.ts.map +1 -1
  33. package/dist/core/logging.js +8 -5
  34. package/dist/core/makeNetworkRequest.d.ts +5 -5
  35. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  36. package/dist/core/makeNetworkRequest.js +113 -28
  37. package/dist/core/read.d.ts +16 -11
  38. package/dist/core/read.d.ts.map +1 -1
  39. package/dist/core/read.js +468 -305
  40. package/dist/core/reader.d.ts +33 -37
  41. package/dist/core/reader.d.ts.map +1 -1
  42. package/dist/core/startUpdate.d.ts +8 -0
  43. package/dist/core/startUpdate.d.ts.map +1 -0
  44. package/dist/core/startUpdate.js +163 -0
  45. package/dist/core/util.d.ts +3 -0
  46. package/dist/core/util.d.ts.map +1 -1
  47. package/dist/index.d.ts +18 -15
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +9 -1
  50. package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -10
  51. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -1
  52. package/dist/loadable-hooks/useClientSideDefer.js +2 -2
  53. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +8 -15
  54. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
  55. package/dist/loadable-hooks/useConnectionSpecPagination.js +6 -4
  56. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -2
  57. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -1
  58. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +4 -6
  59. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
  60. package/dist/loadable-hooks/useImperativeLoadableField.js +1 -1
  61. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +6 -13
  62. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
  63. package/dist/loadable-hooks/useSkipLimitPagination.js +11 -9
  64. package/dist/react/FragmentReader.d.ts +7 -14
  65. package/dist/react/FragmentReader.d.ts.map +1 -1
  66. package/dist/react/FragmentReader.js +3 -30
  67. package/dist/react/FragmentRenderer.d.ts +15 -0
  68. package/dist/react/FragmentRenderer.d.ts.map +1 -0
  69. package/dist/react/FragmentRenderer.js +35 -0
  70. package/dist/react/IsographEnvironmentProvider.d.ts.map +1 -1
  71. package/dist/react/LoadableFieldReader.d.ts +12 -0
  72. package/dist/react/LoadableFieldReader.d.ts.map +1 -0
  73. package/dist/react/LoadableFieldReader.js +10 -0
  74. package/dist/react/LoadableFieldRenderer.d.ts +13 -0
  75. package/dist/react/LoadableFieldRenderer.d.ts.map +1 -0
  76. package/dist/react/LoadableFieldRenderer.js +37 -0
  77. package/dist/react/useImperativeReference.d.ts +7 -10
  78. package/dist/react/useImperativeReference.d.ts.map +1 -1
  79. package/dist/react/useImperativeReference.js +8 -9
  80. package/dist/react/useLazyReference.d.ts +4 -7
  81. package/dist/react/useLazyReference.d.ts.map +1 -1
  82. package/dist/react/useLazyReference.js +26 -5
  83. package/dist/react/useReadAndSubscribe.d.ts +3 -9
  84. package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
  85. package/dist/react/useReadAndSubscribe.js +7 -3
  86. package/dist/react/useRerenderOnChange.d.ts +1 -1
  87. package/dist/react/useRerenderOnChange.d.ts.map +1 -1
  88. package/dist/react/useResult.d.ts +3 -6
  89. package/dist/react/useResult.d.ts.map +1 -1
  90. package/dist/react/useResult.js +10 -8
  91. package/isograph.config.json +1 -0
  92. package/package.json +6 -6
  93. package/src/core/FragmentReference.ts +40 -16
  94. package/src/core/IsographEnvironment.ts +57 -39
  95. package/src/core/PromiseWrapper.ts +15 -18
  96. package/src/core/areEqualWithDeepComparison.ts +22 -2
  97. package/src/core/brand.ts +18 -0
  98. package/src/core/cache.ts +153 -113
  99. package/src/core/check.ts +17 -12
  100. package/src/core/componentCache.ts +47 -50
  101. package/src/core/entrypoint.ts +66 -21
  102. package/src/core/garbageCollection.ts +9 -9
  103. package/src/core/logging.ts +39 -25
  104. package/src/core/makeNetworkRequest.ts +212 -34
  105. package/src/core/read.ts +728 -440
  106. package/src/core/reader.ts +46 -29
  107. package/src/core/startUpdate.ts +334 -0
  108. package/src/core/util.ts +4 -0
  109. package/src/index.ts +89 -8
  110. package/src/loadable-hooks/useClientSideDefer.ts +11 -10
  111. package/src/loadable-hooks/useConnectionSpecPagination.ts +27 -13
  112. package/src/loadable-hooks/useImperativeExposedMutationField.ts +1 -1
  113. package/src/loadable-hooks/useImperativeLoadableField.ts +10 -12
  114. package/src/loadable-hooks/useSkipLimitPagination.ts +38 -19
  115. package/src/react/FragmentReader.tsx +23 -39
  116. package/src/react/FragmentRenderer.tsx +46 -0
  117. package/src/react/IsographEnvironmentProvider.tsx +1 -1
  118. package/src/react/LoadableFieldReader.tsx +40 -0
  119. package/src/react/LoadableFieldRenderer.tsx +41 -0
  120. package/src/react/useImperativeReference.ts +49 -27
  121. package/src/react/useLazyReference.ts +62 -14
  122. package/src/react/useReadAndSubscribe.ts +17 -9
  123. package/src/react/useRerenderOnChange.ts +2 -2
  124. package/src/react/useResult.ts +22 -8
  125. package/src/tests/__isograph/Economist/link/output_type.ts +2 -0
  126. package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +28 -0
  127. package/src/tests/__isograph/Node/link/output_type.ts +3 -0
  128. package/src/tests/__isograph/Query/linkedUpdate/entrypoint.ts +31 -0
  129. package/src/tests/__isograph/Query/linkedUpdate/normalization_ast.ts +95 -0
  130. package/src/tests/__isograph/Query/linkedUpdate/output_type.ts +3 -0
  131. package/src/tests/__isograph/Query/linkedUpdate/param_type.ts +51 -0
  132. package/src/tests/__isograph/Query/linkedUpdate/query_text.ts +20 -0
  133. package/src/tests/__isograph/Query/linkedUpdate/resolver_reader.ts +93 -0
  134. package/src/tests/__isograph/Query/meName/entrypoint.ts +8 -29
  135. package/src/tests/__isograph/Query/meName/normalization_ast.ts +25 -0
  136. package/src/tests/__isograph/Query/meName/query_text.ts +6 -0
  137. package/src/tests/__isograph/Query/meName/resolver_reader.ts +5 -0
  138. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +8 -67
  139. package/src/tests/__isograph/Query/meNameSuccessor/normalization_ast.ts +56 -0
  140. package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +13 -0
  141. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +10 -0
  142. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +8 -34
  143. package/src/tests/__isograph/Query/nodeField/normalization_ast.ts +30 -0
  144. package/src/tests/__isograph/Query/nodeField/query_text.ts +6 -0
  145. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +5 -0
  146. package/src/tests/__isograph/Query/startUpdate/entrypoint.ts +31 -0
  147. package/src/tests/__isograph/Query/startUpdate/normalization_ast.ts +51 -0
  148. package/src/tests/__isograph/Query/startUpdate/output_type.ts +3 -0
  149. package/src/tests/__isograph/Query/startUpdate/param_type.ts +26 -0
  150. package/src/tests/__isograph/Query/startUpdate/parameters_type.ts +3 -0
  151. package/src/tests/__isograph/Query/startUpdate/query_text.ts +11 -0
  152. package/src/tests/__isograph/Query/startUpdate/resolver_reader.ts +55 -0
  153. package/src/tests/__isograph/Query/subquery/entrypoint.ts +8 -44
  154. package/src/tests/__isograph/Query/subquery/normalization_ast.ts +38 -0
  155. package/src/tests/__isograph/Query/subquery/query_text.ts +8 -0
  156. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +7 -0
  157. package/src/tests/__isograph/iso.ts +24 -3
  158. package/src/tests/__isograph/tsconfig.json +8 -0
  159. package/src/tests/garbageCollection.test.ts +10 -8
  160. package/src/tests/meNameSuccessor.ts +1 -1
  161. package/src/tests/nodeQuery.ts +2 -1
  162. package/src/tests/normalizeData.test.ts +1 -2
  163. package/src/tests/startUpdate.test.ts +205 -0
  164. package/tsconfig.pkg.json +1 -2
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "../isograph-compiler/isograph-config-schema.json",
2
3
  "project_root": "./src/tests",
3
4
  "schema": "./schema.graphql",
4
5
  "options": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -8,15 +8,15 @@
8
8
  "author": "Isograph Labs",
9
9
  "license": "MIT",
10
10
  "scripts": {
11
- "compile": "rm -rf dist/* && tsc -p tsconfig.pkg.json",
11
+ "compile-libs": "rimraf dist && tsc -p tsconfig.pkg.json",
12
12
  "compile-watch": "tsc -p tsconfig.pkg.json --watch",
13
13
  "test": "vitest run",
14
14
  "test-watch": "vitest watch",
15
15
  "coverage": "vitest run --coverage",
16
- "prepack": "pnpm run test && pnpm run compile",
16
+ "prepack": "pnpm run test && pnpm run compile-libs",
17
17
  "tsc": "tsc",
18
- "iso": "../../target/debug/isograph_cli --config ./isograph.config.json",
19
- "iso-watch": "../../target/debug/isograph_cli --config ./isograph.config.json --watch"
18
+ "tsc-force": "tsc --build --clean && tsc --build --force",
19
+ "iso": "cross-env ISO_PRINT_ABSOLUTE_FILEPATH=1 ../../target/debug/isograph_cli --config ./isograph.config.json"
20
20
  },
21
21
  "dependencies": {
22
22
  "@isograph/disposable-types": "*",
@@ -24,7 +24,7 @@
24
24
  "@isograph/reference-counted-pointer": "*"
25
25
  },
26
26
  "peerDependencies": {
27
- "react": "18.3.1"
27
+ "react": "^18.0.0 || ^19.0.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@babel/preset-typescript": "^7.24.7",
@@ -1,12 +1,28 @@
1
- import { type Link } from './IsographEnvironment';
2
1
  import { ReaderWithRefetchQueries } from '../core/entrypoint';
2
+ import { stableCopy } from './cache';
3
+ import { type StoreLink } from './IsographEnvironment';
3
4
  import { PromiseWrapper } from './PromiseWrapper';
5
+ import type { StartUpdate } from './reader';
4
6
 
5
7
  // TODO type this better
6
- export type VariableValue = string | number | boolean | null | object;
8
+ export type VariableValue =
9
+ | string
10
+ | number
11
+ | boolean
12
+ | null
13
+ | {
14
+ readonly [index: string]: VariableValue;
15
+ }
16
+ | VariableValue[];
7
17
 
8
18
  export type Variables = { readonly [index: string]: VariableValue };
9
19
 
20
+ export type UnknownTReadFromStore = {
21
+ parameters: object;
22
+ data: object;
23
+ startUpdate?: StartUpdate<object>;
24
+ };
25
+
10
26
  export type ExtractData<T> = T extends {
11
27
  data: infer D extends object;
12
28
  }
@@ -19,31 +35,39 @@ export type ExtractParameters<T> = T extends {
19
35
  ? P
20
36
  : Variables;
21
37
 
38
+ export type ExtractStartUpdate<T extends UnknownTReadFromStore> =
39
+ T['startUpdate'];
40
+
41
+ export type ExtractUpdatableData<T extends UnknownTReadFromStore> =
42
+ ExtractUpdatableDataFromStartUpdate<ExtractStartUpdate<T>>;
43
+
44
+ export type ExtractUpdatableDataFromStartUpdate<T> =
45
+ T extends StartUpdate<infer D> ? D : never;
46
+
22
47
  export type FragmentReference<
23
- TReadFromStore extends { parameters: object; data: object },
48
+ TReadFromStore extends UnknownTReadFromStore,
24
49
  TClientFieldValue,
25
50
  > = {
26
51
  readonly kind: 'FragmentReference';
27
52
  readonly readerWithRefetchQueries: PromiseWrapper<
28
53
  ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>
29
54
  >;
30
- readonly root: Link;
55
+ readonly root: StoreLink;
56
+ // TODO we potentially stably copy and stringify variables a lot!
57
+ // So, we should employ interior mutability: pretend that fragent reference
58
+ // is immutable, but actually store something like
59
+ // `Map<Variable, StablyCopiedStringifiedOutput>`
60
+ // and read or update that map when we would otherwise stably copy and
61
+ // stringify.
31
62
  readonly variables: ExtractParameters<TReadFromStore>;
32
63
  readonly networkRequest: PromiseWrapper<void, any>;
33
64
  };
34
65
 
66
+ export type StableIdForFragmentReference = string;
67
+
35
68
  export function stableIdForFragmentReference(
36
69
  fragmentReference: FragmentReference<any, any>,
37
- ): string {
38
- return `${fragmentReference.root.__typename}/${fragmentReference.root.__link}/TODO_FRAGMENT_NAME/${serializeVariables(fragmentReference.variables ?? {})}`;
39
- }
40
-
41
- function serializeVariables(variables: Variables) {
42
- let s = '';
43
- const keys = Object.keys(variables);
44
- keys.sort();
45
- for (const key of keys) {
46
- s += `${key}:${variables[key]},`;
47
- }
48
- return s;
70
+ fieldName: string,
71
+ ): StableIdForFragmentReference {
72
+ return `${fragmentReference.root.__typename}/${fragmentReference.root.__link}/${fieldName}/${JSON.stringify(stableCopy(fragmentReference.variables))}`;
49
73
  }
@@ -1,57 +1,66 @@
1
1
  import { ParentCache } from '@isograph/react-disposable-state';
2
+ import {
3
+ IsographEntrypoint,
4
+ IsographOperation,
5
+ IsographPersistedOperation,
6
+ } from './entrypoint';
7
+ import {
8
+ FragmentReference,
9
+ Variables,
10
+ type StableIdForFragmentReference,
11
+ type UnknownTReadFromStore,
12
+ } from './FragmentReference';
2
13
  import { RetainedQuery } from './garbageCollection';
3
- import { WithEncounteredRecords } from './read';
4
- import { FragmentReference, Variables } from './FragmentReference';
5
- import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
6
- import { IsographEntrypoint } from './entrypoint';
7
- import type { ReaderAst } from './reader';
8
14
  import { LogFunction, WrappedLogFunction } from './logging';
15
+ import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
16
+ import { WithEncounteredRecords } from './read';
17
+ import type { ReaderAst, StartUpdate } from './reader';
18
+ import type { Brand } from './brand';
9
19
 
10
20
  export type ComponentOrFieldName = string;
11
21
  export type StringifiedArgs = string;
12
- type ComponentCache = {
13
- [key: DataId]: {
14
- [key: ComponentOrFieldName]: { [key: StringifiedArgs]: React.FC<any> };
15
- };
16
- };
17
22
 
18
- export type FragmentSubscription<
19
- TReadFromStore extends { parameters: object; data: object },
20
- > = {
21
- readonly kind: 'FragmentSubscription';
22
- readonly callback: (
23
- newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
24
- ) => void;
25
- /** The value read out from the previous call to readButDoNotEvaluate */
26
- readonly encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
27
- readonly fragmentReference: FragmentReference<TReadFromStore, any>;
28
- readonly readerAst: ReaderAst<TReadFromStore>;
23
+ export type FieldCache<T> = {
24
+ [key: StableIdForFragmentReference]: T;
29
25
  };
30
26
 
31
- type AnyChangesToRecordSubscription = {
27
+ export type FragmentSubscription<TReadFromStore extends UnknownTReadFromStore> =
28
+ {
29
+ readonly kind: 'FragmentSubscription';
30
+ readonly callback: (
31
+ newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
32
+ ) => void;
33
+ /** The value read out from the previous call to readButDoNotEvaluate */
34
+ readonly encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
35
+ readonly fragmentReference: FragmentReference<TReadFromStore, any>;
36
+ readonly readerAst: ReaderAst<TReadFromStore>;
37
+ };
38
+
39
+ export type AnyChangesToRecordSubscription = {
32
40
  readonly kind: 'AnyChangesToRecord';
33
41
  readonly callback: () => void;
34
- readonly recordLink: Link;
42
+ readonly recordLink: StoreLink;
35
43
  };
36
44
 
37
- type AnyRecordSubscription = {
45
+ export type AnyRecordSubscription = {
38
46
  readonly kind: 'AnyRecords';
39
47
  readonly callback: () => void;
40
48
  };
41
49
 
42
- type Subscription =
43
- | FragmentSubscription<{ parameters: object; data: object }>
50
+ export type Subscription =
51
+ | FragmentSubscription<any>
44
52
  | AnyChangesToRecordSubscription
45
53
  | AnyRecordSubscription;
46
- type Subscriptions = Set<Subscription>;
54
+ export type Subscriptions = Set<Subscription>;
47
55
  // Should this be a map?
48
- type CacheMap<T> = { [index: string]: ParentCache<T> };
56
+ export type CacheMap<T> = { [index: string]: ParentCache<T> };
49
57
 
50
58
  export type IsographEnvironment = {
51
59
  readonly store: IsographStore;
52
60
  readonly networkFunction: IsographNetworkFunction;
53
61
  readonly missingFieldHandler: MissingFieldHandler | null;
54
- readonly componentCache: ComponentCache;
62
+ readonly componentCache: FieldCache<React.FC<any>>;
63
+ readonly eagerReaderCache: FieldCache<StartUpdate<any> | undefined>;
55
64
  readonly subscriptions: Subscriptions;
56
65
  // N.B. this must be <any, any>, but all *usages* of this should go through
57
66
  // a function that adds type parameters.
@@ -59,7 +68,7 @@ export type IsographEnvironment = {
59
68
  // TODO make this a CacheMap and add GC
60
69
  readonly entrypointArtifactCache: Map<
61
70
  string,
62
- PromiseWrapper<IsographEntrypoint<any, any>>
71
+ PromiseWrapper<IsographEntrypoint<any, any, any>>
63
72
  >;
64
73
  readonly retainedQueries: Set<RetainedQuery>;
65
74
  readonly gcBuffer: Array<RetainedQuery>;
@@ -69,18 +78,23 @@ export type IsographEnvironment = {
69
78
 
70
79
  export type MissingFieldHandler = (
71
80
  storeRecord: StoreRecord,
72
- root: Link,
81
+ root: StoreLink,
73
82
  fieldName: string,
74
83
  arguments_: { [index: string]: any } | null,
75
84
  variables: Variables | null,
76
- ) => Link | undefined;
85
+ ) => StoreLink | undefined;
77
86
 
78
87
  export type IsographNetworkFunction = (
79
- queryText: string,
88
+ operation: IsographOperation | IsographPersistedOperation,
80
89
  variables: Variables,
81
90
  ) => Promise<any>;
82
91
 
83
- export type Link = {
92
+ export interface Link<T extends TypeName> extends StoreLink {
93
+ readonly __link: Brand<DataId, T>;
94
+ readonly __typename: T;
95
+ }
96
+
97
+ export type StoreLink = {
84
98
  readonly __link: DataId;
85
99
  readonly __typename: TypeName;
86
100
  };
@@ -95,7 +109,7 @@ export type DataTypeValue =
95
109
  | string
96
110
  | null
97
111
  // Singular linked fields:
98
- | Link
112
+ | StoreLink
99
113
  // Plural scalar and linked fields:
100
114
  | DataTypeValue[];
101
115
 
@@ -127,11 +141,15 @@ export function createIsographEnvironment(
127
141
  missingFieldHandler?: MissingFieldHandler | null,
128
142
  logFunction?: LogFunction | null,
129
143
  ): IsographEnvironment {
144
+ logFunction?.({
145
+ kind: 'EnvironmentCreated',
146
+ });
130
147
  return {
131
148
  store,
132
149
  networkFunction,
133
150
  missingFieldHandler: missingFieldHandler ?? null,
134
151
  componentCache: {},
152
+ eagerReaderCache: {},
135
153
  subscriptions: new Set(),
136
154
  fragmentCache: {},
137
155
  entrypointArtifactCache: new Map(),
@@ -150,7 +168,7 @@ export function createIsographStore(): IsographStore {
150
168
  };
151
169
  }
152
170
 
153
- export function assertLink(link: DataTypeValue): Link | null | undefined {
171
+ export function assertLink(link: DataTypeValue): StoreLink | null | undefined {
154
172
  if (Array.isArray(link)) {
155
173
  throw new Error('Unexpected array');
156
174
  }
@@ -163,7 +181,7 @@ export function assertLink(link: DataTypeValue): Link | null | undefined {
163
181
  throw new Error('Invalid link');
164
182
  }
165
183
 
166
- export function getLink(maybeLink: DataTypeValue): Link | null {
184
+ export function getLink(maybeLink: DataTypeValue): StoreLink | null {
167
185
  if (
168
186
  maybeLink != null &&
169
187
  typeof maybeLink === 'object' &&
@@ -180,8 +198,8 @@ export function getLink(maybeLink: DataTypeValue): Link | null {
180
198
  export function getOrLoadIsographArtifact(
181
199
  environment: IsographEnvironment,
182
200
  key: string,
183
- loader: () => Promise<IsographEntrypoint<any, any>>,
184
- ): PromiseWrapper<IsographEntrypoint<any, any>> {
201
+ loader: () => Promise<IsographEntrypoint<any, any, any>>,
202
+ ): PromiseWrapper<IsographEntrypoint<any, any, any>> {
185
203
  const value = environment.entrypointArtifactCache.get(key);
186
204
  if (value != null) {
187
205
  return value;
@@ -1,9 +1,9 @@
1
1
  export type AnyError = any;
2
2
 
3
- const NOT_SET: Symbol = Symbol('NOT_SET');
4
- type NotSet = typeof NOT_SET;
3
+ export const NOT_SET = Symbol('NOT_SET');
4
+ export type NotSet = typeof NOT_SET;
5
5
 
6
- type Result<T, E> =
6
+ export type Result<T, E> =
7
7
  | {
8
8
  kind: 'Ok';
9
9
  value: T;
@@ -18,31 +18,32 @@ type Result<T, E> =
18
18
  * Before the promise is resolved, value becomes non-null.
19
19
  */
20
20
  export type PromiseWrapper<T, E = any> = {
21
- readonly promise: Promise<T>;
21
+ readonly promise: Promise<Exclude<T, NotSet>>;
22
22
  result: Result<Exclude<T, NotSet>, E> | NotSet;
23
23
  };
24
24
 
25
- export function wrapPromise<T>(promise: Promise<T>): PromiseWrapper<T, any> {
25
+ export function wrapPromise<T>(
26
+ promise: Promise<Exclude<T, NotSet>>,
27
+ ): PromiseWrapper<T, unknown> {
26
28
  // TODO confirm suspense works if the promise is already resolved.
27
29
  const wrapper: PromiseWrapper<T, any> = { promise, result: NOT_SET };
28
30
  promise
29
31
  .then((v) => {
30
- // v is assignable to Exclude<T, Symbol> | Symbol
31
- wrapper.result = { kind: 'Ok', value: v as any };
32
+ wrapper.result = { kind: 'Ok', value: v };
32
33
  })
33
34
  .catch((e) => {
34
- // e is assignable to Exclude<T, Symbol> | Symbol
35
- wrapper.result = { kind: 'Err', error: e as any };
35
+ wrapper.result = { kind: 'Err', error: e };
36
36
  });
37
37
  return wrapper;
38
38
  }
39
39
 
40
- export function wrapResolvedValue<T>(value: T): PromiseWrapper<T, never> {
40
+ export function wrapResolvedValue<T>(
41
+ value: Exclude<T, NotSet>,
42
+ ): PromiseWrapper<T, never> {
41
43
  return {
42
44
  promise: Promise.resolve(value),
43
45
  result: {
44
46
  kind: 'Ok',
45
- // @ts-expect-error one should not call wrapResolvedValue with NOT_SET
46
47
  value,
47
48
  },
48
49
  };
@@ -51,8 +52,7 @@ export function wrapResolvedValue<T>(value: T): PromiseWrapper<T, never> {
51
52
  export function readPromise<T, E>(p: PromiseWrapper<T, E>): T {
52
53
  const { result } = p;
53
54
  if (result !== NOT_SET) {
54
- // Safety: p.result is either NOT_SET or an actual value.
55
- const resultKind = result as Result<T, any>;
55
+ const resultKind = result;
56
56
  if (resultKind.kind === 'Ok') {
57
57
  return resultKind.value;
58
58
  } else {
@@ -73,11 +73,8 @@ export type PromiseState<T, E> =
73
73
  export function getPromiseState<T, E>(
74
74
  p: PromiseWrapper<T, E>,
75
75
  ): PromiseState<T, E> {
76
- const { result } = p;
77
- if (result !== NOT_SET) {
78
- // Safety: p.result is either NOT_SET or an actual value.
79
- const resultKind = result as Result<T, any>;
80
- return resultKind;
76
+ if (p.result !== NOT_SET) {
77
+ return p.result;
81
78
  }
82
79
  return {
83
80
  kind: 'Pending',
@@ -1,5 +1,7 @@
1
+ import type { StoreLink } from './IsographEnvironment';
1
2
  import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader';
2
- export function mergeUsingReaderAst(
3
+
4
+ function mergeUsingReaderAst(
3
5
  field: ReaderScalarField | ReaderLinkedField,
4
6
  oldItem: unknown,
5
7
  newItem: unknown,
@@ -38,7 +40,7 @@ export function mergeUsingReaderAst(
38
40
  }
39
41
  }
40
42
 
41
- export function mergeArraysUsingReaderAst(
43
+ function mergeArraysUsingReaderAst(
42
44
  field: ReaderScalarField | ReaderLinkedField,
43
45
  oldItems: ReadonlyArray<unknown>,
44
46
  newItems: Array<unknown>,
@@ -96,6 +98,24 @@ export function mergeObjectsUsingReaderAst(
96
98
  }
97
99
  break;
98
100
  }
101
+ case 'Link': {
102
+ const key = field.alias;
103
+ // @ts-expect-error
104
+ const oldValue: StoreLink = oldItemObject[key];
105
+ // @ts-expect-error
106
+ const newValue: StoreLink = newItemObject[key];
107
+
108
+ if (
109
+ oldValue.__link !== newValue.__link ||
110
+ oldValue.__typename !== newValue.__typename
111
+ ) {
112
+ canRecycle = false;
113
+ } else {
114
+ // @ts-expect-error
115
+ newItemObject[key] = oldValue;
116
+ }
117
+ break;
118
+ }
99
119
  case 'ImperativelyLoadedField':
100
120
  case 'LoadablySelectedField':
101
121
  break;
@@ -0,0 +1,18 @@
1
+ // Suppress the TypeScript compiler warning for this branded-type trick.
2
+ // See discussion: https://github.com/microsoft/TypeScript/issues/202#issuecomment-436900738
3
+ // Pattern: “Brand<BaseType, Brand>” leverages TypeScript conditional and `infer` types to create a pseudo-nominal type,
4
+ // enabling BaseType to be treated as distinct only when tagged with Brand, even though Brand doesn't exist at runtime.
5
+ //
6
+ // Explanation:
7
+ // - Brand extends `symbol | string` acts as a “brand” identifier.
8
+ // - The type uses a conditional check `infer _ extends Brand ? BaseType : never` to strip out BaseType when the branding doesn't match.
9
+ // - This yields a branded type system: `Brand<string, "UserId">` is not accidentally assignable to `Brand<string, "ProductId">`.
10
+ //
11
+ // Usage: Helps enforce semantic distinctions (e.g., distinguishing user IDs from product IDs) even when their runtime values are both strings.
12
+ //
13
+ // Caveat: This is purely a compile-time trick—Brand is erased in emitted JavaScript, so runtime checks must rely on other mechanisms.
14
+ export type Brand<
15
+ BaseType,
16
+ Brand extends symbol | string,
17
+ // @ts-ignore
18
+ > = infer _ extends Brand ? BaseType : never;