UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

312 lines (311 loc) 11 kB
import { join, map, serializeValue, reduce, filter, distinct, compareKeys } from "@tanstack/db-ivm"; import { INCLUDES_ROUTING, FN_SELECT_STATE } from "../compiler/index.js"; import { VIRTUAL_PROP_NAMES } from "../../virtual-props.js"; import { deepEquals } from "../../utils.js"; const BUCKET_FACADE_REF = /* @__PURE__ */ Symbol(`bucketFacadeRef`); let nextBucketFacadeEdgeId = 0; function materializeCompilation(compilation, getRootKey, reduceJoinedPublicKeys = false) { if (!compilation.includes?.length && !(getRootKey && reduceJoinedPublicKeys)) { return { pipeline: compilation.pipeline, facades: [] }; } const built = /* @__PURE__ */ new WeakMap(); const materialized = materializeRelation( compilation, getRootKey, built, `root` ); return { ...materialized, facades: dedupeFacades(materialized.facades) }; } function materializeRelation(compilation, getKey, built, scope) { const cached = built.get(compilation)?.[scope]; if (cached) return cached; let pipeline = canonicalizeByPublicKey( exposeRouting(compilation.pipeline), getKey, scope ); const facades = []; for (const include of compilation.includes ?? []) { const child = materializeRelation( include.childCompilationResult, void 0, built, `child` ); facades.push(...child.facades); const bucketRows = createBucketRows(child.pipeline); if (include.materialization === `collection`) { const edgeId = `bucket-facade-${++nextBucketFacadeEdgeId}`; const activeBuckets = createActiveBuckets(pipeline, include); const activeBucketRows = activeBuckets.pipe( join(bucketRows), map(([bucketKey, [, row]]) => [bucketKey, row]) ); facades.push({ edgeId, rows: activeBucketRows, activeBuckets, hasOrderBy: include.hasOrderBy }); pipeline = attachCollectionInclude(pipeline, include, edgeId, scope); } else { pipeline = attachInlineInclude(pipeline, bucketRows, include, scope); } } const result = { pipeline, facades }; built.set(compilation, { ...built.get(compilation), [scope]: result }); return result; } function dedupeFacades(facades) { return [...new Map(facades.map((facade) => [facade.edgeId, facade])).values()]; } function exposeRouting(pipeline) { return pipeline.pipe( map(([key, rawTuple]) => { const tuple = rawTuple; return [ key, [ tuple[0], tuple[1], tuple[2], tuple[3], tuple[0][INCLUDES_ROUTING], tuple[4] ] ]; }) ); } function canonicalizeByPublicKey(pipeline, getKey, scope) { return pipeline.pipe( map(([internalKey, rawTuple]) => { const tuple = rawTuple; const publicKey = getKey ? getKey(tuple[0]) : tuple[5] ?? internalKey; const relationKey = scope === `root` ? serializeValue([`root`, publicKey]) : serializeValue([routeKey(tuple[2], tuple[3]), publicKey]); return [relationKey, { publicKey, tuple }]; }), reduce((values) => { const totalMultiplicity = values.reduce( (total, [, multiplicity]) => total + multiplicity, 0 ); if (totalMultiplicity === 0) return []; if (totalMultiplicity < 0) { throw new Error(`Canonical query row has negative multiplicity`); } const visible = values.find(([, multiplicity]) => multiplicity > 0)?.[0]; if (!visible) { throw new Error(`Canonical query row has no positive contributor`); } for (const [candidate, multiplicity] of values) { if (multiplicity <= 0) continue; assertCongruentContributors(visible, candidate); } return [[visible, 1]]; }), map(([relationKey, { publicKey, tuple }]) => [ scope === `root` ? publicKey : relationKey, tuple ]) ); } function assertCongruentContributors(left, right) { const [leftValue, leftOrder, leftCorrelation, leftContext, leftRouting] = left.tuple; const [rightValue, rightOrder, rightCorrelation, rightContext, rightRouting] = right.tuple; if (leftOrder !== rightOrder || !deepEquals(leftValue, rightValue) || !deepEquals(leftCorrelation, rightCorrelation) || !deepEquals(leftContext, rightContext) || !deepEquals(leftRouting, rightRouting)) { throw new Error( `Query contributors for public key ${serializeValue(left.publicKey)} are not congruent` ); } } function attachInlineInclude(parentPipeline, bucketRows, include, scope) { const bucketValues = bucketRows.pipe( reduce((values) => { const rows = []; for (const [row, multiplicity] of values) { if (multiplicity < 0) { throw new Error( `Materialization bucket row has negative multiplicity` ); } for (let index = 0; index < multiplicity; index++) rows.push(row); } if (rows.length === 0) return []; rows.sort(compareBucketRows); return [[{ value: materializeRows(rows, include) }, 1]]; }) ); const routedParents = parentPipeline.pipe( map(([parentKey, rawTuple]) => { const tuple = rawTuple; const routing = getIncludeRoute(tuple, include.fieldName); return [ routing?.active !== true ? `inactive:${serializeValue(parentKey)}` : routeKey(routing.correlationKey, routing.parentContext), { parentKey, tuple } ]; }), join(bucketValues, `left`), map(([_bucketKey, [parent, bucketValue]]) => { const [value, order, correlationKey, parentContext, routing, publicKey] = parent.tuple; const edgeRouting = routing?.[include.fieldName]; if (edgeRouting?.active !== true) { return [ parent.parentKey, [value, order, correlationKey, parentContext, routing, publicKey] ]; } const materialized = bucketValue === null ? emptyMaterializedValue(include.materialization) : bucketValue.value; return [ parent.parentKey, [ setMaterializedInclude(value, include.resultPath, materialized), order, correlationKey, parentContext, routing, publicKey ] ]; }) ); return canonicalizeByPublicKey( routedParents, void 0, scope ); } function createBucketRows(childPipeline) { return childPipeline.pipe( map(([internalKey, rawTuple]) => { const [value, order, correlationKey, parentContext, , publicKey] = rawTuple; return [ routeKey(correlationKey, parentContext), { publicKey: publicKey ?? internalKey, value, order } ]; }) ); } function attachCollectionInclude(parentPipeline, include, edgeId, scope) { const routedParents = parentPipeline.pipe( map(([parentKey, rawTuple]) => { const tuple = rawTuple; const routing = getIncludeRoute(tuple, include.fieldName); if (routing?.active !== true) return [parentKey, tuple]; const facade = createBucketFacadeRef( edgeId, routeKey(routing.correlationKey, routing.parentContext) ); return [ parentKey, [ setMaterializedInclude(tuple[0], include.resultPath, facade), tuple[1], tuple[2], tuple[3], tuple[4], tuple[5] ] ]; }) ); return canonicalizeByPublicKey( routedParents, void 0, scope ); } function createActiveBuckets(parentPipeline, include) { return parentPipeline.pipe( map(([parentKey, rawTuple]) => { const tuple = rawTuple; const routing = getIncludeRoute(tuple, include.fieldName); const bucketKey = routing?.active === true ? routeKey(routing.correlationKey, routing.parentContext) : void 0; return [parentKey, bucketKey]; }), filter(([, bucketKey]) => bucketKey !== void 0), distinct(([, bucketKey]) => bucketKey), map(([, bucketKey]) => [bucketKey, true]) ); } function createBucketFacadeRef(edgeId, bucketKey) { return { [BUCKET_FACADE_REF]: { edgeId, bucketKey } }; } function getIncludeRoute(tuple, fieldName) { return tuple[4]?.[fieldName]; } function routeKey(correlationKey, parentContext) { return serializeValue([correlationKey ?? null, parentContext ?? null]); } function compareBucketRows(left, right) { if (left.order !== right.order) { if (left.order === void 0) return 1; if (right.order === void 0) return -1; return left.order < right.order ? -1 : 1; } if ((typeof left.publicKey === `string` || typeof left.publicKey === `number`) && (typeof right.publicKey === `string` || typeof right.publicKey === `number`)) { return compareKeys(left.publicKey, right.publicKey); } const leftKey = serializeValue(left.publicKey); const rightKey = serializeValue(right.publicKey); return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0; } function materializeRows(rows, include) { const scalarField = include.scalarField; const values = scalarField ? rows.map(({ value }) => value[scalarField]) : rows.map(({ value }) => value); if (include.materialization === `array`) return values; if (include.materialization === `singleton`) return values[0]; return values.map((value) => String(value ?? ``)).join(``); } function emptyMaterializedValue(materialization) { if (materialization === `array`) return []; if (materialization === `concat`) return ``; if (materialization === `singleton`) return void 0; throw new Error(`Collection includes require a bucket facade`); } function setNestedValue(source, path, value) { const root = { ...source }; let target = root; let current = source; for (let index = 0; index < path.length - 1; index++) { const part = path[index]; const currentChild = current?.[part]; const next = Array.isArray(currentChild) ? [...currentChild] : { ...currentChild ?? {} }; target[part] = next; target = next; current = currentChild; } target[path[path.length - 1]] = value; return root; } function setMaterializedInclude(value, path, materialized) { const state = value[FN_SELECT_STATE]; if (!state) return setNestedValue(value, path, materialized); const sourceRow = setNestedValue(state.sourceRow, path, materialized); const selectedValue = state.fnSelect(sourceRow); if (!selectedValue || typeof selectedValue !== `object`) { throw new Error(`fn.select must return an object when it projects includes`); } const selected = Array.isArray(selectedValue) ? [...selectedValue] : { ...selectedValue }; for (const property of VIRTUAL_PROP_NAMES) { if (property in value && !(property in selected)) { selected[property] = value[property]; } } selected[INCLUDES_ROUTING] = value[INCLUDES_ROUTING]; Object.defineProperty(selected, FN_SELECT_STATE, { value: { sourceRow, fnSelect: state.fnSelect }, enumerable: true, configurable: true }); return selected; } export { BUCKET_FACADE_REF, materializeCompilation }; //# sourceMappingURL=materialized-pipeline.js.map