diff --git a/packages/async/CHANGELOG.md b/packages/async/CHANGELOG.md
new file mode 100644
index 000000000..21c6fa453
--- /dev/null
+++ b/packages/async/CHANGELOG.md
@@ -0,0 +1,5 @@
+# @solid-primitives/async
+
+## 0.0.1
+
+- Move from @solidjs/router
diff --git a/packages/async/LICENSE b/packages/async/LICENSE
new file mode 100644
index 000000000..d0f4f2652
--- /dev/null
+++ b/packages/async/LICENSE
@@ -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.
diff --git a/packages/async/README.md b/packages/async/README.md
new file mode 100644
index 000000000..5dede5a3d
--- /dev/null
+++ b/packages/async/README.md
@@ -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 (
+
+ {user()}
+
+ );
+}
+```
+
+## 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());
+```
diff --git a/packages/async/package.json b/packages/async/package.json
new file mode 100644
index 000000000..b6b08d30b
--- /dev/null
+++ b/packages/async/package.json
@@ -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"
+ }
+}
diff --git a/packages/async/src/index.ts b/packages/async/src/index.ts
new file mode 100644
index 000000000..e04beabc0
--- /dev/null
+++ b/packages/async/src/index.ts
@@ -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
+
+*/
+
+/**
+ * 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;
+ latest: T;
+}
+
+export function createAsync(
+ fn: (prev: T) => Promise,
+ options: {
+ name?: string;
+ initialValue: T;
+ deferStream?: boolean;
+ }
+): AccessorWithLatest;
+export function createAsync(
+ fn: (prev: T | undefined) => Promise,
+ options?: {
+ name?: string;
+ initialValue?: T;
+ deferStream?: boolean;
+ }
+): AccessorWithLatest;
+export function createAsync(
+ fn: (prev: T | undefined) => Promise,
+ options?: {
+ name?: string;
+ initialValue?: T;
+ deferStream?: boolean;
+ }
+): AccessorWithLatest {
+ 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 = (() => resource()) as any;
+ Object.defineProperty(resultAccessor, 'latest', {
+ get() {
+ return (resource as any).latest;
+ }
+ })
+
+ return resultAccessor;
+}
+
+export function createAsyncStore(
+ fn: (prev: T) => Promise,
+ options: {
+ name?: string;
+ initialValue: T;
+ deferStream?: boolean;
+ reconcile?: ReconcileOptions;
+ }
+): AccessorWithLatest;
+export function createAsyncStore(
+ fn: (prev: T | undefined) => Promise,
+ options?: {
+ name?: string;
+ initialValue?: T;
+ deferStream?: boolean;
+ reconcile?: ReconcileOptions;
+ }
+): AccessorWithLatest;
+export function createAsyncStore(
+ fn: (prev: T | undefined) => Promise,
+ options: {
+ name?: string;
+ initialValue?: T;
+ deferStream?: boolean;
+ reconcile?: ReconcileOptions;
+ } = {}
+): AccessorWithLatest {
+ 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 = (() => resource()) as any;
+ Object.defineProperty(resultAccessor, 'latest', {
+ get() {
+ return (resource as any).latest;
+ }
+ })
+
+ return resultAccessor;
+}
+
+function createDeepSignal(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, Setter];
+}
+
+// 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(fn: (prev: T | undefined) => Promise, prev: T | undefined) {
+ if (isServer || !sharedConfig.context) return fn(prev);
+ 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;
+ }
+}
diff --git a/packages/async/tsconfig.json b/packages/async/tsconfig.json
new file mode 100644
index 000000000..38c71ce71
--- /dev/null
+++ b/packages/async/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "dist",
+ "rootDir": "src"
+ },
+ "references": [],
+ "include": [
+ "src"
+ ]
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f69ea2688..0f10bea4d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -82,7 +82,7 @@ importers:
version: 5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)
vite-plugin-solid:
specifier: ^2.10.2
- version: 2.10.2(solid-js@1.8.22)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
+ version: 2.11.0(solid-js@1.8.22)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
vitest:
specifier: ^2.1.0
version: 2.1.1(@types/node@22.5.4)(jsdom@25.0.0)(sass@1.77.8)(terser@5.31.5)
@@ -106,6 +106,12 @@ importers:
specifier: ^1.8.7
version: 1.8.20
+ packages/async:
+ devDependencies:
+ solid-js:
+ specifier: ^1.8.7
+ version: 1.8.22
+
packages/audio:
dependencies:
'@solid-primitives/static-store':
@@ -6271,12 +6277,12 @@ packages:
'@nuxt/kit':
optional: true
- vite-plugin-solid@2.10.2:
- resolution: {integrity: sha512-AOEtwMe2baBSXMXdo+BUwECC8IFHcKS6WQV/1NEd+Q7vHPap5fmIhLcAzr+DUJ04/KHx/1UBU0l1/GWP+rMAPQ==}
+ vite-plugin-solid@2.11.0:
+ resolution: {integrity: sha512-G+NiwDj4EAeUE0wt3Ur9f+Lt9oMUuLd0FIxYuqwJSqRacKQRteCwUFzNy8zMEt88xWokngQhiFjfJMhjc1fDXw==}
peerDependencies:
'@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.*
solid-js: ^1.7.2
- vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
peerDependenciesMeta:
'@testing-library/jest-dom':
optional: true
@@ -6312,10 +6318,10 @@ packages:
terser:
optional: true
- vitefu@0.2.5:
- resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
+ vitefu@1.0.4:
+ resolution: {integrity: sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==}
peerDependencies:
- vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
peerDependenciesMeta:
vite:
optional: true
@@ -8198,7 +8204,7 @@ snapshots:
source-map-js: 1.2.0
terracotta: 1.0.5(solid-js@1.8.22)
vite-plugin-inspect: 0.7.42(rollup@4.20.0)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
- vite-plugin-solid: 2.10.2(solid-js@1.8.22)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
+ vite-plugin-solid: 2.11.0(solid-js@1.8.22)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
transitivePeerDependencies:
- '@nuxt/kit'
- '@testing-library/jest-dom'
@@ -12458,7 +12464,7 @@ snapshots:
- rollup
- supports-color
- vite-plugin-solid@2.10.2(solid-js@1.8.22)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)):
+ vite-plugin-solid@2.11.0(solid-js@1.8.22)(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)):
dependencies:
'@babel/core': 7.25.2
'@types/babel__core': 7.20.5
@@ -12467,7 +12473,7 @@ snapshots:
solid-js: 1.8.22
solid-refresh: 0.6.3(solid-js@1.8.22)
vite: 5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)
- vitefu: 0.2.5(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
+ vitefu: 1.0.4(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5))
transitivePeerDependencies:
- supports-color
@@ -12482,7 +12488,7 @@ snapshots:
sass: 1.77.8
terser: 5.31.5
- vitefu@0.2.5(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)):
+ vitefu@1.0.4(vite@5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)):
optionalDependencies:
vite: 5.4.4(@types/node@22.5.4)(sass@1.77.8)(terser@5.31.5)