@tanstack/db
Version:
A reactive client store for building super fast apps on sync
220 lines (219 loc) • 8.45 kB
JavaScript
import { BaseQueryBuilder } from "./query/builder/index.js";
import { collectSourceRefs, requireCollectionSource } from "./query/ir.js";
import { isCollection, isFailedStatus } from "./live-query-primitives.js";
import { DEFAULT_GC_TIME_MS, UNSUBSCRIBED_GC_FLOOR_MS } from "./collection/lifecycle.js";
import { createLiveQueryCollection } from "./query/live-query-collection.js";
import { LIVE_QUERY_INTERNAL } from "./query/live/internal.js";
import { getBuilderFromConfig } from "./query/live/collection-registry.js";
import { getStableValueHash, getStableQueryBuilderHash } from "./query/ir-stable-identity.js";
import { getStringCollationIdentity } from "./query/runtime-reference-identity.js";
import { devBuild, codedMessage } from "./error-message.js";
import { InvalidPoolOptionError } from "./errors.js";
function configPool(config) {
const { pool } = config;
if (pool !== void 0 && typeof pool?.createPooledLiveQuery !== `function`)
throw new InvalidPoolOptionError();
return pool;
}
function rejectLiveQueryInput(collections, rejection) {
try {
if (collections) resumeDeferredLiveQueryCollections(collections);
} catch {
}
throw rejection;
}
function releaseDeferredOnReclaim(liveQuery, deferredCollections) {
if (deferredCollections.size === 0) return void 0;
const pending = new Set(deferredCollections);
const release = () => {
try {
resumeDeferredLiveQueryCollections(pending);
} catch {
}
};
const gcTime = liveQuery.config.gcTime ?? DEFAULT_GC_TIME_MS;
if (gcTime > 0 && Number.isFinite(gcTime) && liveQuery.status !== `idle`) {
return liveQuery.once(`status:cleaned-up`, release);
}
const timer = setTimeout(release, UNSUBSCRIBED_GC_FLOOR_MS);
return () => clearTimeout(timer);
}
function resumeDeferredLiveQueryCollections(collections, reportSourceErrors = false) {
const pending = Array.from(collections);
collections.clear();
let firstFailure;
for (const collection of pending) {
try {
collection._resumeSyncStart();
if (reportSourceErrors && isFailedStatus(collection.status)) {
throw collection._lifecycle.getSyncError();
}
} catch (error) {
firstFailure ??= { error };
}
}
if (firstFailure) throw firstFailure.error;
}
function createInitialQueryBuilder() {
return new BaseQueryBuilder();
}
function createCollectionResolver(dbClient, deferredCollections) {
if (!dbClient) return void 0;
return (options) => {
if (!deferredCollections) {
return dbClient.collection(
options
);
}
const collection = dbClient._materializeCollectionForRender(
options
);
if (collection._deferSyncStart()) deferredCollections.add(collection);
return collection;
};
}
function prepareLiveQueryValue(value, dbClient, deferredCollections) {
if (typeof value === `function`) {
return prepareLiveQueryValue(
value(createInitialQueryBuilder()),
dbClient,
deferredCollections
);
}
if (value instanceof BaseQueryBuilder) {
return value._bindCollectionSources(
createCollectionResolver(dbClient, deferredCollections)
);
}
if (value && typeof value === `object` && !isCollection(value) && !(value instanceof BaseQueryBuilder) && `query` in value) {
const pool = configPool(value);
const {
query,
queryKey: _queryKey,
client: _client,
pool: _pool,
...config
} = value;
const preparedQuery = prepareLiveQueryValue(
query,
dbClient,
deferredCollections
);
if (preparedQuery === void 0 || preparedQuery === null) {
return preparedQuery;
}
return {
...config,
// A DbClient query never pools, so its identity must not depend on
// pool: a server preload and a client hook must hash alike.
...pool && !dbClient ? { pool } : {},
query: preparedQuery
};
}
return value;
}
function getPreparedLiveQuerySources(value, onUnboundDescriptor) {
if (isCollection(value)) return [value];
const query = value instanceof BaseQueryBuilder ? value : value && typeof value === `object` && `query` in value ? value.query : value;
if (query instanceof BaseQueryBuilder) {
return collectSourceRefs(query._getQuery()).map(
(source) => source.type === `descriptorRef` ? onUnboundDescriptor ? onUnboundDescriptor(source.alias) : requireCollectionSource(source) : source.collection
);
}
return isCollection(query) ? [query] : [];
}
function getLiveQueryRowOptions(value) {
if (isCollection(value)) {
const { config: config2 } = value;
const internal = config2.utils?.[LIVE_QUERY_INTERNAL];
return [
internal?.hasCustomGetKey ? config2.getKey : void 0,
config2.schema
];
}
const config = value;
return [config?.getKey, config?.schema];
}
function getPreparedLiveQueryIdentity(value, pool) {
if (isCollection(value)) return [`collection`, value.id];
if (value instanceof BaseQueryBuilder) {
const pooled = pool?.getPooledQueryIdentity(value);
return pooled === void 0 ? [`query`, getStableQueryBuilderHash(value)] : [`pooled`, pooled];
}
if (value && typeof value === `object` && `query` in value) {
const config = value;
return [
// getKey and schema bind for this query identity. Inline options must
// not turn a normal framework render into a new Collection.
`config`,
getPreparedLiveQueryIdentity(config.query, configPool(config)),
[`singleResult`, config.singleResult === true],
[
`defaultStringCollation`,
getStringCollationIdentity(config.defaultStringCollation)
]
];
}
if (value === void 0 || value === null) return [`disabled`];
return [`value`, value];
}
function getLiveQueryHash(preparedValue, queryKey) {
if (queryKey?.length) {
const query = preparedValue instanceof BaseQueryBuilder ? preparedValue : preparedValue && typeof preparedValue === `object` && `query` in preparedValue ? preparedValue.query : void 0;
const ir = isCollection(preparedValue) ? getBuilderFromConfig(preparedValue.config)?.getWindow() : query instanceof BaseQueryBuilder ? query._getQuery() : void 0;
const window = ir && (ir.limit !== void 0 || (ir.offset ?? 0) !== 0) ? [{ limit: ir.limit, offset: ir.offset ?? 0 }] : [];
return getStableValueHash([`queryKey`, queryKey, ...window], `queryKey`);
}
const identity = isCollection(preparedValue) ? [`collection`, preparedValue.id] : [`derived`, getPreparedLiveQueryIdentity(preparedValue)];
return getStableValueHash(identity, `queryKey`);
}
function poolConfig(config, gcTime, pool) {
if (!(config.query instanceof BaseQueryBuilder) || !Object.keys(config).every((key) => key === `query` || key === `gcTime`)) {
return void 0;
}
return pool.createPooledLiveQuery(config.query, {
gcTime: config.gcTime ?? gcTime
});
}
function resolveLiveQueryValue(value, {
gcTime,
pool,
startSync = true
} = {}) {
if (value === void 0 || value === null) return null;
if (isCollection(value)) {
value.startSyncImmediate();
return value;
}
if (value instanceof BaseQueryBuilder) {
return (pool ? pool.createPooledLiveQuery(value, { gcTime }) : void 0) ?? createLiveQueryCollection({ query: value, startSync, gcTime });
}
if (typeof value === `object`) {
const requested = configPool(value);
const { pool: _pool, ...given } = value;
const pooling = pool === false ? void 0 : requested ?? pool;
const built = pooling && typeof given.query === `function` ? given.query(createInitialQueryBuilder()) : void 0;
const config = built instanceof BaseQueryBuilder ? { ...given, query: built } : given;
return (pooling ? poolConfig(config, gcTime, pooling) : void 0) ?? createLiveQueryCollection({
...config,
startSync: config.startSync ?? startSync,
gcTime: config.gcTime ?? gcTime
});
}
throw new Error(
devBuild() && process.env.NODE_ENV !== `production` ? `A live query must be a QueryBuilder, LiveQueryCollectionConfig, Collection, undefined, or null. Got: ${typeof value}` : codedMessage(102, { type: typeof value })
);
}
export {
configPool,
getLiveQueryHash,
getLiveQueryRowOptions,
getPreparedLiveQueryIdentity,
getPreparedLiveQuerySources,
prepareLiveQueryValue,
rejectLiveQueryInput,
releaseDeferredOnReclaim,
resolveLiveQueryValue,
resumeDeferredLiveQueryCollections
};
//# sourceMappingURL=live-query-options.js.map