@isograph/react 0.0.0-main-adb0955b → 0.0.0-main-1ed9144e

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.
@@ -4,6 +4,7 @@ import { IsographEntrypoint, NormalizationAst, NormalizationLinkedField, Normali
4
4
  import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
5
5
  import { WithEncounteredRecords } from './read';
6
6
  import { FragmentReference, Variables, ExtractParameters } from './FragmentReference';
7
+ import { FetchOptions } from './check';
7
8
  export declare function getOrCreateItemInSuspenseCache<TReadFromStore extends {
8
9
  parameters: object;
9
10
  data: object;
@@ -17,7 +18,7 @@ export declare function stableCopy<T>(value: T): T;
17
18
  export declare function getOrCreateCacheForArtifact<TReadFromStore extends {
18
19
  parameters: object;
19
20
  data: object;
20
- }, TClientFieldValue>(environment: IsographEnvironment, entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>, variables: ExtractParameters<TReadFromStore>): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>>;
21
+ }, TClientFieldValue>(environment: IsographEnvironment, entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>, variables: ExtractParameters<TReadFromStore>, options?: FetchOptions): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>>;
21
22
  type NetworkResponseScalarValue = string | number | boolean;
22
23
  type NetworkResponseValue = NetworkResponseScalarValue | null | NetworkResponseObject | NetworkResponseObject[] | NetworkResponseScalarValue[];
23
24
  export type NetworkResponseObject = {
@@ -17,6 +17,7 @@ const areEqualWithDeepComparison_1 = require("./areEqualWithDeepComparison");
17
17
  const makeNetworkRequest_1 = require("./makeNetworkRequest");
18
18
  const PromiseWrapper_1 = require("./PromiseWrapper");
19
19
  const logging_1 = require("./logging");
20
+ const check_1 = require("./check");
20
21
  const TYPENAME_FIELD_NAME = '__typename';
21
22
  function getOrCreateItemInSuspenseCache(environment, index, factory) {
22
23
  // TODO this is probably a useless message, we should remove it
@@ -52,10 +53,12 @@ function stableCopy(value) {
52
53
  }
53
54
  return stable;
54
55
  }
55
- function getOrCreateCacheForArtifact(environment, entrypoint, variables) {
56
+ function getOrCreateCacheForArtifact(environment, entrypoint, variables, options) {
56
57
  const cacheKey = entrypoint.queryText + JSON.stringify(stableCopy(variables));
57
58
  const factory = () => {
58
- const [networkRequest, disposeNetworkRequest] = (0, makeNetworkRequest_1.makeNetworkRequest)(environment, entrypoint, variables);
59
+ var _a;
60
+ const fetchPolicy = (_a = options === null || options === void 0 ? void 0 : options.fetchPolicy) !== null && _a !== void 0 ? _a : check_1.DEFAULT_FETCH_POLICY;
61
+ const [networkRequest, disposeNetworkRequest] = (0, makeNetworkRequest_1.maybeMakeNetworkRequest)(environment, entrypoint, variables, fetchPolicy);
59
62
  const itemCleanupPair = [
60
63
  {
61
64
  kind: 'FragmentReference',
@@ -0,0 +1,15 @@
1
+ import { NormalizationAst } from './entrypoint';
2
+ import { Variables } from './FragmentReference';
3
+ import { IsographEnvironment, Link } from './IsographEnvironment';
4
+ export type FetchPolicy = 'Yes' | 'No' | 'IfNecessary';
5
+ export declare const DEFAULT_FETCH_POLICY: FetchPolicy;
6
+ export type FetchOptions = {
7
+ fetchPolicy?: FetchPolicy;
8
+ };
9
+ export type CheckResult = {
10
+ kind: 'EnoughData';
11
+ } | {
12
+ kind: 'MissingData';
13
+ record: Link;
14
+ };
15
+ export declare function check(environment: IsographEnvironment, normalizationAst: NormalizationAst, variables: Variables): CheckResult;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_FETCH_POLICY = void 0;
4
+ exports.check = check;
5
+ const cache_1 = require("./cache");
6
+ const IsographEnvironment_1 = require("./IsographEnvironment");
7
+ const logging_1 = require("./logging");
8
+ exports.DEFAULT_FETCH_POLICY = 'IfNecessary';
9
+ function check(environment, normalizationAst, variables) {
10
+ const checkResult = checkFromRecord(environment, normalizationAst, variables, environment.store[IsographEnvironment_1.ROOT_ID], IsographEnvironment_1.ROOT_ID);
11
+ (0, logging_1.logMessage)(environment, {
12
+ kind: 'EnvironmentCheck',
13
+ result: checkResult,
14
+ });
15
+ return checkResult;
16
+ }
17
+ function checkFromRecord(environment, normalizationAst, variables, record, backupId) {
18
+ var _a, _b, _c;
19
+ normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
20
+ switch (normalizationAstNode.kind) {
21
+ case 'Scalar': {
22
+ const parentRecordKey = (0, cache_1.getParentRecordKey)(normalizationAstNode, variables);
23
+ const scalarValue = record[parentRecordKey];
24
+ // null means the value is known to be missing, so it must
25
+ // be exactly undefined
26
+ if (scalarValue === undefined) {
27
+ return {
28
+ kind: 'MissingData',
29
+ record: {
30
+ __link: (_a = record.id) !== null && _a !== void 0 ? _a : backupId,
31
+ },
32
+ };
33
+ }
34
+ continue normalizationAstLoop;
35
+ }
36
+ case 'Linked': {
37
+ const parentRecordKey = (0, cache_1.getParentRecordKey)(normalizationAstNode, variables);
38
+ const linkedValue = record[parentRecordKey];
39
+ if (linkedValue === undefined) {
40
+ return {
41
+ kind: 'MissingData',
42
+ record: {
43
+ __link: (_b = record.id) !== null && _b !== void 0 ? _b : backupId,
44
+ },
45
+ };
46
+ }
47
+ else if (linkedValue === null) {
48
+ continue;
49
+ }
50
+ else if (Array.isArray(linkedValue)) {
51
+ arrayItemsLoop: for (const item of linkedValue) {
52
+ const link = (0, IsographEnvironment_1.getLink)(item);
53
+ if (link === null) {
54
+ throw new Error('Unexpected non-link in the Isograph store. ' +
55
+ 'This is indicative of a bug in Isograph.');
56
+ }
57
+ const linkedRecord = environment.store[link.__link];
58
+ if (linkedRecord === undefined) {
59
+ return {
60
+ kind: 'MissingData',
61
+ record: link,
62
+ };
63
+ }
64
+ else if (linkedRecord === null) {
65
+ continue arrayItemsLoop;
66
+ }
67
+ else {
68
+ // TODO in __DEV__ assert linkedRecord is an object
69
+ const result = checkFromRecord(environment, normalizationAstNode.selections, variables, linkedRecord,
70
+ // TODO this seems likely to be wrong
71
+ backupId + '.' + parentRecordKey);
72
+ if (result.kind === 'MissingData') {
73
+ return result;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ else {
79
+ const link = (0, IsographEnvironment_1.getLink)(linkedValue);
80
+ if (link === null) {
81
+ throw new Error('Unexpected non-link in the Isograph store. ' +
82
+ 'This is indicative of a bug in Isograph.');
83
+ }
84
+ const linkedRecord = environment.store[link.__link];
85
+ if (linkedRecord === undefined) {
86
+ return {
87
+ kind: 'MissingData',
88
+ record: link,
89
+ };
90
+ }
91
+ else if (linkedRecord === null) {
92
+ continue normalizationAstLoop;
93
+ }
94
+ else {
95
+ // TODO in __DEV__ assert linkedRecord is an object
96
+ const result = checkFromRecord(environment, normalizationAstNode.selections, variables, linkedRecord,
97
+ // TODO this seems likely to be wrong
98
+ backupId + '.' + parentRecordKey);
99
+ if (result.kind === 'MissingData') {
100
+ return result;
101
+ }
102
+ }
103
+ }
104
+ continue normalizationAstLoop;
105
+ }
106
+ case 'InlineFragment': {
107
+ const existingRecordTypename = record['__typename'];
108
+ if (existingRecordTypename == null ||
109
+ existingRecordTypename !== normalizationAstNode.type) {
110
+ return {
111
+ kind: 'MissingData',
112
+ record: {
113
+ __link: (_c = record.id) !== null && _c !== void 0 ? _c : backupId,
114
+ },
115
+ };
116
+ }
117
+ const result = checkFromRecord(environment, normalizationAstNode.selections, variables, record, backupId);
118
+ if (result.kind === 'MissingData') {
119
+ return result;
120
+ }
121
+ continue normalizationAstLoop;
122
+ }
123
+ default: {
124
+ let _ = normalizationAstNode;
125
+ _;
126
+ throw new Error('Unexpected case. This is indicative of a bug in Isograph.');
127
+ }
128
+ }
129
+ }
130
+ return {
131
+ kind: 'EnoughData',
132
+ };
133
+ }
@@ -5,6 +5,7 @@ import { FragmentReference, Variables } from './FragmentReference';
5
5
  import { NetworkResponseObject } from './cache';
6
6
  import { Arguments } from './util';
7
7
  import { ReadDataResult } from './read';
8
+ import { CheckResult } from './check';
8
9
  export type LogMessage = {
9
10
  kind: 'GettingSuspenseCacheItem';
10
11
  index: string;
@@ -51,6 +52,9 @@ export type LogMessage = {
51
52
  } | {
52
53
  kind: 'NonEntrypointReceived';
53
54
  entrypoint: any;
55
+ } | {
56
+ kind: 'EnvironmentCheck';
57
+ result: CheckResult;
54
58
  };
55
59
  export type LogFunction = (logMessage: LogMessage) => void;
56
60
  export type WrappedLogFunction = {
@@ -3,4 +3,6 @@ import { IsographEntrypoint, RefetchQueryNormalizationArtifact } from './entrypo
3
3
  import { Variables } from './FragmentReference';
4
4
  import { IsographEnvironment } from './IsographEnvironment';
5
5
  import { AnyError, PromiseWrapper } from './PromiseWrapper';
6
+ import { FetchPolicy } from './check';
7
+ export declare function maybeMakeNetworkRequest(environment: IsographEnvironment, artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>, variables: Variables, fetchPolicy: FetchPolicy): ItemCleanupPair<PromiseWrapper<void, AnyError>>;
6
8
  export declare function makeNetworkRequest(environment: IsographEnvironment, artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>, variables: Variables): ItemCleanupPair<PromiseWrapper<void, AnyError>>;
@@ -1,11 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.maybeMakeNetworkRequest = maybeMakeNetworkRequest;
3
4
  exports.makeNetworkRequest = makeNetworkRequest;
4
5
  const garbageCollection_1 = require("./garbageCollection");
5
6
  const PromiseWrapper_1 = require("./PromiseWrapper");
6
7
  const cache_1 = require("./cache");
7
8
  const logging_1 = require("./logging");
9
+ const check_1 = require("./check");
8
10
  let networkRequestId = 0;
11
+ function maybeMakeNetworkRequest(environment, artifact, variables, fetchPolicy) {
12
+ switch (fetchPolicy) {
13
+ case 'Yes': {
14
+ return makeNetworkRequest(environment, artifact, variables);
15
+ }
16
+ case 'No': {
17
+ return [(0, PromiseWrapper_1.wrapResolvedValue)(undefined), () => { }];
18
+ }
19
+ case 'IfNecessary': {
20
+ const result = (0, check_1.check)(environment, artifact.normalizationAst, variables);
21
+ if (result.kind === 'EnoughData') {
22
+ return [(0, PromiseWrapper_1.wrapResolvedValue)(undefined), () => { }];
23
+ }
24
+ else {
25
+ return makeNetworkRequest(environment, artifact, variables);
26
+ }
27
+ }
28
+ }
29
+ }
9
30
  function makeNetworkRequest(environment, artifact, variables) {
10
31
  // TODO this should be a DataId and stored in the store
11
32
  const myNetworkRequestId = networkRequestId + '';
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export { readButDoNotEvaluate } from './core/read';
9
9
  export { type ExtractSecondParam, type CombineWithIntrinsicAttributes, type Argument, type ArgumentName, type ArgumentValue, type Arguments, } from './core/util';
10
10
  export { type FragmentReference, type Variables, type ExtractParameters, type ExtractData, stableIdForFragmentReference, } from './core/FragmentReference';
11
11
  export { type LogMessage, type LogFunction, logMessage, registerLogger, } from './core/logging';
12
+ export { check, CheckResult, FetchOptions, FetchPolicy } from './core/check';
12
13
  export { IsographEnvironmentProvider, useIsographEnvironment, type IsographEnvironmentProviderProps, } from './react/IsographEnvironmentProvider';
13
14
  export { useImperativeReference } from './react/useImperativeReference';
14
15
  export { FragmentReader } from './react/FragmentReader';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useImperativeLoadableField = exports.useConnectionSpecPagination = exports.useSkipLimitPagination = exports.useImperativeExposedMutationField = exports.useClientSideDefer = exports.RenderAfterCommit__DO_NOT_USE = exports.useRerenderOnChange = exports.useLazyReference = exports.useSubscribeToMultiple = exports.useReadAndSubscribe = exports.useResult = exports.FragmentReader = exports.useImperativeReference = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.registerLogger = exports.logMessage = exports.stableIdForFragmentReference = exports.readButDoNotEvaluate = exports.assertIsEntrypoint = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = exports.makeNetworkRequest = exports.normalizeData = exports.subscribe = exports.wrapPromise = exports.wrapResolvedValue = exports.getPromiseState = exports.readPromise = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
3
+ exports.useImperativeLoadableField = exports.useConnectionSpecPagination = exports.useSkipLimitPagination = exports.useImperativeExposedMutationField = exports.useClientSideDefer = exports.RenderAfterCommit__DO_NOT_USE = exports.useRerenderOnChange = exports.useLazyReference = exports.useSubscribeToMultiple = exports.useReadAndSubscribe = exports.useResult = exports.FragmentReader = exports.useImperativeReference = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.check = exports.registerLogger = exports.logMessage = exports.stableIdForFragmentReference = exports.readButDoNotEvaluate = exports.assertIsEntrypoint = exports.defaultMissingFieldHandler = exports.createIsographStore = exports.createIsographEnvironment = exports.ROOT_ID = exports.makeNetworkRequest = exports.normalizeData = exports.subscribe = exports.wrapPromise = exports.wrapResolvedValue = exports.getPromiseState = exports.readPromise = exports.garbageCollectEnvironment = exports.unretainQuery = exports.retainQuery = void 0;
4
4
  var garbageCollection_1 = require("./core/garbageCollection");
5
5
  Object.defineProperty(exports, "retainQuery", { enumerable: true, get: function () { return garbageCollection_1.retainQuery; } });
6
6
  Object.defineProperty(exports, "unretainQuery", { enumerable: true, get: function () { return garbageCollection_1.unretainQuery; } });
@@ -29,6 +29,8 @@ Object.defineProperty(exports, "stableIdForFragmentReference", { enumerable: tru
29
29
  var logging_1 = require("./core/logging");
30
30
  Object.defineProperty(exports, "logMessage", { enumerable: true, get: function () { return logging_1.logMessage; } });
31
31
  Object.defineProperty(exports, "registerLogger", { enumerable: true, get: function () { return logging_1.registerLogger; } });
32
+ var check_1 = require("./core/check");
33
+ Object.defineProperty(exports, "check", { enumerable: true, get: function () { return check_1.check; } });
32
34
  var IsographEnvironmentProvider_1 = require("./react/IsographEnvironmentProvider");
33
35
  Object.defineProperty(exports, "IsographEnvironmentProvider", { enumerable: true, get: function () { return IsographEnvironmentProvider_1.IsographEnvironmentProvider; } });
34
36
  Object.defineProperty(exports, "useIsographEnvironment", { enumerable: true, get: function () { return IsographEnvironmentProvider_1.useIsographEnvironment; } });
@@ -1,10 +1,11 @@
1
1
  import { UnassignedState } from '@isograph/react-disposable-state';
2
2
  import { IsographEntrypoint } from '../core/entrypoint';
3
3
  import { FragmentReference, ExtractParameters } from '../core/FragmentReference';
4
+ import { FetchOptions } from '../core/check';
4
5
  export declare function useImperativeReference<TReadFromStore extends {
5
6
  parameters: object;
6
7
  data: object;
7
8
  }, TClientFieldValue>(entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>): {
8
9
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue> | UnassignedState;
9
- loadFragmentReference: (variables: ExtractParameters<TReadFromStore>) => void;
10
+ loadFragmentReference: (variables: ExtractParameters<TReadFromStore>, options?: FetchOptions) => void;
10
11
  };
@@ -6,14 +6,17 @@ const IsographEnvironmentProvider_1 = require("./IsographEnvironmentProvider");
6
6
  const IsographEnvironment_1 = require("../core/IsographEnvironment");
7
7
  const makeNetworkRequest_1 = require("../core/makeNetworkRequest");
8
8
  const PromiseWrapper_1 = require("../core/PromiseWrapper");
9
+ const check_1 = require("../core/check");
9
10
  // TODO rename this to useImperativelyLoadedEntrypoint
10
11
  function useImperativeReference(entrypoint) {
11
12
  const { state, setState } = (0, react_disposable_state_1.useUpdatableDisposableState)();
12
13
  const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
13
14
  return {
14
15
  fragmentReference: state,
15
- loadFragmentReference: (variables) => {
16
- const [networkRequest, disposeNetworkRequest] = (0, makeNetworkRequest_1.makeNetworkRequest)(environment, entrypoint, variables);
16
+ loadFragmentReference: (variables, options) => {
17
+ var _a;
18
+ const fetchPolicy = (_a = options === null || options === void 0 ? void 0 : options.fetchPolicy) !== null && _a !== void 0 ? _a : check_1.DEFAULT_FETCH_POLICY;
19
+ const [networkRequest, disposeNetworkRequest] = (0, makeNetworkRequest_1.maybeMakeNetworkRequest)(environment, entrypoint, variables, fetchPolicy);
17
20
  setState([
18
21
  {
19
22
  kind: 'FragmentReference',
@@ -1,8 +1,9 @@
1
1
  import { FragmentReference, ExtractParameters } from '../core/FragmentReference';
2
2
  import { IsographEntrypoint } from '../core/entrypoint';
3
+ import { FetchOptions } from '../core/check';
3
4
  export declare function useLazyReference<TReadFromStore extends {
4
5
  parameters: object;
5
6
  data: object;
6
- }, TClientFieldValue>(entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>, variables: ExtractParameters<TReadFromStore>): {
7
+ }, TClientFieldValue>(entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>, variables: ExtractParameters<TReadFromStore>, options?: FetchOptions): {
7
8
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>;
8
9
  };
@@ -5,7 +5,7 @@ const IsographEnvironmentProvider_1 = require("./IsographEnvironmentProvider");
5
5
  const cache_1 = require("../core/cache");
6
6
  const react_disposable_state_1 = require("@isograph/react-disposable-state");
7
7
  const logging_1 = require("../core/logging");
8
- function useLazyReference(entrypoint, variables) {
8
+ function useLazyReference(entrypoint, variables, options) {
9
9
  const environment = (0, IsographEnvironmentProvider_1.useIsographEnvironment)();
10
10
  if ((entrypoint === null || entrypoint === void 0 ? void 0 : entrypoint.kind) !== 'Entrypoint') {
11
11
  // TODO have a separate error logger
@@ -14,7 +14,7 @@ function useLazyReference(entrypoint, variables) {
14
14
  entrypoint,
15
15
  });
16
16
  }
17
- const cache = (0, cache_1.getOrCreateCacheForArtifact)(environment, entrypoint, variables);
17
+ const cache = (0, cache_1.getOrCreateCacheForArtifact)(environment, entrypoint, variables, options);
18
18
  return {
19
19
  fragmentReference: (0, react_disposable_state_1.useLazyDisposableState)(cache).state,
20
20
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-adb0955b",
3
+ "version": "0.0.0-main-1ed9144e",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -17,9 +17,9 @@
17
17
  "tsc": "tsc"
18
18
  },
19
19
  "dependencies": {
20
- "@isograph/disposable-types": "0.0.0-main-adb0955b",
21
- "@isograph/react-disposable-state": "0.0.0-main-adb0955b",
22
- "@isograph/reference-counted-pointer": "0.0.0-main-adb0955b"
20
+ "@isograph/disposable-types": "0.0.0-main-1ed9144e",
21
+ "@isograph/react-disposable-state": "0.0.0-main-1ed9144e",
22
+ "@isograph/reference-counted-pointer": "0.0.0-main-1ed9144e"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": "18.3.1"
package/src/core/cache.ts CHANGED
@@ -30,9 +30,10 @@ import {
30
30
  ExtractParameters,
31
31
  } from './FragmentReference';
32
32
  import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison';
33
- import { makeNetworkRequest } from './makeNetworkRequest';
33
+ import { maybeMakeNetworkRequest } from './makeNetworkRequest';
34
34
  import { wrapResolvedValue } from './PromiseWrapper';
35
35
  import { logMessage } from './logging';
36
+ import { DEFAULT_FETCH_POLICY, FetchOptions } from './check';
36
37
 
37
38
  const TYPENAME_FIELD_NAME = '__typename';
38
39
 
@@ -87,14 +88,18 @@ export function getOrCreateCacheForArtifact<
87
88
  environment: IsographEnvironment,
88
89
  entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
89
90
  variables: ExtractParameters<TReadFromStore>,
91
+ options?: FetchOptions,
90
92
  ): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
91
93
  const cacheKey = entrypoint.queryText + JSON.stringify(stableCopy(variables));
92
94
  const factory = () => {
93
- const [networkRequest, disposeNetworkRequest] = makeNetworkRequest(
95
+ const fetchPolicy = options?.fetchPolicy ?? DEFAULT_FETCH_POLICY;
96
+ const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
94
97
  environment,
95
98
  entrypoint,
96
99
  variables,
100
+ fetchPolicy,
97
101
  );
102
+
98
103
  const itemCleanupPair: ItemCleanupPair<
99
104
  FragmentReference<TReadFromStore, TClientFieldValue>
100
105
  > = [
@@ -0,0 +1,208 @@
1
+ import { getParentRecordKey } from './cache';
2
+ import { NormalizationAst } from './entrypoint';
3
+ import { Variables } from './FragmentReference';
4
+ import {
5
+ getLink,
6
+ IsographEnvironment,
7
+ Link,
8
+ ROOT_ID,
9
+ StoreRecord,
10
+ } from './IsographEnvironment';
11
+ import { logMessage } from './logging';
12
+
13
+ export type FetchPolicy = 'Yes' | 'No' | 'IfNecessary';
14
+
15
+ export const DEFAULT_FETCH_POLICY: FetchPolicy = 'IfNecessary';
16
+
17
+ export type FetchOptions = {
18
+ fetchPolicy?: FetchPolicy;
19
+ };
20
+
21
+ export type CheckResult =
22
+ | {
23
+ kind: 'EnoughData';
24
+ }
25
+ | {
26
+ kind: 'MissingData';
27
+ record: Link;
28
+ };
29
+
30
+ export function check(
31
+ environment: IsographEnvironment,
32
+ normalizationAst: NormalizationAst,
33
+ variables: Variables,
34
+ ): CheckResult {
35
+ const checkResult = checkFromRecord(
36
+ environment,
37
+ normalizationAst,
38
+ variables,
39
+ environment.store[ROOT_ID],
40
+ ROOT_ID,
41
+ );
42
+ logMessage(environment, {
43
+ kind: 'EnvironmentCheck',
44
+ result: checkResult,
45
+ });
46
+ return checkResult;
47
+ }
48
+
49
+ function checkFromRecord(
50
+ environment: IsographEnvironment,
51
+ normalizationAst: NormalizationAst,
52
+ variables: Variables,
53
+ record: StoreRecord,
54
+ backupId: string,
55
+ ): CheckResult {
56
+ normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
57
+ switch (normalizationAstNode.kind) {
58
+ case 'Scalar': {
59
+ const parentRecordKey = getParentRecordKey(
60
+ normalizationAstNode,
61
+ variables,
62
+ );
63
+ const scalarValue = record[parentRecordKey];
64
+
65
+ // null means the value is known to be missing, so it must
66
+ // be exactly undefined
67
+ if (scalarValue === undefined) {
68
+ return {
69
+ kind: 'MissingData',
70
+ record: {
71
+ __link: record.id ?? backupId,
72
+ },
73
+ };
74
+ }
75
+ continue normalizationAstLoop;
76
+ }
77
+ case 'Linked': {
78
+ const parentRecordKey = getParentRecordKey(
79
+ normalizationAstNode,
80
+ variables,
81
+ );
82
+
83
+ const linkedValue = record[parentRecordKey];
84
+
85
+ if (linkedValue === undefined) {
86
+ return {
87
+ kind: 'MissingData',
88
+ record: {
89
+ __link: record.id ?? backupId,
90
+ },
91
+ };
92
+ } else if (linkedValue === null) {
93
+ continue;
94
+ } else if (Array.isArray(linkedValue)) {
95
+ arrayItemsLoop: for (const item of linkedValue) {
96
+ const link = getLink(item);
97
+ if (link === null) {
98
+ throw new Error(
99
+ 'Unexpected non-link in the Isograph store. ' +
100
+ 'This is indicative of a bug in Isograph.',
101
+ );
102
+ }
103
+
104
+ const linkedRecord = environment.store[link.__link];
105
+
106
+ if (linkedRecord === undefined) {
107
+ return {
108
+ kind: 'MissingData',
109
+ record: link,
110
+ };
111
+ } else if (linkedRecord === null) {
112
+ continue arrayItemsLoop;
113
+ } else {
114
+ // TODO in __DEV__ assert linkedRecord is an object
115
+ const result = checkFromRecord(
116
+ environment,
117
+ normalizationAstNode.selections,
118
+ variables,
119
+ linkedRecord,
120
+ // TODO this seems likely to be wrong
121
+ backupId + '.' + parentRecordKey,
122
+ );
123
+
124
+ if (result.kind === 'MissingData') {
125
+ return result;
126
+ }
127
+ }
128
+ }
129
+ } else {
130
+ const link = getLink(linkedValue);
131
+ if (link === null) {
132
+ throw new Error(
133
+ 'Unexpected non-link in the Isograph store. ' +
134
+ 'This is indicative of a bug in Isograph.',
135
+ );
136
+ }
137
+
138
+ const linkedRecord = environment.store[link.__link];
139
+
140
+ if (linkedRecord === undefined) {
141
+ return {
142
+ kind: 'MissingData',
143
+ record: link,
144
+ };
145
+ } else if (linkedRecord === null) {
146
+ continue normalizationAstLoop;
147
+ } else {
148
+ // TODO in __DEV__ assert linkedRecord is an object
149
+ const result = checkFromRecord(
150
+ environment,
151
+ normalizationAstNode.selections,
152
+ variables,
153
+ linkedRecord,
154
+ // TODO this seems likely to be wrong
155
+ backupId + '.' + parentRecordKey,
156
+ );
157
+
158
+ if (result.kind === 'MissingData') {
159
+ return result;
160
+ }
161
+ }
162
+ }
163
+
164
+ continue normalizationAstLoop;
165
+ }
166
+ case 'InlineFragment': {
167
+ const existingRecordTypename = record['__typename'];
168
+
169
+ if (
170
+ existingRecordTypename == null ||
171
+ existingRecordTypename !== normalizationAstNode.type
172
+ ) {
173
+ return {
174
+ kind: 'MissingData',
175
+ record: {
176
+ __link: record.id ?? backupId,
177
+ },
178
+ };
179
+ }
180
+
181
+ const result = checkFromRecord(
182
+ environment,
183
+ normalizationAstNode.selections,
184
+ variables,
185
+ record,
186
+ backupId,
187
+ );
188
+
189
+ if (result.kind === 'MissingData') {
190
+ return result;
191
+ }
192
+
193
+ continue normalizationAstLoop;
194
+ }
195
+ default: {
196
+ let _: never = normalizationAstNode;
197
+ _;
198
+ throw new Error(
199
+ 'Unexpected case. This is indicative of a bug in Isograph.',
200
+ );
201
+ }
202
+ }
203
+ }
204
+
205
+ return {
206
+ kind: 'EnoughData',
207
+ };
208
+ }
@@ -14,6 +14,7 @@ import { FragmentReference, Variables } from './FragmentReference';
14
14
  import { NetworkResponseObject } from './cache';
15
15
  import { Arguments } from './util';
16
16
  import { ReadDataResult } from './read';
17
+ import { CheckResult } from './check';
17
18
 
18
19
  export type LogMessage =
19
20
  | {
@@ -74,6 +75,10 @@ export type LogMessage =
74
75
  | {
75
76
  kind: 'NonEntrypointReceived';
76
77
  entrypoint: any;
78
+ }
79
+ | {
80
+ kind: 'EnvironmentCheck';
81
+ result: CheckResult;
77
82
  };
78
83
 
79
84
  export type LogFunction = (logMessage: LogMessage) => void;
@@ -11,12 +11,42 @@ import {
11
11
  unretainQuery,
12
12
  } from './garbageCollection';
13
13
  import { IsographEnvironment } from './IsographEnvironment';
14
- import { AnyError, PromiseWrapper, wrapPromise } from './PromiseWrapper';
14
+ import {
15
+ AnyError,
16
+ PromiseWrapper,
17
+ wrapPromise,
18
+ wrapResolvedValue,
19
+ } from './PromiseWrapper';
15
20
  import { normalizeData } from './cache';
16
21
  import { logMessage } from './logging';
22
+ import { check, FetchPolicy } from './check';
17
23
 
18
24
  let networkRequestId = 0;
19
25
 
26
+ export function maybeMakeNetworkRequest(
27
+ environment: IsographEnvironment,
28
+ artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
29
+ variables: Variables,
30
+ fetchPolicy: FetchPolicy,
31
+ ): ItemCleanupPair<PromiseWrapper<void, AnyError>> {
32
+ switch (fetchPolicy) {
33
+ case 'Yes': {
34
+ return makeNetworkRequest(environment, artifact, variables);
35
+ }
36
+ case 'No': {
37
+ return [wrapResolvedValue(undefined), () => {}];
38
+ }
39
+ case 'IfNecessary': {
40
+ const result = check(environment, artifact.normalizationAst, variables);
41
+ if (result.kind === 'EnoughData') {
42
+ return [wrapResolvedValue(undefined), () => {}];
43
+ } else {
44
+ return makeNetworkRequest(environment, artifact, variables);
45
+ }
46
+ }
47
+ }
48
+ }
49
+
20
50
  export function makeNetworkRequest(
21
51
  environment: IsographEnvironment,
22
52
  artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
package/src/core/read.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { CleanupFn } from '@isograph/isograph-disposable-types/dist';
2
1
  import { getParentRecordKey, onNextChangeToRecord } from './cache';
3
2
  import { getOrCreateCachedComponent } from './componentCache';
4
3
  import {
@@ -30,6 +29,7 @@ import {
30
29
  import { ReaderAst } from './reader';
31
30
  import { Arguments } from './util';
32
31
  import { logMessage } from './logging';
32
+ import { CleanupFn } from '@isograph/disposable-types';
33
33
 
34
34
  export type WithEncounteredRecords<T> = {
35
35
  readonly encounteredRecords: Set<DataId>;
package/src/index.ts CHANGED
@@ -74,6 +74,7 @@ export {
74
74
  logMessage,
75
75
  registerLogger,
76
76
  } from './core/logging';
77
+ export { check, CheckResult, FetchOptions, FetchPolicy } from './core/check';
77
78
 
78
79
  export {
79
80
  IsographEnvironmentProvider,
@@ -9,8 +9,9 @@ import {
9
9
  } from '../core/FragmentReference';
10
10
  import { useIsographEnvironment } from './IsographEnvironmentProvider';
11
11
  import { ROOT_ID } from '../core/IsographEnvironment';
12
- import { makeNetworkRequest } from '../core/makeNetworkRequest';
12
+ import { maybeMakeNetworkRequest } from '../core/makeNetworkRequest';
13
13
  import { wrapResolvedValue } from '../core/PromiseWrapper';
14
+ import { DEFAULT_FETCH_POLICY, FetchOptions } from '../core/check';
14
15
 
15
16
  // TODO rename this to useImperativelyLoadedEntrypoint
16
17
 
@@ -23,7 +24,10 @@ export function useImperativeReference<
23
24
  fragmentReference:
24
25
  | FragmentReference<TReadFromStore, TClientFieldValue>
25
26
  | UnassignedState;
26
- loadFragmentReference: (variables: ExtractParameters<TReadFromStore>) => void;
27
+ loadFragmentReference: (
28
+ variables: ExtractParameters<TReadFromStore>,
29
+ options?: FetchOptions,
30
+ ) => void;
27
31
  } {
28
32
  const { state, setState } =
29
33
  useUpdatableDisposableState<
@@ -32,11 +36,16 @@ export function useImperativeReference<
32
36
  const environment = useIsographEnvironment();
33
37
  return {
34
38
  fragmentReference: state,
35
- loadFragmentReference: (variables: ExtractParameters<TReadFromStore>) => {
36
- const [networkRequest, disposeNetworkRequest] = makeNetworkRequest(
39
+ loadFragmentReference: (
40
+ variables: ExtractParameters<TReadFromStore>,
41
+ options?: FetchOptions,
42
+ ) => {
43
+ const fetchPolicy = options?.fetchPolicy ?? DEFAULT_FETCH_POLICY;
44
+ const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
37
45
  environment,
38
46
  entrypoint,
39
47
  variables,
48
+ fetchPolicy,
40
49
  );
41
50
  setState([
42
51
  {
@@ -7,6 +7,7 @@ import { IsographEntrypoint } from '../core/entrypoint';
7
7
  import { getOrCreateCacheForArtifact } from '../core/cache';
8
8
  import { useLazyDisposableState } from '@isograph/react-disposable-state';
9
9
  import { logMessage } from '../core/logging';
10
+ import { FetchOptions } from '../core/check';
10
11
 
11
12
  export function useLazyReference<
12
13
  TReadFromStore extends { parameters: object; data: object },
@@ -14,6 +15,7 @@ export function useLazyReference<
14
15
  >(
15
16
  entrypoint: IsographEntrypoint<TReadFromStore, TClientFieldValue>,
16
17
  variables: ExtractParameters<TReadFromStore>,
18
+ options?: FetchOptions,
17
19
  ): {
18
20
  fragmentReference: FragmentReference<TReadFromStore, TClientFieldValue>;
19
21
  } {
@@ -27,7 +29,12 @@ export function useLazyReference<
27
29
  });
28
30
  }
29
31
 
30
- const cache = getOrCreateCacheForArtifact(environment, entrypoint, variables);
32
+ const cache = getOrCreateCacheForArtifact(
33
+ environment,
34
+ entrypoint,
35
+ variables,
36
+ options,
37
+ );
31
38
 
32
39
  return {
33
40
  fragmentReference: useLazyDisposableState(cache).state,