Skip to content

Instantly share code, notes, and snippets.

@zerkalica
Last active May 17, 2025 05:37
Show Gist options
  • Save zerkalica/4a5260bf6fcd57492946c463b8cfc0f6 to your computer and use it in GitHub Desktop.
Save zerkalica/4a5260bf6fcd57492946c463b8cfc0f6 to your computer and use it in GitHub Desktop.
batch fetch
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