Skip to content

Commit 8716cc1

Browse files
author
denisp22
committed
Implement TypeScript types via JSDoc comments.
Fixes jaydenseric/graphql-react#6 .
1 parent 6c64eb4 commit 8716cc1

File tree

80 files changed

+1924
-1773
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1924
-1773
lines changed

.eslintrc.json

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"extends": ["env"],
33
"settings": {
4+
"jsdoc": {
5+
"mode": "typescript"
6+
},
47
"polyfills": [
58
"AbortController",
69
"CustomEvent",
@@ -10,5 +13,10 @@
1013
"FormData",
1114
"performance"
1215
]
16+
},
17+
"rules": {
18+
"jsdoc/require-description": "off",
19+
"jsdoc/require-returns": "off",
20+
"jsdoc/valid-types": "off"
1321
}
1422
}

.vscode/settings.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"typescript.disableAutomaticTypeAcquisition": true,
3+
"typescript.enablePromptUseWorkspaceTsdk": true,
4+
"typescript.tsdk": "node_modules/typescript/lib"
5+
}

Cache.mjs

+43-42
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,70 @@
1+
// @ts-check
2+
13
/**
24
* Cache store.
3-
* @kind class
4-
* @name Cache
5-
* @param {object} [store={}] Initial [cache store]{@link Cache#store}. Useful for hydrating cache data from a server side render prior to the initial client side render.
6-
* @example <caption>How to import.</caption>
7-
* ```js
8-
* import Cache from "graphql-react/Cache.mjs";
9-
* ```
10-
* @example <caption>Construct a new instance.</caption>
11-
* ```js
12-
* const cache = new Cache();
13-
* ```
5+
* @see {@link CacheEventMap `CacheEventMap`} for a map of possible events.
146
*/
157
export default class Cache extends EventTarget {
8+
/**
9+
* @param {CacheStore} [store] Initial {@link Cache.store cache store} record.
10+
* Defaults to `{}`. Useful for hydrating cache data from a server side
11+
* render prior to the initial client side render.
12+
*/
1613
constructor(store = {}) {
1714
super();
1815

1916
if (typeof store !== "object" || !store || Array.isArray(store))
2017
throw new TypeError("Constructor argument 1 `store` must be an object.");
2118

2219
/**
23-
* Store of cache [keys]{@link CacheKey} and [values]{@link CacheValue}.
24-
* @kind member
25-
* @name Cache#store
26-
* @type {object}
20+
* Store of cache {@link CacheKey keys} and associated
21+
* {@link CacheValue values}.
22+
* @type {CacheStore}
2723
*/
2824
this.store = store;
2925
}
3026
}
3127

3228
/**
33-
* Signals that a [cache store]{@link Cache#store} entry was set. The event name
34-
* starts with the [cache key]{@link CacheKey} of the set entry, followed by
35-
* `/set`.
36-
* @kind event
37-
* @name Cache#event:set
38-
* @type {CustomEvent}
39-
* @prop {object} detail Event detail.
40-
* @prop {CacheValue} detail.cacheValue Cache value that was set.
29+
* Map of possible {@linkcode Cache} events. Note that the keys don’t match the
30+
* dispatched event names that dynamically contain the associated
31+
* {@link CacheKey cache key}.
32+
* @typedef {object} CacheEventMap
33+
* @prop {CustomEvent<CacheEventSetDetail>} set Signals that a
34+
* {@link Cache.store cache store} entry was set. The event name starts with
35+
* the {@link CacheKey cache key} of the set entry, followed by `/set`.
36+
* @prop {CustomEvent} stale Signals that a {@link Cache.store cache store}
37+
* entry is now stale (often due to a mutation) and should probably be
38+
* reloaded. The event name starts with the
39+
* {@link CacheKey cache key} of the stale entry, followed by `/stale`.
40+
* @prop {CustomEvent} prune Signals that a {@link Cache.store cache store}
41+
* entry will be deleted unless the event is canceled via
42+
* `event.preventDefault()`. The event name starts with the
43+
* {@link CacheKey cache key} of the entry being pruned, followed by `/prune`.
44+
* @prop {CustomEvent} delete Signals that a {@link Cache.store cache store}
45+
* entry was deleted. The event name starts with the
46+
* {@link CacheKey cache key} of the deleted entry, followed by `/delete`.
47+
*/
48+
49+
/**
50+
* @typedef {object} CacheEventSetDetail
51+
* @prop {CacheValue} cacheValue The set {@link CacheValue cache value}.
4152
*/
4253

4354
/**
44-
* Signals that a [cache store]{@link Cache#store} entry is now stale (often due
45-
* to a mutation) and should probably be reloaded. The event name starts with
46-
* the [cache key]{@link CacheKey} of the stale entry, followed by `/stale`.
47-
* @kind event
48-
* @name Cache#event:stale
49-
* @type {CustomEvent}
55+
* A unique key to access a {@link CacheValue cache value}.
56+
* @typedef {string} CacheKey
5057
*/
5158

