@isograph/react 0.0.0-main-0eb53a17 → 0.0.0-main-cddcd660

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 (65) hide show
  1. package/.turbo/turbo-compile-libs.log +1 -1
  2. package/dist/core/FragmentReference.d.ts +2 -2
  3. package/dist/core/FragmentReference.d.ts.map +1 -1
  4. package/dist/core/IsographEnvironment.d.ts +12 -7
  5. package/dist/core/IsographEnvironment.d.ts.map +1 -1
  6. package/dist/core/areEqualWithDeepComparison.d.ts +1 -3
  7. package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
  8. package/dist/core/areEqualWithDeepComparison.js +0 -2
  9. package/dist/core/brand.d.ts +2 -0
  10. package/dist/core/brand.d.ts.map +1 -0
  11. package/dist/core/brand.js +2 -0
  12. package/dist/core/cache.d.ts +4 -4
  13. package/dist/core/cache.d.ts.map +1 -1
  14. package/dist/core/cache.js +8 -3
  15. package/dist/core/check.d.ts +3 -3
  16. package/dist/core/check.d.ts.map +1 -1
  17. package/dist/core/garbageCollection.d.ts +2 -2
  18. package/dist/core/garbageCollection.d.ts.map +1 -1
  19. package/dist/core/logging.d.ts +13 -4
  20. package/dist/core/logging.d.ts.map +1 -1
  21. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  22. package/dist/core/read.d.ts +7 -7
  23. package/dist/core/read.d.ts.map +1 -1
  24. package/dist/core/reader.d.ts +3 -3
  25. package/dist/core/reader.d.ts.map +1 -1
  26. package/dist/core/startUpdate.d.ts.map +1 -1
  27. package/dist/core/startUpdate.js +9 -0
  28. package/dist/index.d.ts +4 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +5 -1
  31. package/dist/react/FragmentReader.d.ts +4 -10
  32. package/dist/react/FragmentReader.d.ts.map +1 -1
  33. package/dist/react/FragmentReader.js +3 -30
  34. package/dist/react/FragmentRenderer.d.ts +15 -0
  35. package/dist/react/FragmentRenderer.d.ts.map +1 -0
  36. package/dist/react/FragmentRenderer.js +35 -0
  37. package/dist/react/LoadableFieldReader.d.ts.map +1 -1
  38. package/dist/react/LoadableFieldReader.js +0 -3
  39. package/dist/react/LoadableFieldRenderer.d.ts +13 -0
  40. package/dist/react/LoadableFieldRenderer.d.ts.map +1 -0
  41. package/dist/react/LoadableFieldRenderer.js +37 -0
  42. package/package.json +4 -4
  43. package/src/core/FragmentReference.ts +2 -2
  44. package/src/core/IsographEnvironment.ts +13 -7
  45. package/src/core/areEqualWithDeepComparison.ts +5 -5
  46. package/src/core/brand.ts +18 -0
  47. package/src/core/cache.ts +22 -14
  48. package/src/core/check.ts +4 -4
  49. package/src/core/garbageCollection.ts +3 -3
  50. package/src/core/logging.ts +16 -4
  51. package/src/core/makeNetworkRequest.ts +2 -2
  52. package/src/core/read.ts +12 -10
  53. package/src/core/reader.ts +3 -3
  54. package/src/core/startUpdate.ts +11 -2
  55. package/src/index.ts +5 -2
  56. package/src/react/FragmentReader.tsx +17 -36
  57. package/src/react/FragmentRenderer.tsx +46 -0
  58. package/src/react/LoadableFieldReader.tsx +0 -3
  59. package/src/react/LoadableFieldRenderer.tsx +41 -0
  60. package/src/tests/__isograph/Economist/link/output_type.ts +2 -0
  61. package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +1 -1
  62. package/src/tests/__isograph/Node/link/output_type.ts +3 -0
  63. package/src/tests/__isograph/Query/linkedUpdate/param_type.ts +4 -4
  64. package/src/tests/__isograph/iso.ts +1 -1
  65. package/src/tests/__isograph/tsconfig.json +8 -0
@@ -1,35 +1,8 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
3
  exports.FragmentReader = FragmentReader;
27
- const React = __importStar(require("react"));
28
4
  const useResult_1 = require("./useResult");
