-
Notifications
You must be signed in to change notification settings - Fork 141
add createAsync primitive #762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
birkskyum
wants to merge
12
commits into
solidjs-community:main
Choose a base branch
from
birkskyum:add-async-package
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
5ff3eba
add createAsync primitive
birkskyum f72a0c8
cleanup
birkskyum 7a75d27
rebase
birkskyum 297172a
add readme
birkskyum 4c7ed59
changeset
birkskyum 849c94d
fix links
birkskyum c3e9e6d
Update packages/async/src/index.ts
birkskyum 9c1f053
Update packages/async/README.md
birkskyum a4d9456
Update packages/async/CHANGELOG.md
birkskyum 633f1ee
remove changeset
birkskyum cb70c8a
fix jsx template
birkskyum 5e0161e
fix template
birkskyum File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# @solid-primitives/async | ||
|
||
## 0.0.1 | ||
|
||
- Move from @solidjs/router |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Solid Primitives Working Group | ||
|
||
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# createAsync | ||
|
||
An asynchronous primitive with a function that tracks similar to `createMemo`. | ||
`createAsync` expects a promise back that is then turned into a Signal. | ||
Reading it before it is ready causes Suspense/Transitions to trigger. | ||
|
||
> [!WARNING] | ||
> Using `query` in `createResource` directly will not work since the fetcher is | ||
> not reactive. This means that it will not invalidate properly. | ||
|
||
This is light wrapper over [`createResource`](https://docs.solidjs.com/reference/basic-reactivity/create-resource) which serves as a stand-in for a future primitive being brought to Solid core in 2.0. | ||
It is recommended that `createAsync` be used in favor of `createResource` specially when in a **SolidStart** app because `createAsync` works better in conjunction with the [cache](https://docs.solidjs.com/solid-router/reference/data-apis/cache) helper. | ||
|
||
|
||
|
||
```tsx | ||
import { createAsync } from "@solid-primitives/async"; | ||
import { Suspense } from "solid-js"; | ||
import { getUser } from "./api"; | ||
|
||
export function Component () => { | ||
const user = createAsync(() => getUser(params.id)); | ||
|
||
return ( | ||
<Suspense fallback="loading user..."> | ||
<p>{user()}</p> | ||
</Suspense> | ||
); | ||
} | ||
``` | ||
|
||
## Options | ||
|
||
| Name | Type | Default | Description | | ||
| ------------ | ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| name | `string` | `undefined` | A name for the resource. This is used for debugging purposes. | | ||
| deferStream | `boolean` | `false` | If true, Solid will wait for the resource to resolve before flushing the stream. | | ||
| initialValue | `any` | `undefined` | The initial value of the resource. | | ||
| onHydrated | `function` | `undefined` | A callback that is called when the resource is hydrated. | | ||
| ssrLoadFrom | `"server" \| "initial"` | `"server"` | The source of the initial value for SSR. If set to `"initial"`, the resource will use the `initialValue` option instead of the value returned by the fetcher. | | ||
| storage | `function` | `createSignal` | A function that returns a signal. This can be used to create a custom storage for the resource. This is still experimental | ||
|
||
|
||
# createAsyncStore | ||
|
||
Similar to createAsync except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates. | ||
|
||
```jsx | ||
const todos = createAsyncStore(() => getTodos()); | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
{ | ||
"name": "@solid-primitives/async", | ||
"version": "0.0.1", | ||
"description": "Primitives for async files.", | ||
"license": "MIT", | ||
"homepage": "https://primitives.solidjs.community/package/async", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/solidjs-community/solid-primitives.git" | ||
}, | ||
"primitive": { | ||
"name": "async", | ||
"stage": 0, | ||
"list": [ | ||
"createAsync" | ||
], | ||
"category": "Reactivity" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"private": false, | ||
"sideEffects": false, | ||
"type": "module", | ||
"main": "./dist/index.cjs", | ||
"module": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"browser": {}, | ||
"exports": { | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
}, | ||
"scripts": { | ||
"dev": "tsx ../../scripts/dev.ts", | ||
"build": "tsx ../../scripts/build.ts" | ||
}, | ||
"peerDependencies": { | ||
"solid-js": "^1.6.12" | ||
}, | ||
"typesVersions": {}, | ||
"devDependencies": { | ||
"solid-js": "^1.8.7" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
/* | ||
|
||
Primitive copied from @solidjs/router: https://github.com/solidjs/solid-router/blob/3c214ce2ceb9b7d9d39d143229a8c6145e83e681/src/data/createAsync.ts | ||
|
||
MIT License | ||
|
||
Copyright Ryan Carniato | ||
|
||
*/ | ||
|
||
/** | ||
birkskyum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured. | ||
*/ | ||
import { type Accessor, createResource, sharedConfig, type Setter, untrack } from "solid-js"; | ||
import { createStore, reconcile, type ReconcileOptions, unwrap } from "solid-js/store"; | ||
import { isServer } from "solid-js/web"; | ||
|
||
/** | ||
* As `createAsync` and `createAsyncStore` are wrappers for `createResource`, | ||
* this type allows to support `latest` field for these primitives. | ||
* It will be removed in the future. | ||
*/ | ||
export type AccessorWithLatest<T> = { | ||
(): T; | ||
latest: T; | ||
} | ||
|
||
export function createAsync<T>( | ||
fn: (prev: T) => Promise<T>, | ||
options: { | ||
name?: string; | ||
initialValue: T; | ||
deferStream?: boolean; | ||
} | ||
): AccessorWithLatest<T>; | ||
export function createAsync<T>( | ||
fn: (prev: T | undefined) => Promise<T>, | ||
options?: { | ||
name?: string; | ||
initialValue?: T; | ||
deferStream?: boolean; | ||
} | ||
): AccessorWithLatest<T | undefined>; | ||
export function createAsync<T>( | ||
fn: (prev: T | undefined) => Promise<T>, | ||
options?: { | ||
name?: string; | ||
initialValue?: T; | ||
deferStream?: boolean; | ||
} | ||
): AccessorWithLatest<T | undefined> { | ||
let resource: () => T; | ||
let prev = () => !resource || (resource as any).state === "unresolved" ? undefined : (resource as any).latest; | ||
[resource] = createResource( | ||
() => subFetch(fn, untrack(prev)), | ||
v => v, | ||
options as any | ||
); | ||
|
||
const resultAccessor: AccessorWithLatest<T> = (() => resource()) as any; | ||
Object.defineProperty(resultAccessor, 'latest', { | ||
get() { | ||
return (resource as any).latest; | ||
} | ||
}) | ||
|
||
return resultAccessor; | ||
} | ||
|
||
export function createAsyncStore<T>( | ||
fn: (prev: T) => Promise<T>, | ||
options: { | ||
name?: string; | ||
initialValue: T; | ||
deferStream?: boolean; | ||
reconcile?: ReconcileOptions; | ||
} | ||
): AccessorWithLatest<T>; | ||
export function createAsyncStore<T>( | ||
fn: (prev: T | undefined) => Promise<T>, | ||
options?: { | ||
name?: string; | ||
initialValue?: T; | ||
deferStream?: boolean; | ||
reconcile?: ReconcileOptions; | ||
} | ||
): AccessorWithLatest<T | undefined>; | ||
export function createAsyncStore<T>( | ||
fn: (prev: T | undefined) => Promise<T>, | ||
options: { | ||
name?: string; | ||
initialValue?: T; | ||
deferStream?: boolean; | ||
reconcile?: ReconcileOptions; | ||
} = {} | ||
): AccessorWithLatest<T | undefined> { | ||
let resource: () => T; | ||
let prev = () => !resource || (resource as any).state === "unresolved" ? undefined : unwrap((resource as any).latest); | ||
[resource] = createResource( | ||
() => subFetch(fn, untrack(prev)), | ||
v => v, | ||
{ | ||
...options, | ||
storage: (init: T | undefined) => createDeepSignal(init, options.reconcile) | ||
} as any | ||
); | ||
|
||
const resultAccessor: AccessorWithLatest<T> = (() => resource()) as any; | ||
Object.defineProperty(resultAccessor, 'latest', { | ||
get() { | ||
return (resource as any).latest; | ||
} | ||
}) | ||
|
||
return resultAccessor; | ||
} | ||
|
||
function createDeepSignal<T>(value: T | undefined, options?: ReconcileOptions) { | ||
const [store, setStore] = createStore({ | ||
value: structuredClone(value) | ||
}); | ||
return [ | ||
() => store.value, | ||
(v: T) => { | ||
typeof v === "function" && (v = v()); | ||
setStore("value", reconcile(structuredClone(v), options)); | ||
return store.value; | ||
} | ||
] as [Accessor<T | null>, Setter<T | null>]; | ||
} | ||
|
||
// mock promise while hydrating to prevent fetching | ||
class MockPromise { | ||
static all() { | ||
return new MockPromise(); | ||
} | ||
static allSettled() { | ||
return new MockPromise(); | ||
} | ||
static any() { | ||
return new MockPromise(); | ||
} | ||
static race() { | ||
return new MockPromise(); | ||
} | ||
static reject() { | ||
return new MockPromise(); | ||
} | ||
static resolve() { | ||
return new MockPromise(); | ||
} | ||
catch() { | ||
return new MockPromise(); | ||
} | ||
then() { | ||
return new MockPromise(); | ||
} | ||
finally() { | ||
return new MockPromise(); | ||
} | ||
} | ||
|
||
function subFetch<T>(fn: (prev: T | undefined) => Promise<T>, prev: T | undefined) { | ||
if (isServer || !sharedConfig.context) return fn(prev); | ||
Comment on lines
+163
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need some explanation on this one, it looks wild. |
||
const ogFetch = fetch; | ||
const ogPromise = Promise; | ||
try { | ||
window.fetch = () => new MockPromise() as any; | ||
Promise = MockPromise as any; | ||
return fn(prev); | ||
} finally { | ||
window.fetch = ogFetch; | ||
Promise = ogPromise; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"composite": true, | ||
"outDir": "dist", | ||
"rootDir": "src" | ||
}, | ||
"references": [], | ||
"include": [ | ||
"src" | ||
] | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.