@m-ld/m-ld-spec
Version:
m-ld Protocol Specification
285 lines (272 loc) • 10.8 kB
text/typescript
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
}