5259
/**
53-
* Signals that a [cache store]{@link Cache#store} entry will be deleted unless
54-
* the event is canceled via `event.preventDefault()`. The event name starts
55-
* with the [cache key]{@link CacheKey} of the entry being pruned, followed by
56-
* `/prune`.
57-
* @kind event
58-
* @name Cache#event:prune
59-
* @type {CustomEvent}
60+
* A {@link Cache.store cache store} value. If server side rendering, it should
61+
* be JSON serializable for client hydration. It should contain information
62+
* about any errors that occurred during loading so they can be rendered, and if
63+
* server side rendering, be hydrated on the client.
64+
* @typedef {unknown} CacheValue
6065
*/
6166

6267
/**
63-
* Signals that a [cache store]{@link Cache#store} entry was deleted. The event
64-
* name starts with the [cache key]{@link CacheKey} of the deleted entry,
65-
* followed by `/delete`.
66-
* @kind event
67-
* @name Cache#event:delete
68-
* @type {CustomEvent}
68+
* Cache store record.
69+
* @typedef {Record<CacheKey, CacheValue>} CacheStore
6970
*/

Cache.test.mjs

+17-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1+
// @ts-check
2+
13
import { deepStrictEqual, strictEqual, throws } from "assert";
24
import Cache from "./Cache.mjs";
35
import assertBundleSize from "./test/assertBundleSize.mjs";
6+
import assertInstanceOf from "./test/assertInstanceOf.mjs";
47

8+
/**
9+
* Adds `Cache` tests.
10+
* @param {import("test-director").default} tests Test director.
11+
*/
512
export default (tests) => {
613
tests.add("`Cache` bundle size.", async () => {
714
await assertBundleSize(new URL("./Cache.mjs", import.meta.url), 200);
815
});
916

1017
tests.add("`Cache` constructor argument 1 `store`, not an object.", () => {
1118
throws(() => {
12-
new Cache(null);
19+
new Cache(
20+
// @ts-expect-error Testing invalid.
21+
null
22+
);
1323
}, new TypeError("Constructor argument 1 `store` must be an object."));
1424
});
1525

@@ -32,25 +42,23 @@ export default (tests) => {
3242
tests.add("`Cache` events.", () => {
3343
const cache = new Cache();
3444

35-
strictEqual(cache instanceof EventTarget, true);
45+
assertInstanceOf(cache, EventTarget);
3646

37-
let listenedEvent;
47+
/** @type {Event | null} */
48+
let listenedEvent = null;
3849

50+
/** @type {EventListener} */
3951
const listener = (event) => {
4052
listenedEvent = event;
4153
};
4254

4355
const eventName = "a";
44-
const eventDetail = 1;
45-
const event = new CustomEvent(eventName, {
46-
detail: eventDetail,
47-
});
56+
const event = new CustomEvent(eventName);
4857

4958
cache.addEventListener(eventName, listener);
5059
cache.dispatchEvent(event);
5160

52-
deepStrictEqual(listenedEvent, event);
53-
strictEqual(listenedEvent.detail, eventDetail);
61+
strictEqual(listenedEvent, event);
5462

5563
listenedEvent = null;
5664

CacheContext.mjs

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1+
// @ts-check
2+
13
import React from "react";
24

5+
/** @typedef {import("./Cache.mjs").default} Cache */
6+
37
/**
4-
* React context for a [`Cache`]{@link Cache} instance.
5-
* @kind member
6-
* @name CacheContext
7-
* @type {object}
8-
* @prop {Function} Provider [React context provider component](https://reactjs.org/docs/context.html#contextprovider).
9-
* @prop {Function} Consumer [React context consumer component](https://reactjs.org/docs/context.html#contextconsumer).
10-
* @example <caption>How to import.</caption>
11-
* ```js
12-
* import CacheContext from "graphql-react/CacheContext.mjs";
13-
* ```
8+
* [React context](https://reactjs.org/docs/context.html) for a
9+
* {@linkcode Cache} instance.
10+
* @type {React.Context<Cache | undefined>}
1411
*/
15-
const CacheContext = React.createContext();
12+
const CacheContext = React.createContext(
13+
/** @type {Cache | undefined} */ (undefined)
14+
);
1615

1716
CacheContext.displayName = "CacheContext";
1817

CacheContext.test.mjs

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
1+
// @ts-check
2+
13
import { strictEqual } from "assert";
24
import React from "react";
3-
import ReactTestRenderer from "react-test-renderer";
5+
import ReactDOMServer from "react-dom/server.js";
6+
import Cache from "./Cache.mjs";
47
import CacheContext from "./CacheContext.mjs";
58
import assertBundleSize from "./test/assertBundleSize.mjs";
69

10+
/**
11+
* Adds `CacheContext` tests.
12+
* @param {import("test-director").default} tests Test director.
13+
*/
714
export default (tests) => {
815
tests.add("`CacheContext` bundle size.", async () => {
916
await assertBundleSize(new URL("./CacheContext.mjs", import.meta.url), 120);
1017
});
1118

1219
tests.add("`CacheContext` used as a React context.", () => {
13-
const TestComponent = () => React.useContext(CacheContext);
14-
const contextValue = "a";
15-
const testRenderer = ReactTestRenderer.create(
20+
let contextValue;
21+
22+
/** Test component. */
23+
function TestComponent() {
24+
contextValue = React.useContext(CacheContext);
25+
return null;
26+
}
27+
28+
const value = new Cache();
29+
30+
ReactDOMServer.renderToStaticMarkup(
1631
React.createElement(
1732
CacheContext.Provider,
18-
{ value: contextValue },
33+
{ value },
1934
React.createElement(TestComponent)
2035
)
2136
);
2237

23-
strictEqual(testRenderer.toJSON(), contextValue);
38+
strictEqual(contextValue, value);
2439
});
2540
};

HYDRATION_TIME_MS.mjs

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1+
// @ts-check
2+
3+
/** @typedef {import("./useAutoLoad.mjs").default} useAutoLoad */
4+
15
/**
26
* Number of milliseconds after the first client render that’s considered the
3-
* hydration time; during which the
4-
* [`useAutoLoad`]{@link useAutoLoad} React hook won’t load if the
5-
* cache entry is already populated.
6-
* @kind constant
7-
* @name HYDRATION_TIME_MS
8-
* @type {number}
9-
* @example <caption>How to import.</caption>
10-
* ```js
11-
* import HYDRATION_TIME_MS from "graphql-react/HYDRATION_TIME_MS.mjs";
12-
* ```
7+
* hydration time; during which the {@linkcode useAutoLoad} React hook won’t
8+
* load if the cache entry is already populated.
139
*/
1410
export default 1000;

HYDRATION_TIME_MS.test.mjs

+6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
// @ts-check
2+
13
import { strictEqual } from "assert";
24
import HYDRATION_TIME_MS from "./HYDRATION_TIME_MS.mjs";
35
import assertBundleSize from "./test/assertBundleSize.mjs";
46

7+
/**
8+
* Adds `HYDRATION_TIME_MS` tests.
9+
* @param {import("test-director").default} tests Test director.
10+
*/
511
export default (tests) => {
612
tests.add("`HYDRATION_TIME_MS` bundle size.", async () => {
713
await assertBundleSize(

HydrationTimeStampContext.mjs

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1+
// @ts-check
2+
13
import React from "react";
24

35
/**
4-
* React context for the client side hydration [time stamp]{@link HighResTimeStamp}.
5-
* @kind member
6-
* @name HydrationTimeStampContext
7-
* @type {object}
8-
* @prop {Function} Provider [React context provider component](https://reactjs.org/docs/context.html#contextprovider).
9-
* @prop {Function} Consumer [React context consumer component](https://reactjs.org/docs/context.html#contextconsumer).
10-
* @example <caption>How to import.</caption>
11-
* ```js
12-
* import HydrationTimeStampContext from "graphql-react/HydrationTimeStampContext.mjs";
13-
* ```
6+
* [React context](https://reactjs.org/docs/context.html) for the client side
7+
* hydration {@link DOMHighResTimeStamp time stamp}.
8+
* @type {React.Context<DOMHighResTimeStamp | undefined>}
149
*/
15-
const HydrationTimeStampContext = React.createContext();
10+
const HydrationTimeStampContext = React.createContext(
11+
/** @type {DOMHighResTimeStamp | undefined} */ (undefined)
12+
);
1613

1714
HydrationTimeStampContext.displayName = "HydrationTimeStampContext";
1815

HydrationTimeStampContext.test.mjs

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
// @ts-check
2+
13
import { strictEqual } from "assert";
24
import React from "react";
3-
import ReactTestRenderer from "react-test-renderer";
5+
import ReactDOMServer from "react-dom/server.js";
46
import HydrationTimeStampContext from "./HydrationTimeStampContext.mjs";
57
import assertBundleSize from "./test/assertBundleSize.mjs";
68

9+
/**
10+
* Adds `HydrationTimeStampContext` tests.
11+
* @param {import("test-director").default} tests Test director.
12+
*/
713
export default (tests) => {
814
tests.add("`HydrationTimeStampContext` bundle size.", async () => {
915
await assertBundleSize(
@@ -13,16 +19,24 @@ export default (tests) => {
1319
});
1420

1521
tests.add("`HydrationTimeStampContext` used as a React context.", () => {
16-
const TestComponent = () => React.useContext(HydrationTimeStampContext);
17-
const contextValue = "a";
18-
const testRenderer = ReactTestRenderer.create(
22+
let contextValue;
23+
24+
/** Test component. */
25+
function TestComponent() {
26+
contextValue = React.useContext(HydrationTimeStampContext);
27+
return null;
28+
}
29+
30+
const value = 1;
31+
32+
ReactDOMServer.renderToStaticMarkup(
1933
React.createElement(
2034
HydrationTimeStampContext.Provider,
21-
{ value: contextValue },
35+
{ value },
2236
React.createElement(TestComponent)
2337
)
2438
);
2539

26-
strictEqual(testRenderer.toJSON(), contextValue);
40+
strictEqual(contextValue, value);
2741
});
2842
};

0 commit comments

Comments
 (0)