29
- function FragmentReader(props) {
30
- const Component = (0, useResult_1.useResult)(props.fragmentReference, props.networkRequestOptions);
31
- // TypeScript is not understanding that if additionalProps is Record<PropertyKey, never>,
32
- // it means that TProps === JSX.IntrinsicAttributes.
33
- // @ts-expect-error
34
- return React.createElement(Component, Object.assign({}, props.additionalProps));
5
+ function FragmentReader({ fragmentReference, networkRequestOptions, children, }) {
6
+ const result = (0, useResult_1.useResult)(fragmentReference, networkRequestOptions);
7
+ return children(result);
35
8
  }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import { type ExtractReadFromStore, type IsographEntrypoint } from '../core/entrypoint';
3
+ import { type FragmentReference } from '../core/FragmentReference';
4
+ import { type NetworkRequestReaderOptions } from '../core/read';
5
+ export type IsExactlyIntrinsicAttributes<T> = T extends JSX.IntrinsicAttributes ? JSX.IntrinsicAttributes extends T ? true : false : false;
6
+ export declare function FragmentRenderer<TProps extends Record<any, any>, TEntrypoint extends IsographEntrypoint<any, React.FC<TProps>, any>>(props: IsExactlyIntrinsicAttributes<TProps> extends true ? {
7
+ fragmentReference: FragmentReference<ExtractReadFromStore<TEntrypoint>, React.FC<TProps>>;
8
+ additionalProps?: Record<PropertyKey, never>;
9
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
10
+ } : {
11
+ fragmentReference: FragmentReference<ExtractReadFromStore<TEntrypoint>, React.FC<TProps>>;
12
+ additionalProps: Omit<TProps, keyof JSX.IntrinsicAttributes>;
13
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
14
+ }): React.ReactNode;
15
+ //# sourceMappingURL=FragmentRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FragmentRenderer.d.ts","sourceRoot":"","sources":["../../src/react/FragmentRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAGhE,MAAM,MAAM,4BAA4B,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,mBAAmB,GAC3E,GAAG,CAAC,mBAAmB,SAAS,CAAC,GAC/B,IAAI,GACJ,KAAK,GACP,KAAK,CAAC;AAEV,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,EAC/B,WAAW,SAAS,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,EAElE,KAAK,EAAE,4BAA4B,CAAC,MAAM,CAAC,SAAS,IAAI,GACpD;IACE,iBAAiB,EAAE,iBAAiB,CAClC,oBAAoB,CAAC,WAAW,CAAC,EACjC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,CACjB,CAAC;IACF,eAAe,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC7C,qBAAqB,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;CAC9D,GACD;IACE,iBAAiB,EAAE,iBAAiB,CAClC,oBAAoB,CAAC,WAAW,CAAC,EACjC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,CACjB,CAAC;IACF,eAAe,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7D,qBAAqB,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;CAC9D,GACJ,KAAK,CAAC,SAAS,CASjB"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.FragmentRenderer = FragmentRenderer;
27
+ const React = __importStar(require("react"));
28
+ const useResult_1 = require("./useResult");
29
+ function FragmentRenderer(props) {
30
+ const Component = (0, useResult_1.useResult)(props.fragmentReference, props.networkRequestOptions);
31
+ // TypeScript is not understanding that if additionalProps is Record<PropertyKey, never>,
32
+ // it means that TProps === JSX.IntrinsicAttributes.
33
+ // @ts-expect-error
34
+ return React.createElement(Component, Object.assign({}, props.additionalProps));
35
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"LoadableFieldReader.d.ts","sourceRoot":"","sources":["../../src/react/LoadableFieldReader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAOpD,wBAAgB,mBAAmB,CACjC,cAAc,SAAS,qBAAqB,EAC5C,OAAO,EACP,aAAa,SAAS,MAAM,EAC5B,eAAe,EACf,KAAK,EAAE;IACP,aAAa,EAAE,aAAa,CAC1B,cAAc,EACd,OAAO,EACP,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,aAAa,CAAC,CAC7D,CAAC;IAEF,IAAI,EAAE,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,aAAa,CAAC,CAAC;IACnE,YAAY,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC7D,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,eAAe,CAAC;CAC7C,GAAG,eAAe,CAalB"}
1
+ {"version":3,"file":"LoadableFieldReader.d.ts","sourceRoot":"","sources":["../../src/react/LoadableFieldReader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,wBAAgB,mBAAmB,CACjC,cAAc,SAAS,qBAAqB,EAC5C,OAAO,EACP,aAAa,SAAS,MAAM,EAC5B,eAAe,EACf,KAAK,EAAE;IACP,aAAa,EAAE,aAAa,CAC1B,cAAc,EACd,OAAO,EACP,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,aAAa,CAAC,CAC7D,CAAC;IAEF,IAAI,EAAE,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,aAAa,CAAC,CAAC;IACnE,YAAY,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC7D,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,eAAe,CAAC;CAC7C,GAAG,eAAe,CAalB"}
@@ -3,9 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LoadableFieldReader = LoadableFieldReader;
4
4
  const useClientSideDefer_1 = require("../loadable-hooks/useClientSideDefer");
5
5
  const useResult_1 = require("./useResult");
6
- // TODO
7
- // - LoadableFieldRenderer, which works for @loadable fields that are components,
8
- // i.e. just renders the resulting component
9
6
  function LoadableFieldReader(props) {
10
7
  const { fragmentReference } = (0, useClientSideDefer_1.useClientSideDefer)(props.loadableField, props.args, props.fetchOptions);
11
8
  const readOutFragmentData = (0, useResult_1.useResult)(fragmentReference, props.networkRequestOptions);
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { type FetchOptions } from '../core/check';
3
+ import { ExtractParameters, type UnknownTReadFromStore } from '../core/FragmentReference';
4
+ import { type NetworkRequestReaderOptions } from '../core/read';
5
+ import { type LoadableField } from '../core/reader';
6
+ export declare function LoadableFieldRenderer<TReadFromStore extends UnknownTReadFromStore, TProvidedArgs extends object, TChildrenResult, TProps>(props: {
7
+ loadableField: LoadableField<TReadFromStore, React.FC<TProps>, Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>>;
8
+ args: Omit<ExtractParameters<TReadFromStore>, keyof TProvidedArgs>;
9
+ fetchOptions?: FetchOptions<React.FC<TProps>>;
10
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions>;
11
+ additionalProps: Omit<TProps, keyof JSX.IntrinsicAttributes>;
12
+ }): TChildrenResult;
13
+ //# sourceMappingURL=LoadableFieldRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadableFieldRenderer.d.ts","sourceRoot":"","sources":["../../src/react/LoadableFieldRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,wBAAgB,qBAAqB,CACnC,cAAc,SAAS,qBAAqB,EAC5C,aAAa,SAAS,MAAM,EAC5B,eAAe,EACf,MAAM,EACN,KAAK,EAAE;IACP,aAAa,EAAE,aAAa,CAC1B,cAAc,EACd,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,EAChB,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,aAAa,CAAC,CAC7D,CAAC;IAEF,IAAI,EAAE,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,aAAa,CAAC,CAAC;IACnE,YAAY,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,qBAAqB,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC7D,eAAe,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,mBAAmB,CAAC,CAAC;CAC9D,GAAG,eAAe,CAalB"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.LoadableFieldRenderer = LoadableFieldRenderer;
27
+ const React = __importStar(require("react"));
28
+ const useClientSideDefer_1 = require("../loadable-hooks/useClientSideDefer");
29
+ const useResult_1 = require("./useResult");
30
+ function LoadableFieldRenderer(props) {
31
+ const { fragmentReference } = (0, useClientSideDefer_1.useClientSideDefer)(props.loadableField, props.args, props.fetchOptions);
32
+ const Component = (0, useResult_1.useResult)(fragmentReference, props.networkRequestOptions);
33
+ // TODO we probably can figure out a way to convince TypeScript of
34
+ // the validity of this.
35
+ // @ts-expect-error
36
+ return React.createElement(Component, Object.assign({}, props.additionalProps));
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isograph/react",
3
- "version": "0.0.0-main-0eb53a17",
3
+ "version": "0.0.0-main-cddcd660",
4
4
  "description": "Use Isograph with React",
5
5
  "homepage": "https://isograph.dev",
6
6
  "main": "dist/index.js",
@@ -19,9 +19,9 @@
19
19
  "iso": "cross-env ISO_PRINT_ABSOLUTE_FILEPATH=1 ../../target/debug/isograph_cli --config ./isograph.config.json"
20
20
  },
21
21
  "dependencies": {
22
- "@isograph/disposable-types": "0.0.0-main-0eb53a17",
23
- "@isograph/react-disposable-state": "0.0.0-main-0eb53a17",
24
- "@isograph/reference-counted-pointer": "0.0.0-main-0eb53a17"
22
+ "@isograph/disposable-types": "0.0.0-main-cddcd660",
23
+ "@isograph/react-disposable-state": "0.0.0-main-cddcd660",
24
+ "@isograph/reference-counted-pointer": "0.0.0-main-cddcd660"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": "^18.0.0 || ^19.0.0"
@@ -1,6 +1,6 @@
1
1
  import { ReaderWithRefetchQueries } from '../core/entrypoint';
2
2
  import { stableCopy } from './cache';
3
- import { type Link } from './IsographEnvironment';
3
+ import { type StoreLink } from './IsographEnvironment';
4
4
  import { PromiseWrapper } from './PromiseWrapper';
5
5
  import type { StartUpdate } from './reader';
6
6
 
@@ -52,7 +52,7 @@ export type FragmentReference<
52
52
  readonly readerWithRefetchQueries: PromiseWrapper<
53
53
  ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>
54
54
  >;
55
- readonly root: Link;
55
+ readonly root: StoreLink;
56
56
  // TODO we potentially stably copy and stringify variables a lot!
57
57
  // So, we should employ interior mutability: pretend that fragent reference
58
58
  // is immutable, but actually store something like
@@ -15,6 +15,7 @@ import { LogFunction, WrappedLogFunction } from './logging';
15
15
  import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
16
16
  import { WithEncounteredRecords } from './read';
17
17
  import type { ReaderAst, StartUpdate } from './reader';
18
+ import type { Brand } from './brand';
18
19
 
19
20
  export type ComponentOrFieldName = string;
20
21
  export type StringifiedArgs = string;
@@ -38,7 +39,7 @@ export type FragmentSubscription<TReadFromStore extends UnknownTReadFromStore> =
38
39
  export type AnyChangesToRecordSubscription = {
39
40
  readonly kind: 'AnyChangesToRecord';
40
41
  readonly callback: () => void;
41
- readonly recordLink: Link;
42
+ readonly recordLink: StoreLink;
42
43
  };
43
44
 
44
45
  export type AnyRecordSubscription = {
@@ -77,18 +78,23 @@ export type IsographEnvironment = {
77
78
 
78
79
  export type MissingFieldHandler = (
79
80
  storeRecord: StoreRecord,
80
- root: Link,
81
+ root: StoreLink,
81
82
  fieldName: string,
82
83
  arguments_: { [index: string]: any } | null,
83
84
  variables: Variables | null,
84
- ) => Link | undefined;
85
+ ) => StoreLink | undefined;
85
86
 
86
87
  export type IsographNetworkFunction = (
87
88
  operation: IsographOperation | IsographPersistedOperation,
88
89
  variables: Variables,
89
90
  ) => Promise<any>;
90
91
 
91
- 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 = {
92
98
  readonly __link: DataId;
93
99
  readonly __typename: TypeName;
94
100
  };
@@ -103,7 +109,7 @@ export type DataTypeValue =
103
109
  | string
104
110
  | null
105
111
  // Singular linked fields:
106
- | Link
112
+ | StoreLink
107
113
  // Plural scalar and linked fields:
108
114
  | DataTypeValue[];
109
115
 
@@ -162,7 +168,7 @@ export function createIsographStore(): IsographStore {
162
168
  };
163
169
  }
164
170
 
165
- export function assertLink(link: DataTypeValue): Link | null | undefined {
171
+ export function assertLink(link: DataTypeValue): StoreLink | null | undefined {
166
172
  if (Array.isArray(link)) {
167
173
  throw new Error('Unexpected array');
168
174
  }
@@ -175,7 +181,7 @@ export function assertLink(link: DataTypeValue): Link | null | undefined {
175
181
  throw new Error('Invalid link');
176
182
  }
177
183
 
178
- export function getLink(maybeLink: DataTypeValue): Link | null {
184
+ export function getLink(maybeLink: DataTypeValue): StoreLink | null {
179
185
  if (
180
186
  maybeLink != null &&
181
187
  typeof maybeLink === 'object' &&
@@ -1,7 +1,7 @@
1
- import type { Link } from './IsographEnvironment';
1
+ import type { StoreLink } from './IsographEnvironment';
2
2
  import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader';
3
3
 
4
- export function mergeUsingReaderAst(
4
+ function mergeUsingReaderAst(
5
5
  field: ReaderScalarField | ReaderLinkedField,
6
6
  oldItem: unknown,
7
7
  newItem: unknown,
@@ -40,7 +40,7 @@ export function mergeUsingReaderAst(
40
40
  }
41
41
  }
42
42
 
43
- export function mergeArraysUsingReaderAst(
43
+ function mergeArraysUsingReaderAst(
44
44
  field: ReaderScalarField | ReaderLinkedField,
45
45
  oldItems: ReadonlyArray<unknown>,
46
46
  newItems: Array<unknown>,
@@ -101,9 +101,9 @@ export function mergeObjectsUsingReaderAst(
101
101
  case 'Link': {
102
102
  const key = field.alias;
103
103
  // @ts-expect-error
104
- const oldValue: Link = oldItemObject[key];
104
+ const oldValue: StoreLink = oldItemObject[key];
105
105
  // @ts-expect-error
106
- const newValue: Link = newItemObject[key];
106
+ const newValue: StoreLink = newItemObject[key];
107
107
 
108
108
  if (
109
109
  oldValue.__link !== newValue.__link ||
@@ -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;
package/src/core/cache.ts CHANGED
@@ -26,8 +26,8 @@ import {
26
26
  DataTypeValue,
27
27
  FragmentSubscription,
28
28
  getLink,
29
- Link,
30
29
  ROOT_ID,
30
+ StoreLink,
31
31
  StoreRecord,
32
32
  type IsographEnvironment,
33
33
  type TypeName,
@@ -157,7 +157,7 @@ export function normalizeData(
157
157
  normalizationAst: NormalizationAstNodes,
158
158
  networkResponse: NetworkResponseObject,
159
159
  variables: Variables,
160
- root: Link,
160
+ root: StoreLink,
161
161
  ): EncounteredIds {
162
162
  const encounteredIds: EncounteredIds = new Map();
163
163
 
@@ -205,7 +205,7 @@ export function subscribeToAnyChange(
205
205
 
206
206
  export function subscribeToAnyChangesToRecord(
207
207
  environment: IsographEnvironment,
208
- recordLink: Link,
208
+ recordLink: StoreLink,
209
209
  callback: () => void,
210
210
  ): () => void {
211
211
  const subscription = {
@@ -240,7 +240,7 @@ export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
240
240
 
241
241
  export function onNextChangeToRecord(
242
242
  environment: IsographEnvironment,
243
- recordLink: Link,
243
+ recordLink: StoreLink,
244
244
  ): Promise<void> {
245
245
  return new Promise((resolve) => {
246
246
  const unsubscribe = subscribeToAnyChangesToRecord(
@@ -259,11 +259,19 @@ export function onNextChangeToRecord(
259
259
  //
260
260
  // That's probably okay to ignore. We don't, however, want to prevent
261
261
  // updating other subscriptions if one subscription had missing data.
262
- function withErrorHandling<T>(f: (t: T) => void): (t: T) => void {
262
+ function withErrorHandling<T>(
263
+ environment: IsographEnvironment,
264
+ f: (t: T) => void,
265
+ ): (t: T) => void {
263
266
  return (t) => {
264
267
  try {
265
268
  return f(t);
266
- } catch {}
269
+ } catch (e) {
270
+ logMessage(environment, () => ({
271
+ kind: 'ErrorEncounteredInWithErrorHandling',
272
+ error: e,
273
+ }));
274
+ }
267
275
  };
268
276
  }
269
277
 
@@ -272,7 +280,7 @@ export function callSubscriptions(
272
280
  recordsEncounteredWhenNormalizing: EncounteredIds,
273
281
  ) {
274
282
  environment.subscriptions.forEach(
275
- withErrorHandling((subscription) => {
283
+ withErrorHandling(environment, (subscription) => {
276
284
  switch (subscription.kind) {
277
285
  case 'FragmentSubscription': {
278
286
  // TODO if there are multiple components subscribed to the same
@@ -387,7 +395,7 @@ function normalizeDataIntoRecord(
387
395
  normalizationAst: NormalizationAstNodes,
388
396
  networkResponseParentRecord: NetworkResponseObject,
389
397
  targetParentRecord: StoreRecord,
390
- targetParentRecordLink: Link,
398
+ targetParentRecordLink: StoreLink,
391
399
  variables: Variables,
392
400
  mutableEncounteredIds: EncounteredIds,
393
401
  ): RecordHasBeenUpdated {
@@ -492,7 +500,7 @@ function normalizeLinkedField(
492
500
  astNode: NormalizationLinkedField,
493
501
  networkResponseParentRecord: NetworkResponseObject,
494
502
  targetParentRecord: StoreRecord,
495
- targetParentRecordLink: Link,
503
+ targetParentRecordLink: StoreLink,
496
504
  variables: Variables,
497
505
  mutableEncounteredIds: EncounteredIds,
498
506
  ): RecordHasBeenUpdated {
@@ -517,7 +525,7 @@ function normalizeLinkedField(
517
525
 
518
526
  if (Array.isArray(networkResponseData)) {
519
527
  // TODO check astNode.plural or the like
520
- const dataIds: (Link | null)[] = [];
528
+ const dataIds: (StoreLink | null)[] = [];
521
529
  for (let i = 0; i < networkResponseData.length; i++) {
522
530
  const networkResponseObject = networkResponseData[i];
523
531
  if (networkResponseObject == null) {
@@ -588,7 +596,7 @@ function normalizeInlineFragment(
588
596
  astNode: NormalizationInlineFragment,
589
597
  networkResponseParentRecord: NetworkResponseObject,
590
598
  targetParentRecord: StoreRecord,
591
- targetParentRecordLink: Link,
599
+ targetParentRecordLink: StoreLink,
592
600
  variables: Variables,
593
601
  mutableEncounteredIds: EncounteredIds,
594
602
  ): RecordHasBeenUpdated {
@@ -610,7 +618,7 @@ function normalizeInlineFragment(
610
618
 
611
619
  function dataIdsAreTheSame(
612
620
  existingValue: DataTypeValue,
613
- newDataIds: (Link | null)[],
621
+ newDataIds: (StoreLink | null)[],
614
622
  ): boolean {
615
623
  if (Array.isArray(existingValue)) {
616
624
  if (newDataIds.length !== existingValue.length) {
@@ -635,7 +643,7 @@ function normalizeNetworkResponseObject(
635
643
  environment: IsographEnvironment,
636
644
  astNode: NormalizationLinkedField,
637
645
  networkResponseData: NetworkResponseObject,
638
- targetParentRecordLink: Link,
646
+ targetParentRecordLink: StoreLink,
639
647
  variables: Variables,
640
648
  index: number | null,
641
649
  mutableEncounteredIds: EncounteredIds,
@@ -830,7 +838,7 @@ export const THIRD_SPLIT_KEY = '__';
830
838
 
831
839
  // Returns a key to look up an item in the store
832
840
  function getDataIdOfNetworkResponse(
833
- parentRecordLink: Link,
841
+ parentRecordLink: StoreLink,
834
842
  dataToNormalize: NetworkResponseObject,
835
843
  astNode: NormalizationLinkedField,
836
844
  variables: Variables,
package/src/core/check.ts CHANGED
@@ -4,7 +4,7 @@ import { Variables } from './FragmentReference';
4
4
  import {
5
5
  getLink,
6
6
  IsographEnvironment,
7
- Link,
7
+ StoreLink,
8
8
  StoreRecord,
9
9
  } from './IsographEnvironment';
10
10
  import { logMessage } from './logging';
@@ -30,14 +30,14 @@ export type CheckResult =
30
30
  }
31
31
  | {
32
32
  kind: 'MissingData';
33
- record: Link;
33
+ record: StoreLink;
34
34
  };
35
35
 
36
36
  export function check(
37
37
  environment: IsographEnvironment,
38
38
  normalizationAst: NormalizationAstNodes,
39
39
  variables: Variables,
40
- root: Link,
40
+ root: StoreLink,
41
41
  ): CheckResult {
42
42
  const recordsById = (environment.store[root.__typename] ??= {});
43
43
  const newStoreRecord = (recordsById[root.__link] ??= {});
@@ -61,7 +61,7 @@ function checkFromRecord(
61
61
  normalizationAst: NormalizationAstNodes,
62
62
  variables: Variables,
63
63
  record: StoreRecord,
64
- recordLink: Link,
64
+ recordLink: StoreLink,
65
65
  ): CheckResult {
66
66
  normalizationAstLoop: for (const normalizationAstNode of normalizationAst) {
67
67
  switch (normalizationAstNode.kind) {
@@ -7,14 +7,14 @@ import {
7
7
  IsographEnvironment,
8
8
  IsographStore,
9
9
  StoreRecord,
10
- type Link,
10
+ type StoreLink,
11
11
  type TypeName,
12
12
  } from './IsographEnvironment';
13
13
 
14
14
  export type RetainedQuery = {
15
15
  readonly normalizationAst: NormalizationAstNodes;
16
16
  readonly variables: {};
17
- readonly root: Link;
17
+ readonly root: StoreLink;
18
18
  };
19
19
 
20
20
  export type DidUnretainSomeQuery = boolean;
@@ -117,7 +117,7 @@ function recordReachableIdsFromRecord(
117
117
  const linkKey = getParentRecordKey(selection, variables ?? {});
118
118
  const linkedFieldOrFields = currentRecord[linkKey];
119
119
 
120
- const links: Link[] = [];
120
+ const links: StoreLink[] = [];
121
121
  if (Array.isArray(linkedFieldOrFields)) {
122
122
  for (const maybeLink of linkedFieldOrFields) {
123
123
  const link = assertLink(maybeLink);
@@ -11,7 +11,7 @@ import {
11
11
  IsographEnvironment,
12
12
  IsographStore,
13
13
  StoreRecord,
14
- type Link,
14
+ type StoreLink,
15
15
  } from './IsographEnvironment';
16
16
  import { ReadDataResult } from './read';
17
17
  import { Arguments } from './util';
@@ -38,7 +38,7 @@ export type LogMessage =
38
38
  | {
39
39
  kind: 'ComponentRerendered';
40
40
  componentName: string;
41
- rootLink: Link;
41
+ rootLink: StoreLink;
42
42
  }
43
43
  | {
44
44
  kind: 'MakeNetworkRequest';
@@ -61,7 +61,7 @@ export type LogMessage =
61
61
  }
62
62
  | {
63
63
  kind: 'MissingFieldHandlerCalled';
64
- root: Link;
64
+ root: StoreLink;
65
65
  storeRecord: StoreRecord;
66
66
  fieldName: string;
67
67
  arguments: Arguments | null;
@@ -71,7 +71,7 @@ export type LogMessage =
71
71
  kind: 'DoneReading';
72
72
  response: ReadDataResult<any>;
73
73
  fieldName: string;
74
- root: Link;
74
+ root: StoreLink;
75
75
  }
76
76
  | {
77
77
  kind: 'NonEntrypointReceived';
@@ -83,6 +83,18 @@ export type LogMessage =
83
83
  }
84
84
  | {
85
85
  kind: 'EnvironmentCreated';
86
+ }
87
+ | {
88
+ kind: 'StartUpdateError';
89
+ error: any;
90
+ }
91
+ | {
92
+ kind: 'StartUpdateComplete';
93
+ updatedIds: EncounteredIds;
94
+ }
95
+ | {
96
+ kind: 'ErrorEncounteredInWithErrorHandling';
97
+ error: any;
86
98
  };
87
99
 
88
100
  export type LogFunction = (logMessage: LogMessage) => void;
@@ -20,7 +20,7 @@ import {
20
20
  retainQuery,
21
21
  unretainQuery,
22
22
  } from './garbageCollection';
23
- import { IsographEnvironment, Link, ROOT_ID } from './IsographEnvironment';
23
+ import { IsographEnvironment, ROOT_ID, StoreLink } from './IsographEnvironment';
24
24
  import { logMessage } from './logging';
25
25
  import {
26
26
  AnyError,
@@ -260,7 +260,7 @@ function readDataForOnComplete<
260
260
  >(
261
261
  artifact: TArtifact,
262
262
  environment: IsographEnvironment,
263
- root: Link,
263
+ root: StoreLink,
264
264
  variables: ExtractParameters<TReadFromStore>,
265
265
  readerWithRefetchQueries:
266
266
  | ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>