-
Notifications
You must be signed in to change notification settings - Fork 177
Expand file tree
/
Copy pathcreateAsync.ts
More file actions
143 lines (138 loc) · 3.39 KB
/
createAsync.ts
File metadata and controls
143 lines (138 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
* 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";
export function createAsync<T>(
fn: (prev: T) => Promise<T>,
options: {
name?: string;
initialValue: T;
deferStream?: boolean;
}
): Accessor<T>;
export function createAsync<T>(
fn: (prev: T | undefined) => Promise<T>,
options?: {
name?: string;
initialValue?: T;
deferStream?: boolean;
}
): Accessor<T | undefined>;
export function createAsync<T>(
fn: (prev: T | undefined) => Promise<T>,
options?: {
name?: string;
initialValue?: T;
deferStream?: boolean;
}
): Accessor<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
);
return () => resource();
}
export function createAsyncStore<T>(
fn: (prev: T) => Promise<T>,
options: {
name?: string;
initialValue: T;
deferStream?: boolean;
reconcile?: ReconcileOptions;
}
): Accessor<T>;
export function createAsyncStore<T>(
fn: (prev: T | undefined) => Promise<T>,
options?: {
name?: string;
initialValue?: T;
deferStream?: boolean;
reconcile?: ReconcileOptions;
}
): Accessor<T | undefined>;
export function createAsyncStore<T>(
fn: (prev: T | undefined) => Promise<T>,
options: {
name?: string;
initialValue?: T;
deferStream?: boolean;
reconcile?: ReconcileOptions;
} = {}
): Accessor<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
);
return () => resource();
}
function createDeepSignal<T>(value: T | undefined, options?: ReconcileOptions) {
const [store, setStore] = createStore({
value
});
return [
() => store.value,
(v: T) => {
typeof v === "function" && (v = v());
setStore("value", reconcile(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);
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;
}
}