|
3 | 3 | /** @import { Transport } from '#shared' */ |
4 | 4 | import { DEV } from 'esm-env'; |
5 | 5 | import * as e from './errors.js'; |
| 6 | +import { save_render_context } from './render-context.js'; |
6 | 7 |
|
7 | 8 | /** @type {SSRContext | null} */ |
8 | 9 | export var ssr_context = null; |
@@ -115,139 +116,10 @@ function get_parent_context(ssr_context) { |
115 | 116 | */ |
116 | 117 | export async function save(promise) { |
117 | 118 | var previous_context = ssr_context; |
118 | | - var previous_sync_store = sync_store; |
119 | | - var value = await promise; |
| 119 | + const restore_render_context = await save_render_context(promise); |
120 | 120 |
|
121 | 121 | return () => { |
122 | 122 | ssr_context = previous_context; |
123 | | - sync_store = previous_sync_store; |
124 | | - return value; |
| 123 | + return restore_render_context(); |
125 | 124 | }; |
126 | 125 | } |
127 | | - |
128 | | -/** @type {string | null} */ |
129 | | -export let hydratable_key = null; |
130 | | - |
131 | | -/** @param {string | null} key */ |
132 | | -export function set_hydratable_key(key) { |
133 | | - hydratable_key = key; |
134 | | -} |
135 | | - |
136 | | -/** |
137 | | - * @template T |
138 | | - * @overload |
139 | | - * @param {string} key |
140 | | - * @param {() => Promise<T>} fn |
141 | | - * @param {{ transport?: Transport<T> }} [options] |
142 | | - * @returns {Promise<T>} |
143 | | - */ |
144 | | -/** |
145 | | - * @template T |
146 | | - * @overload |
147 | | - * @param {() => Promise<T>} fn |
148 | | - * @param {{ transport?: Transport<T> }} [options] |
149 | | - * @returns {Promise<T>} |
150 | | - */ |
151 | | -/** |
152 | | - * @template T |
153 | | - * @param {string | (() => Promise<T>)} key_or_fn |
154 | | - * @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_options] |
155 | | - * @param {{ transport?: Transport<T> }} [maybe_options] |
156 | | - * @returns {Promise<T>} |
157 | | - */ |
158 | | -export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) { |
159 | | - // TODO DRY out with #shared |
160 | | - /** @type {string} */ |
161 | | - let key; |
162 | | - /** @type {() => Promise<T>} */ |
163 | | - let fn; |
164 | | - /** @type {{ transport?: Transport<T> }} */ |
165 | | - let options; |
166 | | - |
167 | | - if (typeof key_or_fn === 'string') { |
168 | | - key = key_or_fn; |
169 | | - fn = /** @type {() => Promise<T>} */ (fn_or_options); |
170 | | - options = /** @type {{ transport?: Transport<T> }} */ (maybe_options); |
171 | | - } else { |
172 | | - if (hydratable_key === null) { |
173 | | - throw new Error( |
174 | | - 'TODO error: `hydratable` must be called synchronously within `cache` in order to omit the key' |
175 | | - ); |
176 | | - } else { |
177 | | - key = hydratable_key; |
178 | | - } |
179 | | - fn = /** @type {() => Promise<T>} */ (key_or_fn); |
180 | | - options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options); |
181 | | - } |
182 | | - const store = get_render_store(); |
183 | | - |
184 | | - if (store.hydratables.has(key)) { |
185 | | - // TODO error |
186 | | - throw new Error("can't have two hydratables with the same key"); |
187 | | - } |
188 | | - |
189 | | - const result = fn(); |
190 | | - store.hydratables.set(key, { value: result, transport: options.transport }); |
191 | | - return Promise.resolve(result); |
192 | | -} |
193 | | - |
194 | | -/** @type {RenderContext | null} */ |
195 | | -export let sync_store = null; |
196 | | - |
197 | | -/** @param {RenderContext | null} store */ |
198 | | -export function set_sync_store(store) { |
199 | | - sync_store = store; |
200 | | -} |
201 | | - |
202 | | -/** @type {AsyncLocalStorage<RenderContext | null> | null} */ |
203 | | -let als = null; |
204 | | - |
205 | | -import('node:async_hooks') |
206 | | - .then((hooks) => (als = new hooks.AsyncLocalStorage())) |
207 | | - .catch(() => { |
208 | | - // can't use ALS but can still use manual context preservation |
209 | | - return null; |
210 | | - }); |
211 | | - |
212 | | -/** @returns {RenderContext | null} */ |
213 | | -function try_get_render_store() { |
214 | | - return sync_store ?? als?.getStore() ?? null; |
215 | | -} |
216 | | - |
217 | | -/** @returns {RenderContext} */ |
218 | | -export function get_render_store() { |
219 | | - const store = try_get_render_store(); |
220 | | - |
221 | | - if (!store) { |
222 | | - // TODO make this a proper e.error |
223 | | - let message = 'Could not get rendering context.'; |
224 | | - |
225 | | - if (als) { |
226 | | - message += ' This is an internal error.'; |
227 | | - } else { |
228 | | - message += |
229 | | - ' In environments without `AsyncLocalStorage`, `hydratable` must be accessed synchronously, not after an `await`.' + |
230 | | - ' If it was accessed synchronously then this is an internal error.'; |
231 | | - } |
232 | | - |
233 | | - throw new Error(message); |
234 | | - } |
235 | | - |
236 | | - return store; |
237 | | -} |
238 | | - |
239 | | -/** |
240 | | - * @template T |
241 | | - * @param {RenderContext} store |
242 | | - * @param {() => Promise<T>} fn |
243 | | - * @returns {Promise<T>} |
244 | | - */ |
245 | | -export function with_render_store(store, fn) { |
246 | | - try { |
247 | | - sync_store = store; |
248 | | - const storage = als; |
249 | | - return storage ? storage.run(store, fn) : fn(); |
250 | | - } finally { |
251 | | - sync_store = null; |
252 | | - } |
253 | | -} |
0 commit comments