import type { Query, Sort } from "@peerbit/indexer-interface"; import { Entry, type ShallowEntry } from "@peerbit/log"; import { logger as loggerFn } from "@peerbit/logger"; import { type EntryReplicated, MAX_U32, MAX_U64, type NumberFromType, type ReplicationDomain, type SharedLog, } from "@peerbit/shared-log"; import { type Operation, isPutOperation } from "./operation.js"; import type { DocumentIndex } from "./search.js"; const logger = loggerFn("peerbit:program:document:domain"); type InferT = D extends Documents ? T : never; type InferR = D extends Documents> ? R : never; type Documents< T, D extends ReplicationDomain, R extends "u32" | "u64" = D extends ReplicationDomain ? I : "u32", > = { log: SharedLog; index: DocumentIndex }; type RangeArgs = { from: NumberFromType; to: NumberFromType; }; export type CustomDocumentDomain = ReplicationDomain< RangeArgs, Operation, R > & { canProjectToOneSegment: (request: { query: Query[]; sort: Sort[]; }) => boolean; }; type FromEntry = { fromEntry?: ( entry: ShallowEntry | Entry | EntryReplicated, ) => NumberFromType; }; type FromValue = { fromValue?: ( value: T | undefined, entry: ShallowEntry | Entry | EntryReplicated, ) => NumberFromType; }; type CreateArgs< R extends "u32" | "u64", DB extends Documents, > = { resolution: R; canProjectToOneSegment: (request: { query: Query[]; sort: Sort[]; }) => boolean; mergeSegmentMaxDelta?: number; } & (FromEntry | FromValue, R>); export const createDocumentDomainFromProperty = < R extends "u32" | "u64", DB extends Documents, >(properties: { property: keyof InferT; resolution: R; mergeSegmentMaxDelta?: number; }): ((db: DB) => CustomDocumentDomain>) => { const coerceNumber = (number: number | bigint): NumberFromType => (properties.resolution === "u32" ? number : BigInt(number)) as NumberFromType; return createDocumentDomain({ resolution: properties.resolution, canProjectToOneSegment: (request) => request.sort[0]?.key[0] === properties.property, fromValue: (value) => coerceNumber(value![properties.property]), mergeSegmentMaxDelta: properties.mergeSegmentMaxDelta, }); }; export const createDocumentDomain = >( args: CreateArgs, ): ((db: DB) => CustomDocumentDomain>) => (db: DB) => { let maxValue = args.resolution === "u32" ? MAX_U32 : MAX_U64; let fromEntry = (args as FromEntry>).fromEntry ? (args as FromEntry>).fromEntry! : async ( entry: ShallowEntry | Entry | EntryReplicated, ) => { const item = await ( entry instanceof Entry ? entry : await db.log.log.get(entry.hash) )?.getPayloadValue(); let document: InferT | undefined = undefined; if (!item) { logger.error("Item not found"); } else if (isPutOperation(item)) { document = db.index.valueEncoding.decoder(item.data); } return (args as FromValue).fromValue!( document, entry, ) as NumberFromType>; }; return { type: "custom", resolution: args.resolution as InferR, canProjectToOneSegment: args.canProjectToOneSegment, fromArgs(args) { if (!args) { return { offset: db.log.node.identity.publicKey, length: maxValue as NumberFromType>, }; } return { offset: args.from, length: (args.to - args.from) as NumberFromType>, }; }, fromEntry, canMerge: args.mergeSegmentMaxDelta == null ? undefined : (from, into) => { if ( Math.abs(Number(from.end2 - into.start1)) <= args.mergeSegmentMaxDelta! ) { return true; } if ( Math.abs(Number(from.start1 - into.end2)) <= args.mergeSegmentMaxDelta! ) { return true; } if (from.overlaps(into)) { return true; } return false; }, }; };