Initial project import
This commit is contained in:
21
node_modules/suspend-react/LICENSE
generated
vendored
Normal file
21
node_modules/suspend-react/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Paul Henschel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
92
node_modules/suspend-react/index.cjs.js
generated
vendored
Normal file
92
node_modules/suspend-react/index.cjs.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
const isPromise = promise => typeof promise === 'object' && typeof promise.then === 'function';
|
||||
|
||||
const globalCache = [];
|
||||
|
||||
function shallowEqualArrays(arrA, arrB, equal = (a, b) => a === b) {
|
||||
if (arrA === arrB) return true;
|
||||
if (!arrA || !arrB) return false;
|
||||
const len = arrA.length;
|
||||
if (arrB.length !== len) return false;
|
||||
|
||||
for (let i = 0; i < len; i++) if (!equal(arrA[i], arrB[i])) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function query(fn, keys = null, preload = false, config = {}) {
|
||||
// If no keys were given, the function is the key
|
||||
if (keys === null) keys = [fn];
|
||||
|
||||
for (const entry of globalCache) {
|
||||
// Find a match
|
||||
if (shallowEqualArrays(keys, entry.keys, entry.equal)) {
|
||||
// If we're pre-loading and the element is present, just return
|
||||
if (preload) return undefined; // If an error occurred, throw
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(entry, 'error')) throw entry.error; // If a response was successful, return
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(entry, 'response')) {
|
||||
if (config.lifespan && config.lifespan > 0) {
|
||||
if (entry.timeout) clearTimeout(entry.timeout);
|
||||
entry.timeout = setTimeout(entry.remove, config.lifespan);
|
||||
}
|
||||
|
||||
return entry.response;
|
||||
} // If the promise is still unresolved, throw
|
||||
|
||||
|
||||
if (!preload) throw entry.promise;
|
||||
}
|
||||
} // The request is new or has changed.
|
||||
|
||||
|
||||
const entry = {
|
||||
keys,
|
||||
equal: config.equal,
|
||||
remove: () => {
|
||||
const index = globalCache.indexOf(entry);
|
||||
if (index !== -1) globalCache.splice(index, 1);
|
||||
},
|
||||
promise: // Execute the promise
|
||||
(isPromise(fn) ? fn : fn(...keys) // When it resolves, store its value
|
||||
).then(response => {
|
||||
entry.response = response; // Remove the entry in time if a lifespan was given
|
||||
|
||||
if (config.lifespan && config.lifespan > 0) {
|
||||
entry.timeout = setTimeout(entry.remove, config.lifespan);
|
||||
}
|
||||
}) // Store caught errors, they will be thrown in the render-phase to bubble into an error-bound
|
||||
.catch(error => entry.error = error)
|
||||
}; // Register the entry
|
||||
|
||||
globalCache.push(entry); // And throw the promise, this yields control back to React
|
||||
|
||||
if (!preload) throw entry.promise;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suspend = (fn, keys, config) => query(fn, keys, false, config);
|
||||
|
||||
const preload = (fn, keys, config) => void query(fn, keys, true, config);
|
||||
|
||||
const peek = keys => {
|
||||
var _globalCache$find;
|
||||
|
||||
return (_globalCache$find = globalCache.find(entry => shallowEqualArrays(keys, entry.keys, entry.equal))) == null ? void 0 : _globalCache$find.response;
|
||||
};
|
||||
|
||||
const clear = keys => {
|
||||
if (keys === undefined || keys.length === 0) globalCache.splice(0, globalCache.length);else {
|
||||
const entry = globalCache.find(entry => shallowEqualArrays(keys, entry.keys, entry.equal));
|
||||
if (entry) entry.remove();
|
||||
}
|
||||
};
|
||||
|
||||
exports.clear = clear;
|
||||
exports.peek = peek;
|
||||
exports.preload = preload;
|
||||
exports.suspend = suspend;
|
||||
11
node_modules/suspend-react/index.d.ts
generated
vendored
Normal file
11
node_modules/suspend-react/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
declare type Tuple<T = any> = [T] | T[];
|
||||
declare type Await<T> = T extends Promise<infer V> ? V : never;
|
||||
declare type Config = {
|
||||
lifespan?: number;
|
||||
equal?: (a: any, b: any) => boolean;
|
||||
};
|
||||
declare const suspend: <Keys extends Tuple<unknown>, Fn extends (...keys: Keys) => Promise<unknown>>(fn: Promise<unknown> | Fn, keys?: Keys | undefined, config?: Config | undefined) => Await<ReturnType<Fn>>;
|
||||
declare const preload: <Keys extends Tuple<unknown>, Fn extends (...keys: Keys) => Promise<unknown>>(fn: Promise<unknown> | Fn, keys?: Keys | undefined, config?: Config | undefined) => undefined;
|
||||
declare const peek: <Keys extends Tuple<unknown>>(keys: Keys) => unknown;
|
||||
declare const clear: <Keys extends Tuple<unknown>>(keys?: Keys | undefined) => void;
|
||||
export { suspend, clear, preload, peek };
|
||||
85
node_modules/suspend-react/index.js
generated
vendored
Normal file
85
node_modules/suspend-react/index.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
const isPromise = promise => typeof promise === 'object' && typeof promise.then === 'function';
|
||||
|
||||
const globalCache = [];
|
||||
|
||||
function shallowEqualArrays(arrA, arrB, equal = (a, b) => a === b) {
|
||||
if (arrA === arrB) return true;
|
||||
if (!arrA || !arrB) return false;
|
||||
const len = arrA.length;
|
||||
if (arrB.length !== len) return false;
|
||||
|
||||
for (let i = 0; i < len; i++) if (!equal(arrA[i], arrB[i])) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function query(fn, keys = null, preload = false, config = {}) {
|
||||
// If no keys were given, the function is the key
|
||||
if (keys === null) keys = [fn];
|
||||
|
||||
for (const entry of globalCache) {
|
||||
// Find a match
|
||||
if (shallowEqualArrays(keys, entry.keys, entry.equal)) {
|
||||
// If we're pre-loading and the element is present, just return
|
||||
if (preload) return undefined; // If an error occurred, throw
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(entry, 'error')) throw entry.error; // If a response was successful, return
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(entry, 'response')) {
|
||||
if (config.lifespan && config.lifespan > 0) {
|
||||
if (entry.timeout) clearTimeout(entry.timeout);
|
||||
entry.timeout = setTimeout(entry.remove, config.lifespan);
|
||||
}
|
||||
|
||||
return entry.response;
|
||||
} // If the promise is still unresolved, throw
|
||||
|
||||
|
||||
if (!preload) throw entry.promise;
|
||||
}
|
||||
} // The request is new or has changed.
|
||||
|
||||
|
||||
const entry = {
|
||||
keys,
|
||||
equal: config.equal,
|
||||
remove: () => {
|
||||
const index = globalCache.indexOf(entry);
|
||||
if (index !== -1) globalCache.splice(index, 1);
|
||||
},
|
||||
promise: // Execute the promise
|
||||
(isPromise(fn) ? fn : fn(...keys) // When it resolves, store its value
|
||||
).then(response => {
|
||||
entry.response = response; // Remove the entry in time if a lifespan was given
|
||||
|
||||
if (config.lifespan && config.lifespan > 0) {
|
||||
entry.timeout = setTimeout(entry.remove, config.lifespan);
|
||||
}
|
||||
}) // Store caught errors, they will be thrown in the render-phase to bubble into an error-bound
|
||||
.catch(error => entry.error = error)
|
||||
}; // Register the entry
|
||||
|
||||
globalCache.push(entry); // And throw the promise, this yields control back to React
|
||||
|
||||
if (!preload) throw entry.promise;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suspend = (fn, keys, config) => query(fn, keys, false, config);
|
||||
|
||||
const preload = (fn, keys, config) => void query(fn, keys, true, config);
|
||||
|
||||
const peek = keys => {
|
||||
var _globalCache$find;
|
||||
|
||||
return (_globalCache$find = globalCache.find(entry => shallowEqualArrays(keys, entry.keys, entry.equal))) == null ? void 0 : _globalCache$find.response;
|
||||
};
|
||||
|
||||
const clear = keys => {
|
||||
if (keys === undefined || keys.length === 0) globalCache.splice(0, globalCache.length);else {
|
||||
const entry = globalCache.find(entry => shallowEqualArrays(keys, entry.keys, entry.equal));
|
||||
if (entry) entry.remove();
|
||||
}
|
||||
};
|
||||
|
||||
export { clear, peek, preload, suspend };
|
||||
24
node_modules/suspend-react/package.json
generated
vendored
Normal file
24
node_modules/suspend-react/package.json
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "suspend-react",
|
||||
"version": "0.1.3",
|
||||
"description": "Integrate React Suspense into your apps",
|
||||
"main": "index.cjs.js",
|
||||
"module": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"sideEffects": false,
|
||||
"keywords": [
|
||||
"react",
|
||||
"suspense",
|
||||
"resource",
|
||||
"asset"
|
||||
],
|
||||
"author": "Paul Henschel",
|
||||
"license": "MIT",
|
||||
"repository": "pmndrs/suspend-react",
|
||||
"homepage": "https://github.com/pmndrs/suspend-react#readme",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0"
|
||||
},
|
||||
"dependencies": {},
|
||||
"private": false
|
||||
}
|
||||
154
node_modules/suspend-react/readme.md
generated
vendored
Normal file
154
node_modules/suspend-react/readme.md
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
[](https://bundlephobia.com/result?p=suspend-react)
|
||||
[](https://www.npmjs.com/package/suspend-react)
|
||||
|
||||
<br />
|
||||
<a href="https://github.com/pmndrs/suspend-react"><img src="https://github.com/pmndrs/suspend-react/blob/main/hero.svg?raw=true" /></a>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
```shell
|
||||
npm install suspend-react
|
||||
```
|
||||
|
||||
This library integrates your async ops into React suspense. Pending- and error-states are handled at the parental level which frees the individual component from that burden and allows for better orchestration. Think of it as async/await for components. **Works in all React versions >= 16.6**.
|
||||
|
||||
```jsx
|
||||
import { Suspense } from 'react'
|
||||
import { suspend } from 'suspend-react'
|
||||
|
||||
function Post({ id, version }) {
|
||||
const data = suspend(async () => {
|
||||
const res = await fetch(`https://hacker-news.firebaseio.com/${version}/item/${id}.json`)
|
||||
return res.json()
|
||||
}, [id, version])
|
||||
return (
|
||||
<div>
|
||||
{data.title} by {data.by}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<div>loading...</div>}>
|
||||
<Post id={1000} version="v0" />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### API
|
||||
|
||||
```tsx
|
||||
const suspend = <Keys extends Tuple<unknown>, Fn extends (...keys: Keys) => Promise<unknown>>(
|
||||
fn: Fn | Promise<unknown>,
|
||||
keys?: Keys,
|
||||
config?: Config
|
||||
) => Await<ReturnType<Fn>>
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Function that returns a promise
|
||||
const result = suspend((...keys) => anyPromise, keys, config)
|
||||
// async function
|
||||
const result = suspend(async (...keys) => { /* ... */ }, keys, config)
|
||||
// Promise with keys
|
||||
const result = suspend(anyPromise, keys, config)
|
||||
// Promise itself is the key
|
||||
const result = suspend(anyPromise)
|
||||
```
|
||||
|
||||
`suspend` yields control back to React and the render-phase is aborted. It will resume once your promise resolves. For this to work you need to wrap it into a `<React.Suspense>` block, which requires you to set a fallback (can be `null`).
|
||||
|
||||
The dependencies (the 2nd argument) act as cache-keys, use as many as you want. If an entry is already in cache, calling `suspend` with the same keys will return it _immediately_ without breaking the render-phase. Cache access is similar to useMemo but *across the component tree*.
|
||||
|
||||
The 1st argument has to be a promise, or a function that returns a promise, or an asyn function. It receives the keys as arguments. `suspend` will return the resolved value, not a promise! This is guaranteed, *you do not have to check for validity*. Errors will bubble up to the nearest error-boundary.
|
||||
|
||||
#### Config
|
||||
|
||||
Both `suspend` and `preload` can _optionally_ receive a config object,
|
||||
|
||||
###### Keep-alive
|
||||
|
||||
The `lifespan` prop allows you to invalidate items over time, it defaults to `0` (keep-alive forever). Every read refreshes the timer to ensure that used entries stay valid.
|
||||
|
||||
```jsx
|
||||
// Keep cached item alive for one minute without read
|
||||
suspend(fn, keys, { lifespan: 60000 })
|
||||
```
|
||||
|
||||
###### Equality function
|
||||
|
||||
The `equal` prop customizes per-key validation, it defaults to `(a, b) => a === b` (reference equality).
|
||||
|
||||
```jsx
|
||||
import equal from 'fast-deep-equal'
|
||||
|
||||
// Validate keys deeply
|
||||
suspend(fn, keys, { equal })
|
||||
```
|
||||
|
||||
#### Preloading
|
||||
|
||||
```jsx
|
||||
import { preload } from 'suspend-react'
|
||||
|
||||
async function fetchFromHN(id, version) {
|
||||
const res = await fetch(`https://hacker-news.firebaseio.com/${version}/item/${id}.json`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
preload(fetchFromHN, [1000, 'v0'])
|
||||
```
|
||||
|
||||
#### Cache busting
|
||||
|
||||
```jsx
|
||||
import { clear } from 'suspend-react'
|
||||
|
||||
// Clear all cached entries
|
||||
clear()
|
||||
// Clear a specific entry
|
||||
clear([1000, 'v0'])
|
||||
```
|
||||
|
||||
#### Peeking into entries outside of suspense
|
||||
|
||||
```jsx
|
||||
import { peek } from 'suspend-react'
|
||||
|
||||
// This will either return the value (without suspense!) or undefined
|
||||
peek([1000, 'v0'])
|
||||
```
|
||||
|
||||
#### Making cache-keys unique
|
||||
|
||||
Since `suspend` operates on a global cache (for now, see [React 18](#react-18)), you might be wondering if keys could bleed, and yes they would. To establish cache-safety, create unique or semi-unique appendixes.
|
||||
|
||||
```diff
|
||||
- suspend(fn, [1000, 'v0'])
|
||||
+ suspend(fn, [1000, 'v0', 'functionName/fetch'])
|
||||
```
|
||||
|
||||
If you publish a library that suspends, consider symbols.
|
||||
|
||||
```jsx
|
||||
const fetchUUID = Symbol()
|
||||
|
||||
export function Foo() {
|
||||
suspend(fn, [1000, 'v0', fetchUUID])
|
||||
```
|
||||
|
||||
#### Typescript
|
||||
|
||||
Correct types will be inferred automatically.
|
||||
|
||||
#### React 18
|
||||
|
||||
Suspense, as is, has been a stable part of React since 16.6, but React will likely add some [interesting caching and cache busting APIs](https://github.com/reactwg/react-18/discussions/25) that could allow you to define cache boundaries declaratively. Expect these to be work for suspend-react once they come out.
|
||||
|
||||
#### Demos
|
||||
|
||||
Fetching posts from hacker-news: [codesandbox](https://codesandbox.io/s/use-asset-forked-yb62q)
|
||||
|
||||
Infinite list: [codesandbox](https://codesandbox.io/s/use-asset-infinite-list-forked-cwvs7)
|
||||
Reference in New Issue
Block a user