UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

311 lines (285 loc) • 9.59 kB
import { VIRTUAL_PROP_NAMES } from '../../virtual-props.js' import { isPlainObject } from '../../utils/type-guards.js' import { getParentContextValue } from '../equality-value-identity.js' import type { NamespacedRow } from '../../types.js' const ROUTED_SCALAR_VALUE = Symbol(`tanstack_db_routed_scalar_value`) const ROUTE_METADATA = Symbol(`tanstack_db_route_metadata`) export const INCLUDES_PUBLIC_KEY = Symbol(`includesPublicKey`) export const SOURCE_METADATA = Symbol(`sourceMetadata`) const SOURCE_ROUTING = Symbol(`sourceRouting`) export const ROW_METADATA = Symbol(`rowMetadata`) export type RowMetadata = Partial< Record<(typeof VIRTUAL_PROP_NAMES)[number], unknown> > export const INCLUDES_ROUTING = Symbol(`includesRouting`) const INTERNAL_ROUTE_KEYS = new Set<PropertyKey>([ ROUTE_METADATA, INCLUDES_PUBLIC_KEY, SOURCE_METADATA, ROW_METADATA, SOURCE_ROUTING, ]) const INTERNAL_CALLBACK_KEYS = new Set<PropertyKey>([ ...INTERNAL_ROUTE_KEYS, INCLUDES_ROUTING, ]) type RoutedResult = { [ROUTED_SCALAR_VALUE]: unknown [ROUTE_METADATA]: RouteMetadata [INCLUDES_PUBLIC_KEY]: unknown } type PublicContainer = { value: object properties: Record< PropertyKey, PropertyDescriptor & { child?: PublicContainer } > parents: Array<PublicContainer> copy?: object } export type RouteMetadata = { correlationKey: unknown parentContext: unknown } export type RoutedScalarMetadata = { value: unknown correlationKey: unknown parentContext: unknown publicKey: unknown } export function attachRouteMetadata<T extends object>( value: T, correlationKey: unknown, parentContext: unknown, ): T { return Object.assign(value, { [ROUTE_METADATA]: { correlationKey, parentContext } satisfies RouteMetadata, }) } export function getRouteMetadata(value: unknown): RouteMetadata | undefined { if ( value == null || typeof value !== `object` || !(ROUTE_METADATA in value) ) { return undefined } return (value as { [ROUTE_METADATA]: RouteMetadata })[ROUTE_METADATA] } export function getNamespacedRouteMetadata( row: unknown, source: string, ): RouteMetadata | undefined { return ( getRouteMetadata(row) ?? (row != null && typeof row === `object` ? getRouteMetadata((row as Record<string, unknown>)[source]) : undefined) ) } export function attachRouteMetadataToResult( value: unknown, correlationKey: unknown, parentContext: unknown, publicKey: unknown, ): unknown { if ( correlationKey === undefined && parentContext === undefined && publicKey === undefined ) { return value } return Object.assign( isPlainObject(value) ? { ...value } : { [ROUTED_SCALAR_VALUE]: value }, { [ROUTE_METADATA]: { correlationKey, parentContext }, [INCLUDES_PUBLIC_KEY]: publicKey, }, ) } export function getRoutedScalarMetadata( value: unknown, ): RoutedScalarMetadata | undefined { if ( value == null || typeof value !== `object` || !(ROUTED_SCALAR_VALUE in value) ) { return undefined } const routed = value as RoutedResult return { value: routed[ROUTED_SCALAR_VALUE], ...routed[ROUTE_METADATA], publicKey: routed[INCLUDES_PUBLIC_KEY], } } /** Give FROM and JOIN rows the same route and callback namespace. */ export function wrapInputRow( alias: string, row: unknown, metadata?: RowMetadata | null, hasInternalState = true, ): NamespacedRow { if (!hasInternalState) return { [alias]: row } as NamespacedRow const scalar = getRoutedScalarMetadata(row) const route = scalar ?? getRouteMetadata(row) const namespaced = { ...getParentContextValue(route?.parentContext), [alias]: scalar ? scalar.value : stripInternalCallbackMetadata(row), } as NamespacedRow if (route) attachRouteMetadata(namespaced, route.correlationKey, route.parentContext) if (route) Object.assign(namespaced, { [INCLUDES_PUBLIC_KEY]: scalar?.publicKey ?? (row as Record<PropertyKey, unknown>)[INCLUDES_PUBLIC_KEY], }) if (metadata !== undefined) Object.assign(namespaced, { [SOURCE_METADATA]: { [alias]: metadata } }) if (row !== null && typeof row === `object` && INCLUDES_ROUTING in row) Object.assign(namespaced, { [SOURCE_ROUTING]: { [alias]: (row as Record<PropertyKey, unknown>)[INCLUDES_ROUTING], }, }) return namespaced } /** Attribution and include routing belong to the namespace, never source data. */ export function getSourceMetadata( row: Record<PropertyKey, unknown>, alias: string, ): RowMetadata | null { const metadata = row[SOURCE_METADATA] as Record<string, RowMetadata | null> | undefined if (metadata && Object.hasOwn(metadata, alias)) return metadata[alias]! const source = row[alias] if (source == null || typeof source !== `object`) return null const result: RowMetadata = {} for (const name of VIRTUAL_PROP_NAMES) if (name in source) result[name] = (source as Record<string, unknown>)[name] return result } export function getSourceRouting( row: Record<PropertyKey, unknown>, alias: string, ): unknown { return (row[SOURCE_ROUTING] as Record<string, unknown> | undefined)?.[alias] } export function mergeSourceContext( target: Record<PropertyKey, unknown>, left: Record<PropertyKey, unknown> | undefined, right: Record<PropertyKey, unknown> | undefined, ): void { for (const field of [SOURCE_METADATA, SOURCE_ROUTING]) if (left?.[field] || right?.[field]) target[field] = { ...(left?.[field] as object | undefined), ...(right?.[field] as object | undefined), } } /** Restore namespace state after resolving its public include paths. */ export function restoreInternalRouteMetadata( target: Record<PropertyKey, unknown>, source: object, ): void { for (const key of INTERNAL_ROUTE_KEYS) if (Object.hasOwn(source, key)) target[key] = (source as Record<PropertyKey, unknown>)[key] } /** Remove private route state from a known result root. */ export function stripInternalRouteMetadata(value: unknown): unknown { return stripInternalMetadata(value, INTERNAL_ROUTE_KEYS) } /** Remove compiler-owned keys before invoking user code, at the known scope. */ export function stripInternalCallbackMetadata(value: unknown): unknown { return stripInternalMetadata(value, INTERNAL_CALLBACK_KEYS) } function stripInternalMetadata( value: unknown, keys: ReadonlySet<PropertyKey>, ): unknown { if (!isPublicContainer(value)) return value for (const key of keys) if (Object.hasOwn(value, key)) { const properties: Record<PropertyKey, PropertyDescriptor> = Object.getOwnPropertyDescriptors(value) for (const omitted of keys) delete properties[omitted] return Object.defineProperties( createPublicContainerShell(value), properties, ) } return value } /** Copy only paths changed by a leaf transform or an omitted private key. */ export function transformPublicContainers( value: unknown, transformLeaf: (value: unknown) => unknown, omittedKeys: ReadonlySet<PropertyKey>, ): unknown { const rootReplacement = transformLeaf(value) if (!Object.is(rootReplacement, value)) return rootReplacement if (!isPublicContainer(value)) return value const containers = new Map<object, PublicContainer>() const queue: Array<PublicContainer> = [] const markDirty = (container: PublicContainer) => { if (container.copy) return container.copy = createPublicContainerShell(container.value) queue.push(container) } const visit = (current: object): PublicContainer => { const existing = containers.get(current) if (existing) return existing const container: PublicContainer = { value: current, properties: Object.getOwnPropertyDescriptors(current), parents: [], } containers.set(current, container) for (const key of Reflect.ownKeys(container.properties)) { if (omittedKeys.has(key)) { delete container.properties[key] markDirty(container) continue } const descriptor = container.properties[key]! if (!descriptor.enumerable || !(`value` in descriptor)) continue const child = descriptor.value descriptor.value = transformLeaf(child) if (!Object.is(descriptor.value, child)) markDirty(container) else if (isPublicContainer(child)) { descriptor.child = visit(child) descriptor.child.parents.push(container) } } return container } const root = visit(value) for (const container of queue) { for (const parent of container.parents) markDirty(parent) } // Every dirty container has a shell before linking children, so shared // references and cycles point to the same copy without a second recursion. for (const container of queue) { for (const key of Reflect.ownKeys(container.properties)) { const descriptor = container.properties[key]! if (descriptor.child) descriptor.value = descriptor.child.copy ?? descriptor.child.value } Object.defineProperties(container.copy!, container.properties) } return root.copy ?? value } /** Allocate a descriptor-copy target without losing array prototypes. */ export function createPublicContainerShell(value: object): object { const prototype = Object.getPrototypeOf(value) return Array.isArray(value) ? Object.setPrototypeOf([], prototype) : Object.create(prototype) } export function isPublicContainer(value: unknown): value is object { return Array.isArray(value) || isPlainObject(value) }