aa-coordination-resource
Dominic Gannaway 7 months ago
parent 5c0749fdf3
commit fbe0a3d350

@ -219,4 +219,4 @@ export { getContext, getAllContexts, hasContext, setContext } from './internal/c
export { hydrate, mount, unmount } from './internal/client/render.js'; export { hydrate, mount, unmount } from './internal/client/render.js';
export { tick, untrack } from './internal/client/runtime.js'; export { tick, untrack } from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';
export { Resource } from './internal/client/reactivity/resources.js'; export { Resource, getResource, deferPending } from './internal/client/reactivity/resources.js';

@ -1,12 +1,17 @@
/** @import { Derived, Effect, Source } from '#client' */ /** @import { Derived, Effect, Source } from '#client' */
import { UNINITIALIZED } from '../../../constants.js'; import { UNINITIALIZED } from '../../../constants.js';
import { is_array } from '../../shared/utils.js';
import { EFFECT_PRESERVED } from '../constants.js'; import { EFFECT_PRESERVED } from '../constants.js';
import { active_effect, get, handle_error } from '../runtime.js'; import { getContext, setContext } from '../context.js';
import { active_effect, get, handle_error, untrack } from '../runtime.js';
import { derived } from './deriveds.js'; import { derived } from './deriveds.js';
import { block } from './effects.js'; import { block } from './effects.js';
import { internal_set, source } from './sources.js'; import { internal_set, source } from './sources.js';
const resource_symbol = Symbol('resource');
const current_map = new WeakMap();
/** /**
* @template T * @template T
*/ */
@ -18,14 +23,28 @@ export class Resource {
/** @type {Source<boolean>} */ /** @type {Source<boolean>} */
#pending = source(true); #pending = source(true);
/** @param {() => Promise<T>} fn */ /**
constructor(fn) { * @param {() => Promise<T>} fn
* @param {symbol} [symbol]
*/
constructor(fn, symbol) {
let parent = /** @type {Effect | null} */ (active_effect); let parent = /** @type {Effect | null} */ (active_effect);
current_map.set(this, this.#current);
if (parent === null) { if (parent === null) {
throw new Error('TODO cannot create resources outside of an effect'); throw new Error('TODO cannot create resources outside of an effect');
} }
if (typeof symbol === 'symbol') {
let resources = getContext(resource_symbol);
if (resources === undefined) {
resources = new Map();
setContext(resource_symbol, resources);
}
resources.set(symbol, this);
}
/** @type {{}} */ /** @type {{}} */
var current_token; var current_token;
@ -59,39 +78,51 @@ export class Resource {
return get(this.#pending); return get(this.#pending);
} }
get current() {
var value = get(this.#current);
if (value === UNINITIALIZED) {
throw new Error('Resource is not yet resolved, ensure it is awaited');
}
return value;
}
get latest() {
var value = get(this.#current);
get(this.#pending);
if (value === UNINITIALIZED) {
throw new Error('Resource is not yet resolved, ensure it is awaited');
}
return value;
}
/** /**
* @param {(arg0: { readonly current: T; readonly pending: boolean; readonly latest: T; }) => void} onfulfilled * @param {(arg0: { readonly current: T; readonly latest: T; }) => void} onfulfilled
* @param {((reason: any) => PromiseLike<never>) | null | undefined} onrejected * @param {((reason: any) => PromiseLike<never>) | null | undefined} onrejected
*/ */
then(onfulfilled, onrejected) { then(onfulfilled, onrejected) {
return get(this.#fn).then(() => { return get(this.#fn).then(() => {
var self = this; var self = this;
onfulfilled({ onfulfilled({
get current() { return self.current }, get current() {
get pending() { return self.pending }, return get(self.#current);
get latest() { return self.latest } },
get latest() {
get(self.#pending);
return get(self.#current);
}
}); });
}, onrejected); }, onrejected);
} }
} }
/**
* @template T
* @param {symbol} symbol
* @returns {Resource<T> | null}
*/
export function getResource(symbol) {
return getContext(resource_symbol)?.get(symbol) ?? null;
}
/**
* @template T
* @template V
* @param {Resource<T> | Resource<T>[]} resources
* @param {() => V} fn
*/
export function deferPending(resources, fn) {
const res = is_array(resources) ? resources : [resources];
for (let i = 0; i < res.length; i += 1) {
const resource = res[i];
const pending = untrack(() => resource.pending);
get(/** @type {Source} */ (current_map.get(resource)));
if (pending) {
break;
}
}
return untrack(fn);
}

@ -512,13 +512,19 @@ declare module 'svelte' {
* ``` * ```
* */ * */
export function untrack<T>(fn: () => T): T; export function untrack<T>(fn: () => T): T;
export function isPending(fn: () => any): boolean; export function getResource<T>(symbol: symbol): Resource<T> | null;
export function deferPending<T, V>(resources: Resource<T> | Resource<T>[], fn: () => V): V;
export class Resource<T> { export class Resource<T> {
constructor(fn: () => Promise<T>); constructor(fn: () => Promise<T>, symbol?: symbol | undefined);
get current(): T | Promise<Awaited<T>>; get pending(): boolean;
get latest(): T | Promise<Awaited<T>>;
then(onfulfilled: (arg0: {
readonly current: T;
readonly latest: T;
}) => void, onrejected: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>;
#private; #private;
} }
type Getters<T> = { type Getters<T> = {

Loading…
Cancel
Save