Last active
May 17, 2025 05:37
-
-
Save zerkalica/4a5260bf6fcd57492946c463b8cfc0f6 to your computer and use it in GitHub Desktop.
batch fetch
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
namespace $ { | |
type Unit<Full, Patch> = $mol_promise<Full | null> & { | |
id: string | |
patch?: Patch | null | |
error: Error | |
} | |
export class $gd_kit_batch< | |
Full extends {}, | |
Patch extends ({} | null) = Partial<Full>, | |
> extends $mol_object { | |
protected sync_timeout = undefined as undefined | $mol_after_timeout | |
id() { | |
return '' | |
} | |
destructor() { | |
this.units = [] | |
this.sync_timeout?.destructor() | |
this.sync_timeout = undefined | |
} | |
@ $mol_action | |
load(ids: readonly string[]): Record<string, Full> { | |
throw new Error('implement') | |
} | |
@ $mol_action | |
patch(patches: Record<string, Patch | null>): Record<string, Full> { | |
throw new Error('implement') | |
} | |
strict_ids_check() { | |
return false | |
} | |
protected units = [] as Unit<Full, Patch>[] | |
protected async batch() { | |
this.sync_timeout = undefined | |
const units = this.units | |
if( units.length === 0 ) return | |
this.units = [] | |
const load_ids = new Set<string>() | |
const patches = {} as Record<string, Patch | null> | |
let has_patches = false | |
for (const { id, patch } of units) { | |
if (patch === undefined) { | |
load_ids.add(id) | |
continue | |
} | |
patches[id] = patch === null ? null : Object.assign(patches[id] ?? {}, patch) | |
has_patches = true | |
} | |
let not_fetched = units | |
try { | |
const [ loaded, patched ] = await Promise.all([ | |
load_ids.size ? $mol_wire_async(this).load([ ...load_ids ]) : undefined, | |
has_patches ? $mol_wire_async(this).patch(patches) : undefined, | |
]) | |
const strict_check = this.strict_ids_check() | |
not_fetched = [] | |
for( const unit of units ) { | |
const id = unit.id | |
const val = patched?.[id] ?? loaded?.[id] ?? null | |
if (! val && strict_check) not_fetched.push(unit) | |
else unit.done(val) | |
} | |
if (not_fetched.length) { | |
throw new Error(`Not fetched ids`, { cause: { ids: not_fetched.map(unit => unit.id) } }) | |
} | |
} catch (e) { | |
for( const unit of not_fetched) unit.fail(new $mol_error_mix( | |
(e as Error).message || 'Не сохранился объект', | |
{ id: unit.id, patch: unit.patch }, | |
e as Error, | |
unit.error, | |
)) | |
} | |
} | |
timeout() { | |
return 70 | |
} | |
static place = undefined as undefined | string | |
data_async(id: string, patch?: Patch | null | false) { | |
const prev = this.units.find(u => u.id === id) | |
if (prev && patch) prev.patch = { ...prev.patch, ...patch } | |
if (prev) return prev | |
const error = new Error('Stack save') // new Error() сохраняет стек трейс для отладки | |
const unit: Unit<Full, Patch> = Object.assign(new $mol_promise<Full | null>(), { id, patch: patch === false ? undefined : patch, error }) | |
this.units.push(unit) | |
if (this.sync_timeout) return unit | |
this.sync_timeout = new this.$.$mol_after_timeout( | |
this.timeout(), | |
this.batch.bind(this) | |
) | |
this.$.$gd_kit_batch.place = undefined | |
return unit | |
} | |
cache(id: string, cache?: Full | Error | null) { | |
return this.data(id, null, cache) | |
} | |
static level = 0 | |
static promises = {} as Record<string, Unit<any, any>> | |
static wait_async() { | |
return Promise.all(Object.values(this.promises)) | |
} | |
@ $mol_action | |
static wait(id: string) { | |
return this.promises[id] ?? null | |
} | |
static transaction<V>(task: () => V): V { | |
try { | |
const prev_level = this.level | |
this.level++ | |
const result = task() | |
if (prev_level === 0) { | |
$mol_wire_sync(this).wait_async() | |
} | |
this.promises = {} | |
return result | |
} catch(e) { | |
if (! $mol_promise_like(e)) this.promises = {} | |
$mol_fail_hidden(e) | |
} finally { | |
this.level-- | |
} | |
} | |
private _cache = {} as Record<string, Full | undefined | null> | |
cache_dto(id: string, next?: Full | null) { | |
if (next !== undefined) this._cache[id] = next | |
return this._cache[id] | |
} | |
@ $mol_mem_key | |
data(id: string, patch?: Patch | null | false, cache?: Full | Error | null): Full | null { | |
// $mol_wire_solid() | |
if (cache) { | |
this.cache_dto(id, cache instanceof Error ? undefined : cache) | |
this.$.$gd_kit_batch.promises[id]?.done(cache) | |
return cache as Full | |
} | |
if (patch && this.$.$gd_kit_batch.level > 0) { | |
const unit = this.data_async(id, patch) | |
const set = this.data.bind(this, id, null) | |
const promise = unit.then(set).catch(set) | |
this.$.$gd_kit_batch.promises[id] = Object.assign(promise, unit) | |
const prev = $mol_wire_probe(() => this.data(id)) | |
const res = { | |
...prev, | |
...patch as unknown as Full | |
} | |
this.cache_dto(id, res) | |
return res | |
} | |
const cached = this.cache_dto(id) | |
if (cached !== undefined && patch === undefined) return cached | |
const result = $mol_wire_sync(this).data_async(id, patch) | |
this.cache_dto(id, result) | |
return result | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment