@isograph/react 0.3.1 → 0.4.1
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.
- package/.turbo/turbo-compile-libs.log +5 -0
- package/dist/core/FragmentReference.d.ts +5 -5
- package/dist/core/FragmentReference.d.ts.map +1 -1
- package/dist/core/IsographEnvironment.d.ts +15 -10
- package/dist/core/IsographEnvironment.d.ts.map +1 -1
- package/dist/core/PromiseWrapper.d.ts +4 -4
- package/dist/core/PromiseWrapper.d.ts.map +1 -1
- package/dist/core/PromiseWrapper.js +2 -9
- package/dist/core/areEqualWithDeepComparison.d.ts +1 -3
- package/dist/core/areEqualWithDeepComparison.d.ts.map +1 -1
- package/dist/core/areEqualWithDeepComparison.js +0 -2
- package/dist/core/brand.d.ts +2 -0
- package/dist/core/brand.d.ts.map +1 -0
- package/dist/core/brand.js +2 -0
- package/dist/core/cache.d.ts +7 -6
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +57 -36
- package/dist/core/check.d.ts +3 -3
- package/dist/core/check.d.ts.map +1 -1
- package/dist/core/componentCache.d.ts.map +1 -1
- package/dist/core/componentCache.js +3 -2
- package/dist/core/entrypoint.d.ts +20 -2
- package/dist/core/entrypoint.d.ts.map +1 -1
- package/dist/core/garbageCollection.d.ts +2 -2
- package/dist/core/garbageCollection.d.ts.map +1 -1
- package/dist/core/logging.d.ts +13 -4
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.d.ts +3 -3
- package/dist/core/makeNetworkRequest.d.ts.map +1 -1
- package/dist/core/makeNetworkRequest.js +15 -15
- package/dist/core/read.d.ts +8 -8
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +98 -29
- package/dist/core/reader.d.ts +12 -8
- package/dist/core/reader.d.ts.map +1 -1
- package/dist/core/startUpdate.d.ts +7 -4
- package/dist/core/startUpdate.d.ts.map +1 -1
- package/dist/core/startUpdate.js +153 -5
- package/dist/core/util.d.ts +2 -1
- package/dist/core/util.d.ts.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useConnectionSpecPagination.js +1 -1
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +1 -1
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts.map +1 -1
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +1 -1
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +1 -1
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
- package/dist/loadable-hooks/useImperativeLoadableField.js +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
- package/dist/loadable-hooks/useSkipLimitPagination.js +1 -1
- package/dist/react/FragmentReader.d.ts +7 -13
- package/dist/react/FragmentReader.d.ts.map +1 -1
- package/dist/react/FragmentReader.js +3 -30
- package/dist/react/FragmentRenderer.d.ts +15 -0
- package/dist/react/FragmentRenderer.d.ts.map +1 -0
- package/dist/react/FragmentRenderer.js +35 -0
- package/dist/react/LoadableFieldReader.d.ts +12 -0
- package/dist/react/LoadableFieldReader.d.ts.map +1 -0
- package/dist/react/LoadableFieldReader.js +10 -0
- package/dist/react/LoadableFieldRenderer.d.ts +13 -0
- package/dist/react/LoadableFieldRenderer.d.ts.map +1 -0
- package/dist/react/LoadableFieldRenderer.js +37 -0
- package/dist/react/useImperativeReference.d.ts.map +1 -1
- package/dist/react/useImperativeReference.js +6 -6
- package/dist/react/useResult.d.ts.map +1 -1
- package/dist/react/useResult.js +1 -1
- package/package.json +4 -5
- package/src/core/FragmentReference.ts +16 -7
- package/src/core/IsographEnvironment.ts +20 -10
- package/src/core/PromiseWrapper.ts +13 -16
- package/src/core/areEqualWithDeepComparison.ts +5 -5
- package/src/core/brand.ts +18 -0
- package/src/core/cache.ts +74 -51
- package/src/core/check.ts +4 -4
- package/src/core/componentCache.ts +7 -2
- package/src/core/entrypoint.ts +32 -5
- package/src/core/garbageCollection.ts +3 -3
- package/src/core/logging.ts +16 -4
- package/src/core/makeNetworkRequest.ts +48 -23
- package/src/core/read.ts +153 -48
- package/src/core/reader.ts +11 -7
- package/src/core/startUpdate.ts +313 -7
- package/src/core/util.ts +4 -2
- package/src/index.ts +11 -3
- package/src/loadable-hooks/useConnectionSpecPagination.ts +1 -0
- package/src/loadable-hooks/useImperativeExposedMutationField.ts +2 -2
- package/src/loadable-hooks/useImperativeLoadableField.ts +2 -2
- package/src/loadable-hooks/useSkipLimitPagination.ts +1 -0
- package/src/react/FragmentReader.tsx +23 -39
- package/src/react/FragmentRenderer.tsx +46 -0
- package/src/react/LoadableFieldReader.tsx +40 -0
- package/src/react/LoadableFieldRenderer.tsx +41 -0
- package/src/react/useImperativeReference.ts +9 -8
- package/src/react/useResult.ts +1 -0
- package/src/tests/__isograph/Economist/link/output_type.ts +2 -0
- package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +28 -0
- package/src/tests/__isograph/Node/link/output_type.ts +3 -0
- package/src/tests/__isograph/Query/linkedUpdate/entrypoint.ts +31 -0
- package/src/tests/__isograph/Query/linkedUpdate/normalization_ast.ts +95 -0
- package/src/tests/__isograph/Query/linkedUpdate/output_type.ts +3 -0
- package/src/tests/__isograph/Query/linkedUpdate/param_type.ts +51 -0
- package/src/tests/__isograph/Query/linkedUpdate/query_text.ts +20 -0
- package/src/tests/__isograph/Query/linkedUpdate/resolver_reader.ts +93 -0
- package/src/tests/__isograph/Query/meName/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/meName/query_text.ts +1 -1
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/meNameSuccessor/query_text.ts +1 -1
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/nodeField/query_text.ts +1 -1
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -0
- package/src/tests/__isograph/Query/startUpdate/entrypoint.ts +31 -0
- package/src/tests/__isograph/Query/startUpdate/normalization_ast.ts +51 -0
- package/src/tests/__isograph/Query/startUpdate/output_type.ts +3 -0
- package/src/tests/__isograph/Query/startUpdate/param_type.ts +26 -0
- package/src/tests/__isograph/Query/startUpdate/parameters_type.ts +3 -0
- package/src/tests/__isograph/Query/startUpdate/query_text.ts +11 -0
- package/src/tests/__isograph/Query/startUpdate/resolver_reader.ts +55 -0
- package/src/tests/__isograph/Query/subquery/entrypoint.ts +4 -1
- package/src/tests/__isograph/Query/subquery/query_text.ts +1 -1
- package/src/tests/__isograph/Query/subquery/resolver_reader.ts +2 -0
- package/src/tests/__isograph/iso.ts +21 -1
- package/src/tests/__isograph/tsconfig.json +8 -0
- package/src/tests/normalizeData.test.ts +0 -1
- package/src/tests/startUpdate.test.ts +205 -0
- package/.turbo/turbo-compile-typescript.log +0 -4
|
@@ -12,15 +12,15 @@ function useImperativeReference(entrypoint) {
|
|
|
12
12
|
return {
|
|
13
13
|
fragmentReference: state !== react_disposable_state_1.UNASSIGNED_STATE ? state : null,
|
|
14
14
|
loadFragmentReference: (variables, fetchOptions) => {
|
|
15
|
-
const
|
|
15
|
+
const readerWithRefetchQueries = entrypoint.readerWithRefetchQueries.kind ===
|
|
16
|
+
'ReaderWithRefetchQueriesLoader'
|
|
17
|
+
? (0, PromiseWrapper_1.wrapPromise)(entrypoint.readerWithRefetchQueries.loader())
|
|
18
|
+
: (0, PromiseWrapper_1.wrapResolvedValue)(entrypoint.readerWithRefetchQueries);
|
|
19
|
+
const [networkRequest, disposeNetworkRequest] = (0, makeNetworkRequest_1.maybeMakeNetworkRequest)(environment, entrypoint, variables, readerWithRefetchQueries, fetchOptions !== null && fetchOptions !== void 0 ? fetchOptions : null);
|
|
16
20
|
setState([
|
|
17
21
|
{
|
|
18
22
|
kind: 'FragmentReference',
|
|
19
|
-
readerWithRefetchQueries
|
|
20
|
-
kind: 'ReaderWithRefetchQueries',
|
|
21
|
-
readerArtifact: entrypoint.readerWithRefetchQueries.readerArtifact,
|
|
22
|
-
nestedRefetchQueries: entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
|
|
23
|
-
}),
|
|
23
|
+
readerWithRefetchQueries,
|
|
24
24
|
root: { __link: IsographEnvironment_1.ROOT_ID, __typename: entrypoint.concreteType },
|
|
25
25
|
variables,
|
|
26
26
|
networkRequest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResult.d.ts","sourceRoot":"","sources":["../../src/react/useResult.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,cAAc,EAEf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,2BAA2B,EAC5B,MAAM,cAAc,CAAC;AAKtB,wBAAgB,SAAS,CACvB,cAAc,SAAS,qBAAqB,EAC5C,iBAAiB,EAEjB,iBAAiB,EAAE,iBAAiB,CAAC,cAAc,EAAE,iBAAiB,CAAC,EACvE,4BAA4B,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,GAAG,IAAI,GACzE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"useResult.d.ts","sourceRoot":"","sources":["../../src/react/useResult.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,cAAc,EAEf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,2BAA2B,EAC5B,MAAM,cAAc,CAAC;AAKtB,wBAAgB,SAAS,CACvB,cAAc,SAAS,qBAAqB,EAC5C,iBAAiB,EAEjB,iBAAiB,EAAE,iBAAiB,CAAC,cAAc,EAAE,iBAAiB,CAAC,EACvE,4BAA4B,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,GAAG,IAAI,GACzE,iBAAiB,CA+CnB;AAED,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,EACzC,qBAAqB,EAAE,2BAA2B,QAWnD"}
|
package/dist/react/useResult.js
CHANGED
|
@@ -22,7 +22,7 @@ function useResult(fragmentReference, partialNetworkRequestOptions) {
|
|
|
22
22
|
const data = (0, useReadAndSubscribe_1.useReadAndSubscribe)(fragmentReference, networkRequestOptions, readerWithRefetchQueries.readerArtifact.readerAst);
|
|
23
23
|
const param = Object.assign({ data: data, parameters: fragmentReference.variables }, (readerWithRefetchQueries.readerArtifact.hasUpdatable
|
|
24
24
|
? {
|
|
25
|
-
startUpdate: (0, startUpdate_1.getOrCreateCachedStartUpdate)(environment, fragmentReference, readerWithRefetchQueries.readerArtifact.fieldName),
|
|
25
|
+
startUpdate: (0, startUpdate_1.getOrCreateCachedStartUpdate)(environment, fragmentReference, readerWithRefetchQueries.readerArtifact.fieldName, networkRequestOptions),
|
|
26
26
|
}
|
|
27
27
|
: undefined));
|
|
28
28
|
return readerWithRefetchQueries.readerArtifact.resolver(param);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isograph/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Use Isograph with React",
|
|
5
5
|
"homepage": "https://isograph.dev",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
"author": "Isograph Labs",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"compile-
|
|
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
18
|
"tsc-force": "tsc --build --clean && tsc --build --force",
|
|
19
|
-
"iso": "cross-env ../../target/debug/isograph_cli --config ./isograph.config.json"
|
|
20
|
-
"iso-watch": "cross-env ../../target/debug/isograph_cli --config ./isograph.config.json --watch"
|
|
19
|
+
"iso": "cross-env ISO_PRINT_ABSOLUTE_FILEPATH=1 ../../target/debug/isograph_cli --config ./isograph.config.json"
|
|
21
20
|
},
|
|
22
21
|
"dependencies": {
|
|
23
22
|
"@isograph/disposable-types": "*",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReaderWithRefetchQueries } from '../core/entrypoint';
|
|
2
2
|
import { stableCopy } from './cache';
|
|
3
|
-
import { type
|
|
3
|
+
import { type StoreLink } from './IsographEnvironment';
|
|
4
4
|
import { PromiseWrapper } from './PromiseWrapper';
|
|
5
5
|
import type { StartUpdate } from './reader';
|
|
6
6
|
|
|
@@ -35,11 +35,14 @@ export type ExtractParameters<T> = T extends {
|
|
|
35
35
|
? P
|
|
36
36
|
: Variables;
|
|
37
37
|
|
|
38
|
-
export type ExtractStartUpdate<
|
|
39
|
-
T
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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;
|
|
43
46
|
|
|
44
47
|
export type FragmentReference<
|
|
45
48
|
TReadFromStore extends UnknownTReadFromStore,
|
|
@@ -49,7 +52,13 @@ export type FragmentReference<
|
|
|
49
52
|
readonly readerWithRefetchQueries: PromiseWrapper<
|
|
50
53
|
ReaderWithRefetchQueries<TReadFromStore, TClientFieldValue>
|
|
51
54
|
>;
|
|
52
|
-
readonly root:
|
|
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.
|
|
53
62
|
readonly variables: ExtractParameters<TReadFromStore>;
|
|
54
63
|
readonly networkRequest: PromiseWrapper<void, any>;
|
|
55
64
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ParentCache } from '@isograph/react-disposable-state';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
IsographEntrypoint,
|
|
4
|
+
IsographOperation,
|
|
5
|
+
IsographPersistedOperation,
|
|
6
|
+
} from './entrypoint';
|
|
3
7
|
import {
|
|
4
8
|
FragmentReference,
|
|
5
9
|
Variables,
|
|
@@ -11,6 +15,7 @@ import { LogFunction, WrappedLogFunction } from './logging';
|
|
|
11
15
|
import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
|
|
12
16
|
import { WithEncounteredRecords } from './read';
|
|
13
17
|
import type { ReaderAst, StartUpdate } from './reader';
|
|
18
|
+
import type { Brand } from './brand';
|
|
14
19
|
|
|
15
20
|
export type ComponentOrFieldName = string;
|
|
16
21
|
export type StringifiedArgs = string;
|
|
@@ -26,7 +31,7 @@ export type FragmentSubscription<TReadFromStore extends UnknownTReadFromStore> =
|
|
|
26
31
|
newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
|
27
32
|
) => void;
|
|
28
33
|
/** The value read out from the previous call to readButDoNotEvaluate */
|
|
29
|
-
|
|
34
|
+
encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
|
|
30
35
|
readonly fragmentReference: FragmentReference<TReadFromStore, any>;
|
|
31
36
|
readonly readerAst: ReaderAst<TReadFromStore>;
|
|
32
37
|
};
|
|
@@ -34,7 +39,7 @@ export type FragmentSubscription<TReadFromStore extends UnknownTReadFromStore> =
|
|
|
34
39
|
export type AnyChangesToRecordSubscription = {
|
|
35
40
|
readonly kind: 'AnyChangesToRecord';
|
|
36
41
|
readonly callback: () => void;
|
|
37
|
-
readonly recordLink:
|
|
42
|
+
readonly recordLink: StoreLink;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
export type AnyRecordSubscription = {
|
|
@@ -73,18 +78,23 @@ export type IsographEnvironment = {
|
|
|
73
78
|
|
|
74
79
|
export type MissingFieldHandler = (
|
|
75
80
|
storeRecord: StoreRecord,
|
|
76
|
-
root:
|
|
81
|
+
root: StoreLink,
|
|
77
82
|
fieldName: string,
|
|
78
83
|
arguments_: { [index: string]: any } | null,
|
|
79
84
|
variables: Variables | null,
|
|
80
|
-
) =>
|
|
85
|
+
) => StoreLink | undefined;
|
|
81
86
|
|
|
82
87
|
export type IsographNetworkFunction = (
|
|
83
|
-
|
|
88
|
+
operation: IsographOperation | IsographPersistedOperation,
|
|
84
89
|
variables: Variables,
|
|
85
90
|
) => Promise<any>;
|
|
86
91
|
|
|
87
|
-
export
|
|
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 = {
|
|
88
98
|
readonly __link: DataId;
|
|
89
99
|
readonly __typename: TypeName;
|
|
90
100
|
};
|
|
@@ -99,7 +109,7 @@ export type DataTypeValue =
|
|
|
99
109
|
| string
|
|
100
110
|
| null
|
|
101
111
|
// Singular linked fields:
|
|
102
|
-
|
|
|
112
|
+
| StoreLink
|
|
103
113
|
// Plural scalar and linked fields:
|
|
104
114
|
| DataTypeValue[];
|
|
105
115
|
|
|
@@ -158,7 +168,7 @@ export function createIsographStore(): IsographStore {
|
|
|
158
168
|
};
|
|
159
169
|
}
|
|
160
170
|
|
|
161
|
-
export function assertLink(link: DataTypeValue):
|
|
171
|
+
export function assertLink(link: DataTypeValue): StoreLink | null | undefined {
|
|
162
172
|
if (Array.isArray(link)) {
|
|
163
173
|
throw new Error('Unexpected array');
|
|
164
174
|
}
|
|
@@ -171,7 +181,7 @@ export function assertLink(link: DataTypeValue): Link | null | undefined {
|
|
|
171
181
|
throw new Error('Invalid link');
|
|
172
182
|
}
|
|
173
183
|
|
|
174
|
-
export function getLink(maybeLink: DataTypeValue):
|
|
184
|
+
export function getLink(maybeLink: DataTypeValue): StoreLink | null {
|
|
175
185
|
if (
|
|
176
186
|
maybeLink != null &&
|
|
177
187
|
typeof maybeLink === 'object' &&
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type AnyError = any;
|
|
2
2
|
|
|
3
|
-
export const NOT_SET
|
|
3
|
+
export const NOT_SET = Symbol('NOT_SET');
|
|
4
4
|
export type NotSet = typeof NOT_SET;
|
|
5
5
|
|
|
6
6
|
export type Result<T, E> =
|
|
@@ -18,31 +18,32 @@ export 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>(
|
|
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
|
-
|
|
31
|
-
wrapper.result = { kind: 'Ok', value: v as any };
|
|
32
|
+
wrapper.result = { kind: 'Ok', value: v };
|
|
32
33
|
})
|
|
33
34
|
.catch((e) => {
|
|
34
|
-
|
|
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>(
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StoreLink } from './IsographEnvironment';
|
|
2
2
|
import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader';
|
|
3
3
|
|
|
4
|
-
|
|
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
|
-
|
|
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:
|
|
104
|
+
const oldValue: StoreLink = oldItemObject[key];
|
|
105
105
|
// @ts-expect-error
|
|
106
|
-
const newValue:
|
|
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
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
NormalizationInlineFragment,
|
|
9
9
|
NormalizationLinkedField,
|
|
10
10
|
NormalizationScalarField,
|
|
11
|
-
RefetchQueryNormalizationArtifactWrapper,
|
|
12
11
|
type NormalizationAst,
|
|
13
12
|
type NormalizationAstLoader,
|
|
14
13
|
type NormalizationAstNodes,
|
|
@@ -27,15 +26,15 @@ import {
|
|
|
27
26
|
DataTypeValue,
|
|
28
27
|
FragmentSubscription,
|
|
29
28
|
getLink,
|
|
30
|
-
Link,
|
|
31
29
|
ROOT_ID,
|
|
30
|
+
StoreLink,
|
|
32
31
|
StoreRecord,
|
|
33
32
|
type IsographEnvironment,
|
|
34
33
|
type TypeName,
|
|
35
34
|
} from './IsographEnvironment';
|
|
36
35
|
import { logMessage } from './logging';
|
|
37
36
|
import { maybeMakeNetworkRequest } from './makeNetworkRequest';
|
|
38
|
-
import { wrapResolvedValue } from './PromiseWrapper';
|
|
37
|
+
import { wrapPromise, wrapResolvedValue } from './PromiseWrapper';
|
|
39
38
|
import { readButDoNotEvaluate, WithEncounteredRecords } from './read';
|
|
40
39
|
import { ReaderLinkedField, ReaderScalarField, type ReaderAst } from './reader';
|
|
41
40
|
import { Argument, ArgumentValue } from './util';
|
|
@@ -93,15 +92,31 @@ export function getOrCreateCacheForArtifact<
|
|
|
93
92
|
variables: ExtractParameters<TReadFromStore>,
|
|
94
93
|
fetchOptions?: FetchOptions<TClientFieldValue>,
|
|
95
94
|
): ParentCache<FragmentReference<TReadFromStore, TClientFieldValue>> {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
let cacheKey = '';
|
|
96
|
+
switch (entrypoint.networkRequestInfo.operation.kind) {
|
|
97
|
+
case 'Operation':
|
|
98
|
+
cacheKey =
|
|
99
|
+
entrypoint.networkRequestInfo.operation.text +
|
|
100
|
+
JSON.stringify(stableCopy(variables));
|
|
101
|
+
break;
|
|
102
|
+
case 'PersistedOperation':
|
|
103
|
+
cacheKey =
|
|
104
|
+
entrypoint.networkRequestInfo.operation.operationId +
|
|
105
|
+
JSON.stringify(stableCopy(variables));
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
99
108
|
const factory = () => {
|
|
109
|
+
const readerWithRefetchQueries =
|
|
110
|
+
entrypoint.readerWithRefetchQueries.kind ===
|
|
111
|
+
'ReaderWithRefetchQueriesLoader'
|
|
112
|
+
? wrapPromise(entrypoint.readerWithRefetchQueries.loader())
|
|
113
|
+
: wrapResolvedValue(entrypoint.readerWithRefetchQueries);
|
|
100
114
|
const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest(
|
|
101
115
|
environment,
|
|
102
116
|
entrypoint,
|
|
103
117
|
variables,
|
|
104
|
-
|
|
118
|
+
readerWithRefetchQueries,
|
|
119
|
+
fetchOptions ?? null,
|
|
105
120
|
);
|
|
106
121
|
|
|
107
122
|
const itemCleanupPair: ItemCleanupPair<
|
|
@@ -109,12 +124,7 @@ export function getOrCreateCacheForArtifact<
|
|
|
109
124
|
> = [
|
|
110
125
|
{
|
|
111
126
|
kind: 'FragmentReference',
|
|
112
|
-
readerWithRefetchQueries
|
|
113
|
-
kind: 'ReaderWithRefetchQueries',
|
|
114
|
-
readerArtifact: entrypoint.readerWithRefetchQueries.readerArtifact,
|
|
115
|
-
nestedRefetchQueries:
|
|
116
|
-
entrypoint.readerWithRefetchQueries.nestedRefetchQueries,
|
|
117
|
-
}),
|
|
127
|
+
readerWithRefetchQueries,
|
|
118
128
|
root: { __link: ROOT_ID, __typename: entrypoint.concreteType },
|
|
119
129
|
variables,
|
|
120
130
|
networkRequest: networkRequest,
|
|
@@ -147,8 +157,7 @@ export function normalizeData(
|
|
|
147
157
|
normalizationAst: NormalizationAstNodes,
|
|
148
158
|
networkResponse: NetworkResponseObject,
|
|
149
159
|
variables: Variables,
|
|
150
|
-
|
|
151
|
-
root: Link,
|
|
160
|
+
root: StoreLink,
|
|
152
161
|
): EncounteredIds {
|
|
153
162
|
const encounteredIds: EncounteredIds = new Map();
|
|
154
163
|
|
|
@@ -169,7 +178,6 @@ export function normalizeData(
|
|
|
169
178
|
newStoreRecord,
|
|
170
179
|
root,
|
|
171
180
|
variables,
|
|
172
|
-
nestedRefetchQueries,
|
|
173
181
|
encounteredIds,
|
|
174
182
|
);
|
|
175
183
|
|
|
@@ -197,7 +205,7 @@ export function subscribeToAnyChange(
|
|
|
197
205
|
|
|
198
206
|
export function subscribeToAnyChangesToRecord(
|
|
199
207
|
environment: IsographEnvironment,
|
|
200
|
-
recordLink:
|
|
208
|
+
recordLink: StoreLink,
|
|
201
209
|
callback: () => void,
|
|
202
210
|
): () => void {
|
|
203
211
|
const subscription = {
|
|
@@ -232,7 +240,7 @@ export function subscribe<TReadFromStore extends UnknownTReadFromStore>(
|
|
|
232
240
|
|
|
233
241
|
export function onNextChangeToRecord(
|
|
234
242
|
environment: IsographEnvironment,
|
|
235
|
-
recordLink:
|
|
243
|
+
recordLink: StoreLink,
|
|
236
244
|
): Promise<void> {
|
|
237
245
|
return new Promise((resolve) => {
|
|
238
246
|
const unsubscribe = subscribeToAnyChangesToRecord(
|
|
@@ -251,20 +259,28 @@ export function onNextChangeToRecord(
|
|
|
251
259
|
//
|
|
252
260
|
// That's probably okay to ignore. We don't, however, want to prevent
|
|
253
261
|
// updating other subscriptions if one subscription had missing data.
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
262
|
+
function logAnyError(
|
|
263
|
+
environment: IsographEnvironment,
|
|
264
|
+
context: any,
|
|
265
|
+
f: () => void,
|
|
266
|
+
) {
|
|
267
|
+
try {
|
|
268
|
+
f();
|
|
269
|
+
} catch (e) {
|
|
270
|
+
logMessage(environment, () => ({
|
|
271
|
+
kind: 'ErrorEncounteredInWithErrorHandling',
|
|
272
|
+
error: e,
|
|
273
|
+
context,
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
260
276
|
}
|
|
261
277
|
|
|
262
|
-
function callSubscriptions(
|
|
278
|
+
export function callSubscriptions(
|
|
263
279
|
environment: IsographEnvironment,
|
|
264
280
|
recordsEncounteredWhenNormalizing: EncounteredIds,
|
|
265
281
|
) {
|
|
266
|
-
environment.subscriptions.forEach(
|
|
267
|
-
|
|
282
|
+
environment.subscriptions.forEach((subscription) =>
|
|
283
|
+
logAnyError(environment, { situation: 'calling subscriptions' }, () => {
|
|
268
284
|
switch (subscription.kind) {
|
|
269
285
|
case 'FragmentSubscription': {
|
|
270
286
|
// TODO if there are multiple components subscribed to the same
|
|
@@ -313,13 +329,25 @@ function callSubscriptions(
|
|
|
313
329
|
}));
|
|
314
330
|
|
|
315
331
|
if (mergedItem !== subscription.encounteredDataAndRecords.item) {
|
|
316
|
-
|
|
332
|
+
logAnyError(
|
|
333
|
+
environment,
|
|
334
|
+
{ situation: 'calling FragmentSubscription callback' },
|
|
335
|
+
() => {
|
|
336
|
+
subscription.callback(newEncounteredDataAndRecords);
|
|
337
|
+
},
|
|
338
|
+
);
|
|
339
|
+
subscription.encounteredDataAndRecords =
|
|
340
|
+
newEncounteredDataAndRecords;
|
|
317
341
|
}
|
|
318
342
|
}
|
|
319
343
|
return;
|
|
320
344
|
}
|
|
321
345
|
case 'AnyRecords': {
|
|
322
|
-
|
|
346
|
+
logAnyError(
|
|
347
|
+
environment,
|
|
348
|
+
{ situation: 'calling AnyRecords callback' },
|
|
349
|
+
() => subscription.callback(),
|
|
350
|
+
);
|
|
323
351
|
return;
|
|
324
352
|
}
|
|
325
353
|
case 'AnyChangesToRecord': {
|
|
@@ -328,7 +356,11 @@ function callSubscriptions(
|
|
|
328
356
|
.get(subscription.recordLink.__typename)
|
|
329
357
|
?.has(subscription.recordLink.__link)
|
|
330
358
|
) {
|
|
331
|
-
|
|
359
|
+
logAnyError(
|
|
360
|
+
environment,
|
|
361
|
+
{ situation: 'calling AnyChangesToRecord callback' },
|
|
362
|
+
() => subscription.callback(),
|
|
363
|
+
);
|
|
332
364
|
}
|
|
333
365
|
return;
|
|
334
366
|
}
|
|
@@ -379,9 +411,8 @@ function normalizeDataIntoRecord(
|
|
|
379
411
|
normalizationAst: NormalizationAstNodes,
|
|
380
412
|
networkResponseParentRecord: NetworkResponseObject,
|
|
381
413
|
targetParentRecord: StoreRecord,
|
|
382
|
-
targetParentRecordLink:
|
|
414
|
+
targetParentRecordLink: StoreLink,
|
|
383
415
|
variables: Variables,
|
|
384
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
385
416
|
mutableEncounteredIds: EncounteredIds,
|
|
386
417
|
): RecordHasBeenUpdated {
|
|
387
418
|
let recordHasBeenUpdated = false;
|
|
@@ -406,7 +437,6 @@ function normalizeDataIntoRecord(
|
|
|
406
437
|
targetParentRecord,
|
|
407
438
|
targetParentRecordLink,
|
|
408
439
|
variables,
|
|
409
|
-
nestedRefetchQueries,
|
|
410
440
|
mutableEncounteredIds,
|
|
411
441
|
);
|
|
412
442
|
recordHasBeenUpdated =
|
|
@@ -421,7 +451,6 @@ function normalizeDataIntoRecord(
|
|
|
421
451
|
targetParentRecord,
|
|
422
452
|
targetParentRecordLink,
|
|
423
453
|
variables,
|
|
424
|
-
nestedRefetchQueries,
|
|
425
454
|
mutableEncounteredIds,
|
|
426
455
|
);
|
|
427
456
|
recordHasBeenUpdated =
|
|
@@ -437,7 +466,7 @@ function normalizeDataIntoRecord(
|
|
|
437
466
|
}
|
|
438
467
|
}
|
|
439
468
|
if (recordHasBeenUpdated) {
|
|
440
|
-
let encounteredRecordsIds =
|
|
469
|
+
let encounteredRecordsIds = insertEmptySetIfMissing(
|
|
441
470
|
mutableEncounteredIds,
|
|
442
471
|
targetParentRecordLink.__typename,
|
|
443
472
|
);
|
|
@@ -447,7 +476,7 @@ function normalizeDataIntoRecord(
|
|
|
447
476
|
return recordHasBeenUpdated;
|
|
448
477
|
}
|
|
449
478
|
|
|
450
|
-
export function
|
|
479
|
+
export function insertEmptySetIfMissing<K, V>(map: Map<K, Set<V>>, key: K) {
|
|
451
480
|
let result = map.get(key);
|
|
452
481
|
if (result === undefined) {
|
|
453
482
|
result = new Set();
|
|
@@ -487,9 +516,8 @@ function normalizeLinkedField(
|
|
|
487
516
|
astNode: NormalizationLinkedField,
|
|
488
517
|
networkResponseParentRecord: NetworkResponseObject,
|
|
489
518
|
targetParentRecord: StoreRecord,
|
|
490
|
-
targetParentRecordLink:
|
|
519
|
+
targetParentRecordLink: StoreLink,
|
|
491
520
|
variables: Variables,
|
|
492
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
493
521
|
mutableEncounteredIds: EncounteredIds,
|
|
494
522
|
): RecordHasBeenUpdated {
|
|
495
523
|
const networkResponseKey = getNetworkResponseKey(astNode);
|
|
@@ -513,7 +541,7 @@ function normalizeLinkedField(
|
|
|
513
541
|
|
|
514
542
|
if (Array.isArray(networkResponseData)) {
|
|
515
543
|
// TODO check astNode.plural or the like
|
|
516
|
-
const dataIds: (
|
|
544
|
+
const dataIds: (StoreLink | null)[] = [];
|
|
517
545
|
for (let i = 0; i < networkResponseData.length; i++) {
|
|
518
546
|
const networkResponseObject = networkResponseData[i];
|
|
519
547
|
if (networkResponseObject == null) {
|
|
@@ -527,7 +555,6 @@ function normalizeLinkedField(
|
|
|
527
555
|
targetParentRecordLink,
|
|
528
556
|
variables,
|
|
529
557
|
i,
|
|
530
|
-
nestedRefetchQueries,
|
|
531
558
|
mutableEncounteredIds,
|
|
532
559
|
);
|
|
533
560
|
|
|
@@ -554,7 +581,6 @@ function normalizeLinkedField(
|
|
|
554
581
|
targetParentRecordLink,
|
|
555
582
|
variables,
|
|
556
583
|
null,
|
|
557
|
-
nestedRefetchQueries,
|
|
558
584
|
mutableEncounteredIds,
|
|
559
585
|
);
|
|
560
586
|
|
|
@@ -586,9 +612,8 @@ function normalizeInlineFragment(
|
|
|
586
612
|
astNode: NormalizationInlineFragment,
|
|
587
613
|
networkResponseParentRecord: NetworkResponseObject,
|
|
588
614
|
targetParentRecord: StoreRecord,
|
|
589
|
-
targetParentRecordLink:
|
|
615
|
+
targetParentRecordLink: StoreLink,
|
|
590
616
|
variables: Variables,
|
|
591
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
592
617
|
mutableEncounteredIds: EncounteredIds,
|
|
593
618
|
): RecordHasBeenUpdated {
|
|
594
619
|
const typeToRefineTo = astNode.type;
|
|
@@ -600,7 +625,6 @@ function normalizeInlineFragment(
|
|
|
600
625
|
targetParentRecord,
|
|
601
626
|
targetParentRecordLink,
|
|
602
627
|
variables,
|
|
603
|
-
nestedRefetchQueries,
|
|
604
628
|
mutableEncounteredIds,
|
|
605
629
|
);
|
|
606
630
|
return hasBeenModified;
|
|
@@ -610,7 +634,7 @@ function normalizeInlineFragment(
|
|
|
610
634
|
|
|
611
635
|
function dataIdsAreTheSame(
|
|
612
636
|
existingValue: DataTypeValue,
|
|
613
|
-
newDataIds: (
|
|
637
|
+
newDataIds: (StoreLink | null)[],
|
|
614
638
|
): boolean {
|
|
615
639
|
if (Array.isArray(existingValue)) {
|
|
616
640
|
if (newDataIds.length !== existingValue.length) {
|
|
@@ -635,10 +659,9 @@ function normalizeNetworkResponseObject(
|
|
|
635
659
|
environment: IsographEnvironment,
|
|
636
660
|
astNode: NormalizationLinkedField,
|
|
637
661
|
networkResponseData: NetworkResponseObject,
|
|
638
|
-
targetParentRecordLink:
|
|
662
|
+
targetParentRecordLink: StoreLink,
|
|
639
663
|
variables: Variables,
|
|
640
664
|
index: number | null,
|
|
641
|
-
nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
|
|
642
665
|
mutableEncounteredIds: EncounteredIds,
|
|
643
666
|
): DataId /* The id of the modified or newly created item */ {
|
|
644
667
|
const newStoreRecordId = getDataIdOfNetworkResponse(
|
|
@@ -668,7 +691,6 @@ function normalizeNetworkResponseObject(
|
|
|
668
691
|
newStoreRecord,
|
|
669
692
|
{ __link: newStoreRecordId, __typename: __typename },
|
|
670
693
|
variables,
|
|
671
|
-
nestedRefetchQueries,
|
|
672
694
|
mutableEncounteredIds,
|
|
673
695
|
);
|
|
674
696
|
|
|
@@ -760,13 +782,14 @@ function getStoreKeyChunkForArgumentValue(
|
|
|
760
782
|
}
|
|
761
783
|
|
|
762
784
|
function getStoreKeyChunkForArgument(argument: Argument, variables: Variables) {
|
|
763
|
-
|
|
785
|
+
const [argumentName, argumentValue] = argument;
|
|
786
|
+
let chunk = getStoreKeyChunkForArgumentValue(argumentValue, variables);
|
|
764
787
|
|
|
765
788
|
if (typeof chunk === 'object') {
|
|
766
789
|
chunk = JSON.stringify(stableCopy(chunk));
|
|
767
790
|
}
|
|
768
791
|
|
|
769
|
-
return `${FIRST_SPLIT_KEY}${
|
|
792
|
+
return `${FIRST_SPLIT_KEY}${argumentName}${SECOND_SPLIT_KEY}${chunk}`;
|
|
770
793
|
}
|
|
771
794
|
|
|
772
795
|
function getNetworkResponseKey(
|
|
@@ -831,7 +854,7 @@ export const THIRD_SPLIT_KEY = '__';
|
|
|
831
854
|
|
|
832
855
|
// Returns a key to look up an item in the store
|
|
833
856
|
function getDataIdOfNetworkResponse(
|
|
834
|
-
parentRecordLink:
|
|
857
|
+
parentRecordLink: StoreLink,
|
|
835
858
|
dataToNormalize: NetworkResponseObject,
|
|
836
859
|
astNode: NormalizationLinkedField,
|
|
837
860
|
variables: Variables,
|