UNPKG

@m-ld/m-ld-spec

Version:

m-ld Protocol Specification

285 lines (272 loc) 10.8 kB
import type { Context, Group, Read, Subject, Update } from 'json-rql'; import type { Observable } from 'rxjs'; /** * A **m-ld** clone represents domain data to an app. This is the abstract * interface implemented by a clone engine. It adheres to the **m-ld** data * [concurrency](https://m-ld.org/doc/#concurrency) contract. * * > This abstract definition will be realised differently depending on the * > capabilities and idioms of the engine platform. It may offer additional * > data features, such as data persistence between re-starts; and API features * > such as language-specific convenience methods. Refer to the clone * > [engine](https://m-ld.org/doc/#platforms) documentation to begin using it. */ export interface MeldClone { /** * Actively reads data from the domain. * * An engine can legitimately offer a limited subset of the full **json-rql** * syntax for the `request` parameter, and reject patterns that it does not * support with an `Unsupported pattern` error. * * @param request the declarative read description * @returns an observable stream of subjects. * @see [Read](http://json-rql.org/interfaces/read.html) * @see [Subject](http://json-rql.org/interfaces/subject.html) */ read(request: Read): Observable<Subject>; /** * Actively writes data to the domain. A write can be: * - A [Subject](http://json-rql.org/interfaces/subject.html) (any JSON object * not a Read, Group or Update). Interpreted as data to be inserted. * - A [Group](http://json-rql.org/interfaces/group.html) containing only a * `@graph` key. Interpreted as containing the data to be inserted. * - An [Update](http://json-rql.org/interfaces/update.html) with either an * `@insert`, `@delete`, or both. * * An engine can legitimately offer a limited subset of the full **json-rql** * syntax for the `request` parameter, and reject patterns that it does not * support with an `Unsupported pattern` error. * * @param request the declarative transaction description * @returns final completion or error of the transaction. */ write(request: Subject | Group | Update): Promise<unknown>; /** * Follow updates from the domain. All data changes are signalled through the * returned stream, strictly ordered according to the clone's logical clock. * The updates can therefore be correctly used to maintain some other view of * data, for example in a user interface or separate database. * * @param after updates will be emitted to the returned stream after (not * including) the given tick count for the clone's logical clock. This tick * count can be in the past or future. If the clone is unable to recall * updates from a too-distant past, the stream will fail with `Updates * unavailable`. * @returns an observable stream of updates from the domain. */ follow(after?: number): Observable<MeldUpdate>; /** * The current and future status of a clone. This stream is hot and * continuous, terminating when the clone closes (and can therefore be used to * detect closure). */ readonly status: LiveStatus; } /** * An update event signalling a write operation, which may have been transacted * locally in this clone, or remotely on another clone. */ export interface MeldUpdate { /** * Partial subjects, containing properties that have been deleted from the * domain. Note that deletion of a property (even of all properties) does not * necessarily indicate that the subject's identity is not longer represented * in the domain. */ '@delete': Subject[]; /** * Partial subjects, containing properties that have been inserted into the * domain. */ '@insert': Subject[]; /** * Current local clock ticks at the time of the update. * @see MeldStatus.ticks */ '@ticks': number; } /** * A means to obtain the current status, and await future statuses, of a clone. */ export interface LiveStatus extends Observable<MeldStatus> { /** * The current clone status */ readonly value: MeldStatus; /** * @returns a promise of a future status matching the given partial status * (with the exception of the `@ticks` field, which is ignored if specified). * If the clone never achieves the requested status, the promise will resolve * to `undefined` when the clone closes. */ becomes: (match?: Partial<MeldStatus>) => Promise<MeldStatus | undefined>; } export interface MeldStatus { /** * Whether the clone is attached to the domain and able to receive updates. */ online: boolean; /** * Whether the clone needs to catch-up with the latest updates from the * domain. For convenience, this flag will have the value `false` in * indeterminate scenarios such as if there are no other live clones on the * domain (this is a "silo"). */ outdated: boolean; /** * Whether this clone is the only one attached to a domain. Being a silo may * be a danger to data safety, as any changes made to a silo clone are not * being backed-up on any other clone. */ silo: boolean; /** * Current local clock ticks at the time of the status change. This can be * used in a subsequent call to {@link MeldClone.follow}, to ensure no updates * are missed. * * This clock is *strictly* local, and there is no relationship between the * clock ticks of one clone and that of another, even for the same transaction. * * @see MeldUpdate.@ticks */ ticks: number; } /** * **m-ld** clone configuration, used to initialise a {@link MeldClone} for use. * The use of this interface is optional, since clone initialisation is part of * an engine's application API. */ export interface MeldConfig { /** * The local identity of the m-ld clone session, used for message bus identity * and logging. This identity does not need to be the same across re-starts of * a clone with persistent data. It must be unique among the clones for the * domain. */ '@id': string; /** * A URI domain name, which defines the universal identity of the dataset * being manipulated by a set of clones (for example, on the configured * message bus). For a clone with persistent data from a prior session, this * *must* be the same as the previous session. */ '@domain': string; /** * Set to `true` to indicate that this clone will be 'genesis'; that is, the * first new clone on a new domain. This flag will be ignored if the clone is * not new. If `false`, and this clone is new, successful clone initialisation * is dependent on the availability of another clone. If set to true, and * subsequently another non-new clone appears on the same domain, either or * both clones will immediately close to preserve their data integrity. */ genesis: boolean; /** * An optional JSON-LD context for the domain data. If not specified: * * `@base` defaults to `http://{domain}` * * `@vocab` defaults to the resolution of `/#` against the base */ '@context'?: Context; } // noinspection JSUnusedGlobalSymbols /** * Errors that occur in a **m-ld** engine should be signalled with the given * error codes where appropriate. The means by which errors are signalled is * platform-specific. */ export enum MeldErrorStatus { /** * No error has occurred. */ 'No error' = 0, ////////////////////////////////////////////////////////////////////////////// // Bad request errors /** * A **json-rql** pattern has been specified that neither reads nor writes * data, for example a Group with variable content. */ 'Pattern is not read or writeable' = 4001, /** * The requested transaction results in a delta that is too large to transmit * using the current message publishing implementation. */ 'Delta too big' = 4002, ////////////////////////////////////////////////////////////////////////////// // Unauthorised error /** * A request was made for data for which the current security principal does * not have access rights. */ 'Unauthorised' = 4030, ////////////////////////////////////////////////////////////////////////////// // Not found error /** * A request was made for updates in the too-distant past. This can occur when * following clone updates, or when re-starting a clone that has been offline * for too long. */ 'Updates unavailable' = 4041, ////////////////////////////////////////////////////////////////////////////// // Internal errors /** * A serious error has occurred in the engine implementation. */ 'Unknown error' = 5000, /** * The engine has received an update that it cannot parse. This may be due to * a version inconsistency, or a bad actor. */ 'Bad update' = 5001, /** * The engine has received a response that it cannot parse. This may be due * to a version inconsistency, or a bad actor. */ 'Bad response' = 5002, /** * The engine has received a rejection from another engine on the domain to * one of its requests. This could lead to the clone failing to initialise, or * shutting down shortly after initialisation. */ 'Request rejected' = 5003, /** * The engine has attempted an operation that requires other clones to be * visible. This indicates a concurrency problem in the engine. */ 'Meld is offline' = 5004, /** * An update from another clone has arrived out-of-order, and the clone has * not been able to recover. This indicates a concurrency problem in the * engine. */ 'Update out of order' = 5005, ////////////////////////////////////////////////////////////////////////////// // Unsupported operations /** * The engine does not support the pattern in the transaction request. */ 'Unsupported pattern' = 5011, ////////////////////////////////////////////////////////////////////////////// // Service unavailable /** * This is a new clone on the domain, but no other clones are visible, * possibly due to a network partition. The clone cannot initialise. */ 'No visible clones' = 5031, /** * This clone has been closed, explicitly by the app or due to an error. All * subsequent transactions will fail. */ 'Clone has closed' = 5032, /** * The clone data is not writeable due to a platform limitation such as file * locking, or concurrent access controls. */ 'Clone data is locked' = 5034, /** * The clone's data is out of date and no other clones have kept sufficient * information to recover it. The app could re-try initialising the clone * later if, for example, the architecture includes a clone which keeps a long * history, but it is currently unavailable. */ 'Clone outdated' = 5035 }