import type { BulkWriteRow, EventBulk, PreparedQuery, RxDocumentData, RxDocumentDataById, RxStorageBulkWriteResponse, RxStorageChangeEvent, RxStorageCountResult, RxStorageInstanceCreationParams, RxStorageQueryResult } from 'nxdb-old/src/types/rx-storage'; import type { DeepReadonly, JsonSchema, MangoQuery, MangoQuerySelector, MangoQuerySortPart, Override, RxConflictResultionTask, RxConflictResultionTaskSolution, RxJsonSchema, RxQueryPlan } from 'nxdb-old/src/types'; import type { Observable } from 'rxjs'; /** * RxStorage * This is an interface that abstracts the storage engine. * This allows us to use NxDB with different storage engines. * * @link https://nxpkg.github.io/nxdb/rx-storage.html * @link https://github.com/nxpkg/nxdb/issues/1636 */ /** * A RxStorage is a module that acts * as a factory that can create multiple RxStorageInstance * objects. * * All data inputs and outputs of a StorageInstance must be plain json objects. * Do not use Map, Set or anything else that cannot be JSON.stringify-ed. * This will ensure that the storage can exchange data * when it is a WebWorker or a WASM process or data is send via BroadcastChannel. */ export interface RxStorage { /** * name of the storage engine * used to detect if plugins do not work so we can throw proper errors. */ readonly name: string; /** * Static functions */ readonly statics: RxStorageStatics; /** * Creates a storage instance * that can contain the NoSQL documents of a collection. */ createStorageInstance( params: RxStorageInstanceCreationParams ): Promise>; } /** * User provided mango queries will be filled up by NxDB via normalizeMangoQuery() * so we do not have to do many if-field-exist tests in the internals. */ export type FilledMangoQuery = Override< MangoQuery, { /** * The selector is required here. */ selector: MangoQuerySelector; /** * In contrast to the user-provided MangoQuery, * the sorting is required here because * NxDB has to ensure that the primary key is always * part of the sort params. */ sort: MangoQuerySortPart[]; /** * In the normalized mango query, * the index must always be a string[], * never just a string. * This makes it easier to use the query because * we do not have to do an array check. */ index?: string[]; /** * Skip must be set which defaults to 0 */ skip: number; } >; /** * Static functions of the RxStorage. * Can be used without creating an instance of any kind. * These functions are not directly children of RxStorage because * we might need them without having to import the whole storage engine. * For example when the Worker plugin is used, the main process only needs the * static functions, while the worker process needs the whole storage engine. */ export type RxStorageStatics = Readonly<{ /** * Storages can have some bugs * and behaviors that must be worked around * before querying the db. * * Also some storages do optimizations * and other things related to query planning. * * For performance reason this preparation * runs in a single step so it can be cached * when the query is used multiple times. * * @returns a format of the query that can be used with the storage * when calling RxStorageInstance().query() */ prepareQuery( schema: RxJsonSchema>, /** * a query that can be mutated by the function without side effects. */ mutateableQuery: FilledMangoQuery ): PreparedQuery; /** * Contains the JsonSchema that matches the checkpoint * of this RxStorage. * Used in some plugins like the graphql plugin * where it is used to create a GraphQL Schema from the checkpoint. */ checkpointSchema: DeepReadonly; }>; export type DefaultPreparedQuery = { query: FilledMangoQuery; queryPlan: RxQueryPlan; }; export interface RxStorageInstance< /** * The type of the documents that can be stored in this instance. * All documents in an instance must comply to the same schema. * Also all documents are RxDocumentData with the meta properties like * _deleted or _rev etc. */ RxDocType, Internals, InstanceCreationOptions, CheckpointType = any > { readonly databaseName: string; /** * Returns the internal data that is used by the storage engine. */ readonly internals: Readonly; readonly options: Readonly; /** * The schema that defines the documents that are stored in this instance. * Notice that the schema must be enhanced with the meta properties like * _meta, _rev and _deleted etc. which are added by fillWithDefaultSettings() */ readonly schema: Readonly>>; readonly collectionName: string; /** * Writes multiple documents to the storage instance. * The write for each single document is atomic, there * is no transaction around all documents. * The written documents must be the newest revision of that documents data. * If the previous document is not the current newest revision, a conflict error * must be returned. * It must be possible that some document writes succeed * and others error. We need this to have a similar behavior as most NoSQL databases. */ bulkWrite( documentWrites: BulkWriteRow[], /** * Context will be used in all * changeStream()-events that are emitted as a result * of that bulkWrite() operation. * Used in plugins so that we can detect that event X * comes from operation Y. */ context: string ): Promise< /** * returns the response, split into success and error lists. */ RxStorageBulkWriteResponse >; /** * Get Multiple documents by their primary value. * This must also return deleted documents. */ findDocumentsById( /** * List of primary values * of the documents to find. */ ids: string[], /** * If set to true, deleted documents will also be returned. */ withDeleted: boolean ): Promise>; /** * Runs a NoSQL 'mango' query over the storage * and returns the found documents data. * Having all storage instances behave similar * is likely the most difficult thing when creating a new * rx-storage implementation. */ query( /** * Here we get the result of this.prepareQuery() * instead of the plain mango query. * This makes it easier to have good performance * when transformations of the query must be done. */ preparedQuery: PreparedQuery ): Promise>; /** * Returns the amount of non-deleted documents * that match the given query. * Sort, skip and limit of the query must be ignored! */ count( preparedQuery: PreparedQuery ): Promise; /** * Returns the plain data of a single attachment. */ getAttachmentData( documentId: string, attachmentId: string, digest: string ): Promise; /** * Returns the current (not the old!) data of all documents that have been changed AFTER the given checkpoint. * If the returned array does not reach the limit, it can be assumed that the "end" is reached, when paginating over the changes. * Also returns a new checkpoint for each document which can be used to continue with the pagination from that change on. * Must never return the same document multiple times in the same call operation. * This is used by NxDB to known what has changed since X so these docs can be handled by the backup or the replication * plugin. */ getChangedDocumentsSince( limit: number, /** * The checkpoint from with to start * when the events are sorted in time. * If we want to start from the beginning, * undefined is used as a checkpoint. */ checkpoint?: CheckpointType ): Promise<{ documents: RxDocumentData[]; /** * The checkpoint contains data so that another * call to getChangedDocumentsSince() will continue * from exactly the last document that was returned before. */ checkpoint: CheckpointType; }>; /** * Returns an ongoing stream * of all changes that happen to the * storage instance. * Do not forget to unsubscribe. * * If the RxStorage support multi-instance, * and the storage is persistent, * then the emitted changes of one RxStorageInstance * must be also emitted to other instances with the same databaseName+collectionName. * See ./rx-storage-multiinstance.ts */ changeStream(): Observable, CheckpointType>>; /** * Runs a cleanup that removes all tompstones * of documents that have _deleted set to true * to free up disc space. * * Returns true if all cleanable documents have been removed. * Returns false if there are more documents to be cleaned up, * but not all have been purged because that would block the storage for too long. */ cleanup( /** * The minimum time in milliseconds * of how long a document must have been deleted * until it is purged by the cleanup. */ minimumDeletedTime: number ): Promise< /** * True if all docs cleaned up, * false if there are more docs to clean up */ boolean >; /** * Closes the storage instance so it cannot be used * anymore and should clear all memory. * The returned promise must resolve when everything is cleaned up. */ close(): Promise; /** * Remove the database and * deletes all of its data. */ remove(): Promise; /** * Instead of passing the conflict-resolver function * into the storage, we have to work with an observable that emits tasks * and a resolver that takes resolved tasks. * This is needed because the RxStorageInstance might run inside of a Worker * other JavaScript process, so we cannot pass plain code. */ conflictResultionTasks(): Observable>; resolveConflictResultionTask(taskSolution: RxConflictResultionTaskSolution): Promise; }