UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

411 lines (410 loc) 12.2 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const refProxy = require("./builder/ref-proxy.cjs"); const index = require("./builder/index.cjs"); class UnhashableQueryIRError extends Error { constructor(path, reason) { super(`Query IR is not stably hashable at ${path}: ${reason}`); this.path = path; this.reason = reason; this.name = `UnhashableQueryIRError`; } } function getStableQueryIRHash(query) { return JSON.stringify(canonicalizeQueryIR(query)); } function getStableQueryBuilderHash(query) { return getStableQueryIRHash(index.getQueryIR(query)); } function getStableValueHash(value, path = `value`) { return JSON.stringify(canonicalizeRuntimeValue(value, path, /* @__PURE__ */ new WeakSet())); } function canonicalizeQueryIR(query) { return canonicalizeQuery(query, `query`, /* @__PURE__ */ new WeakSet()); } function canonicalizeQuery(query, path, seen) { if (query.fnSelect) { throw new UnhashableQueryIRError(`${path}.fnSelect`, `function select`); } if (query.fnWhere?.length) { throw new UnhashableQueryIRError(`${path}.fnWhere`, `function where`); } if (query.fnHaving?.length) { throw new UnhashableQueryIRError(`${path}.fnHaving`, `function having`); } const result = { type: `query`, from: canonicalizeSource(query.from, `${path}.from`, seen) }; if (query.select) { result.select = canonicalizeSelect(query.select, `${path}.select`, seen); } if (query.join) { result.join = query.join.map( (join, index2) => canonicalizeJoin(join, `${path}.join[${index2}]`, seen) ); } if (query.where) { result.where = query.where.map( (where, index2) => canonicalizeWhere(where, `${path}.where[${index2}]`, seen) ); } if (query.groupBy) { result.groupBy = query.groupBy.map( (expression, index2) => canonicalizeExpression(expression, `${path}.groupBy[${index2}]`, seen) ); } if (query.having) { result.having = query.having.map( (having, index2) => canonicalizeWhere(having, `${path}.having[${index2}]`, seen) ); } if (query.orderBy) { result.orderBy = query.orderBy.map( (orderBy, index2) => canonicalizeOrderBy(orderBy, `${path}.orderBy[${index2}]`, seen) ); } if (query.limit !== void 0) { result.limit = canonicalizeRuntimeValue(query.limit, `${path}.limit`, seen); } if (query.offset !== void 0) { result.offset = canonicalizeRuntimeValue( query.offset, `${path}.offset`, seen ); } if (query.distinct) { result.distinct = true; } if (query.singleResult) { result.singleResult = true; } return result; } function canonicalizeJoin(join, path, seen) { return { type: join.type, from: canonicalizeSource(join.from, `${path}.from`, seen), left: canonicalizeExpression(join.left, `${path}.left`, seen), right: canonicalizeExpression(join.right, `${path}.right`, seen) }; } function canonicalizeSource(source, path, seen) { if (source.type === `collectionRef`) { return { type: `collectionRef`, alias: source.alias, collectionId: canonicalizeRuntimeValue( source.collection.id, `${path}.collection.id`, seen ) }; } if (source.type === `unionFrom`) { return { type: `unionFrom`, sources: source.sources.map( (unionSource, index2) => canonicalizeSource(unionSource, `${path}.sources[${index2}]`, seen) ) }; } if (source.type === `unionAll`) { return { type: `unionAll`, queries: source.queries.map( (query, index2) => canonicalizeQuery(query, `${path}.queries[${index2}]`, seen) ) }; } return { type: `queryRef`, alias: source.alias, query: canonicalizeQuery(source.query, `${path}.query`, seen) }; } function canonicalizeSelect(select, path, seen) { return { type: `select`, fields: Object.keys(select).sort().map((key) => [ key, canonicalizeSelectValue(select[key], `${path}.${key}`, seen) ]) }; } function canonicalizeSelectValue(value, path, seen) { if (refProxy.isRefProxy(value)) { return canonicalizeExpression(refProxy.toExpression(value), path, seen); } if (isExpression(value)) { return canonicalizeExpression(value, path, seen); } if (isPlainObject(value)) { return canonicalizeSelect(value, path, seen); } return canonicalizeRuntimeValue(value, path, seen); } function canonicalizeWhere(where, path, seen) { if (isWhereObject(where)) { const result = { type: `where`, expression: canonicalizeExpression( where.expression, `${path}.expression`, seen ) }; if (where.residual === true) { result.residual = true; } return result; } return canonicalizeExpression(where, path, seen); } function canonicalizeOrderBy(orderBy, path, seen) { return { expression: canonicalizeExpression( orderBy.expression, `${path}.expression`, seen ), compareOptions: canonicalizeRuntimeValue( orderBy.compareOptions, `${path}.compareOptions`, seen ) }; } function canonicalizeExpression(expression, path, seen) { if (expression.type === `ref`) { return { type: `ref`, path: expression.path.map( (segment, index2) => canonicalizeRuntimeValue(segment, `${path}.path[${index2}]`, seen) ) }; } if (expression.type === `val`) { return { type: `val`, value: canonicalizeRuntimeValue(expression.value, `${path}.value`, seen) }; } if (expression.type === `func`) { return { type: `func`, name: expression.name, args: expression.args.map( (arg, index2) => canonicalizeExpression(arg, `${path}.args[${index2}]`, seen) ) }; } if (expression.type === `agg`) { return { type: `agg`, name: expression.name, args: expression.args.map( (arg, index2) => canonicalizeExpression(arg, `${path}.args[${index2}]`, seen) ) }; } if (expression.type === `conditionalSelect`) { const result2 = { type: `conditionalSelect`, branches: expression.branches.map((branch, index2) => ({ condition: canonicalizeExpression( branch.condition, `${path}.branches[${index2}].condition`, seen ), value: canonicalizeSelectValue( branch.value, `${path}.branches[${index2}].value`, seen ) })) }; if (expression.defaultValue !== void 0) { result2.defaultValue = canonicalizeSelectValue( expression.defaultValue, `${path}.defaultValue`, seen ); } return result2; } const result = { type: `includesSubquery`, query: canonicalizeQuery(expression.query, `${path}.query`, seen), correlationField: canonicalizeExpression( expression.correlationField, `${path}.correlationField`, seen ), childCorrelationField: canonicalizeExpression( expression.childCorrelationField, `${path}.childCorrelationField`, seen ), fieldName: expression.fieldName, materialization: expression.materialization }; if (expression.parentFilters) { result.parentFilters = expression.parentFilters.map( (where, index2) => canonicalizeWhere(where, `${path}.parentFilters[${index2}]`, seen) ); } if (expression.parentProjection) { result.parentProjection = expression.parentProjection.map( (projection, index2) => canonicalizeExpression( projection, `${path}.parentProjection[${index2}]`, seen ) ); } if (expression.scalarField !== void 0) { result.scalarField = expression.scalarField; } return result; } function canonicalizeRuntimeValue(value, path, seen) { if (value === null) return [`null`]; if (typeof value === `string`) { return [`string`, value]; } if (typeof value === `boolean`) { return [`boolean`, value]; } if (typeof value === `number`) { if (Number.isNaN(value)) { return [`number`, `NaN`]; } if (value === Infinity) { return [`number`, `Infinity`]; } if (value === -Infinity) { return [`number`, `-Infinity`]; } if (Object.is(value, -0)) { return [`number`, `-0`]; } return [`number`, value]; } if (typeof value === `undefined`) { return [`undefined`]; } if (typeof value === `bigint`) { return [`bigint`, value.toString()]; } if (typeof value === `function`) { throw new UnhashableQueryIRError(path, `function value`); } if (typeof value === `symbol`) { throw new UnhashableQueryIRError(path, `symbol value`); } if (refProxy.isRefProxy(value)) { return canonicalizeExpression(refProxy.toExpression(value), path, seen); } if (Array.isArray(value)) { return withCircularGuard(value, path, seen, () => [ `array`, value.map( (item, index2) => canonicalizeRuntimeValue(item, `${path}[${index2}]`, seen) ) ]); } if (value instanceof Date) { const timestamp = value.getTime(); if (Number.isNaN(timestamp)) { throw new UnhashableQueryIRError(path, `invalid Date`); } return [`Date`, value.toISOString()]; } if (value instanceof ArrayBuffer) { return [`binary`, `ArrayBuffer`, Array.from(new Uint8Array(value))]; } if (ArrayBuffer.isView(value)) { return [ `binary`, value.constructor.name, Array.from( new Uint8Array(value.buffer, value.byteOffset, value.byteLength) ) ]; } if (value instanceof Map) { return withCircularGuard(value, path, seen, () => { const entries = Array.from( value.entries(), ([key, entryValue], index2) => [ canonicalizeRuntimeValue(key, `${path}.key[${index2}]`, seen), canonicalizeRuntimeValue(entryValue, `${path}.value[${index2}]`, seen) ] ); entries.sort(compareStableIdentityValues); return [`Map`, entries]; }); } if (value instanceof Set) { return withCircularGuard(value, path, seen, () => { const entries = Array.from( value, (entry, index2) => canonicalizeRuntimeValue(entry, `${path}[${index2}]`, seen) ); entries.sort(compareStableIdentityValues); return [`Set`, entries]; }); } if (isPlainObject(value)) { return canonicalizeObject(value, path, seen); } throw new UnhashableQueryIRError(path, `non-plain object value`); } function compareStableIdentityValues(left, right) { const serializedLeft = JSON.stringify(left); const serializedRight = JSON.stringify(right); return serializedLeft < serializedRight ? -1 : serializedLeft > serializedRight ? 1 : 0; } function canonicalizeObject(value, path, seen) { return withCircularGuard(value, path, seen, () => [ `object`, Object.keys(value).sort().map((key) => [ key, canonicalizeRuntimeValue(value[key], `${path}.${key}`, seen) ]) ]); } function withCircularGuard(value, path, seen, callback) { if (seen.has(value)) { throw new UnhashableQueryIRError(path, `circular value`); } seen.add(value); try { return callback(); } finally { seen.delete(value); } } function isWhereObject(where) { return `expression` in where; } function isExpression(value) { if (value === null || typeof value !== `object`) { return false; } const expressionType = value.type; return expressionType === `agg` || expressionType === `conditionalSelect` || expressionType === `func` || expressionType === `ref` || expressionType === `val` || expressionType === `includesSubquery`; } function isPlainObject(value) { if (value === null || typeof value !== `object`) return false; const prototype = Object.getPrototypeOf(value); return prototype === Object.prototype || prototype === null; } exports.UnhashableQueryIRError = UnhashableQueryIRError; exports.canonicalizeQueryIR = canonicalizeQueryIR; exports.getStableQueryBuilderHash = getStableQueryBuilderHash; exports.getStableQueryIRHash = getStableQueryIRHash; exports.getStableValueHash = getStableValueHash; //# sourceMappingURL=ir-stable-identity.cjs.map