UNPKG

@liveblocks/core

Version:

Private internals for Liveblocks. DO NOT import directly from this package!

1,424 lines (1,386 loc) 179 kB
import { JSONSchema7 } from 'json-schema'; /** * Throws an error if multiple copies of a Liveblocks package are being loaded * at runtime. This likely indicates a packaging issue with the project. */ declare function detectDupes(pkgName: string, pkgVersion: string | false, // false if not built yet pkgFormat: string | false): void; /** * This helper type is effectively a no-op, but will force TypeScript to * "evaluate" any named helper types in its definition. This can sometimes make * API signatures clearer in IDEs. * * For example, in: * * type Payload<T> = { data: T }; * * let r1: Payload<string>; * let r2: Resolve<Payload<string>>; * * The inferred type of `r1` is going to be `Payload<string>` which shows up in * editor hints, and it may be unclear what's inside if you don't know the * definition of `Payload`. * * The inferred type of `r2` is going to be `{ data: string }`, which may be * more helpful. * * This trick comes from: * https://effectivetypescript.com/2022/02/25/gentips-4-display/ */ type Resolve<T> = T extends (...args: unknown[]) => unknown ? T : { [K in keyof T]: T[K]; }; /** * Relaxes a discriminated union type definition, by explicitly adding * properties defined in any other member as 'never'. * * This makes accessing the members much more relaxed in TypeScript. * * For example: * type MyUnion = Relax< * | { foo: string } * | { foo: number; bar: string; } * | { qux: boolean } * >; * * // With Relax, accessing is much easier: * union.foo; // string | number | undefined * union.bar; // string | undefined * union.qux; // boolean * union.whatever; // Error: Property 'whatever' does not exist on type 'MyUnion' * * // Without Relax, these would all be type errors: * union.foo; // Error: Property 'foo' does not exist on type 'MyUnion' * union.bar; // Error: Property 'bar' does not exist on type 'MyUnion' * union.qux; // Error: Property 'qux' does not exist on type 'MyUnion' */ type Relax<T> = DistributiveRelax<T, T extends any ? keyof T : never>; type DistributiveRelax<T, Ks extends string | number | symbol> = T extends any ? Resolve<{ [K in keyof T]: T[K]; } & { [K in Exclude<Ks, keyof T>]?: never; }> : never; type CustomAuthenticationResult = Relax<{ token: string; } | { error: "forbidden"; reason: string; } | { error: string; reason: string; }>; /** * Represents an indefinitely deep arbitrary JSON data structure. There are * four types that make up the Json family: * * - Json any legal JSON value * - JsonScalar any legal JSON leaf value (no lists or objects) * - JsonArray a JSON value whose outer type is an array * - JsonObject a JSON value whose outer type is an object * */ type Json = JsonScalar | JsonArray | JsonObject; type JsonScalar = string | number | boolean | null; type JsonArray = Json[]; /** * Any valid JSON object. */ type JsonObject = { [key: string]: Json | undefined; }; declare function isJsonScalar(data: Json): data is JsonScalar; declare function isJsonArray(data: Json): data is JsonArray; declare function isJsonObject(data: Json): data is JsonObject; /** * Represents some constraints for user info. Basically read this as: "any JSON * object is fine, but _if_ it has a name field, it _must_ be a string." * (Ditto for avatar.) */ type IUserInfo = { [key: string]: Json | undefined; name?: string; avatar?: string; }; /** * This type is used by clients to define the metadata for a user. */ type BaseUserMeta = { /** * The id of the user that has been set in the authentication endpoint. * Useful to get additional information about the connected user. */ id?: string; /** * Additional user information that has been set in the authentication endpoint. */ info?: IUserInfo; }; declare enum Permission { Read = "room:read", Write = "room:write", PresenceWrite = "room:presence:write", CommentsWrite = "comments:write", CommentsRead = "comments:read" } type RenameDataField<T, TFieldName extends string> = T extends any ? { [K in keyof T as K extends "data" ? TFieldName : K]: T[K]; } : never; type AsyncLoading<F extends string = "data"> = RenameDataField<{ readonly isLoading: true; readonly data?: never; readonly error?: never; }, F>; type AsyncSuccess<T, F extends string = "data"> = RenameDataField<{ readonly isLoading: false; readonly data: T; readonly error?: never; }, F>; type AsyncError<F extends string = "data"> = RenameDataField<{ readonly isLoading: false; readonly data?: never; readonly error: Error; }, F>; type AsyncResult<T, F extends string = "data"> = AsyncLoading<F> | AsyncSuccess<T, F> | AsyncError<F>; type Callback<T> = (event: T) => void; type UnsubscribeCallback = () => void; type Observable<T> = { /** * Register a callback function to be called whenever the event source emits * an event. */ subscribe(callback: Callback<T>): UnsubscribeCallback; /** * Register a one-time callback function to be called whenever the event * source emits an event. After the event fires, the callback is * auto-unsubscribed. */ subscribeOnce(callback: Callback<T>): UnsubscribeCallback; /** * Returns a promise that will resolve when an event is emitted by this * event source. Optionally, specify a predicate that has to match. The first * event matching that predicate will then resolve the promise. */ waitUntil(predicate?: (event: T) => boolean): Promise<T>; }; type EventSource<T> = Observable<T> & { /** * Notify all subscribers about the event. Will return `false` if there * weren't any subscribers at the time the .notify() was called, or `true` if * there was at least one subscriber. */ notify(event: T): boolean; /** * Returns the number of active subscribers. */ count(): number; /** * Observable instance, which can be used to subscribe to this event source * in a readonly fashion. Safe to publicly expose. */ observable: Observable<T>; /** * Disposes of this event source. * * Will clears all registered event listeners. None of the registered * functions will ever get called again. * * WARNING! * Be careful when using this API, because the subscribers may not have any * idea they won't be notified anymore. */ dispose(): void; }; /** * makeEventSource allows you to generate a subscribe/notify pair of functions * to make subscribing easy and to get notified about events. * * The events are anonymous, so you can use it to define events, like so: * * const event1 = makeEventSource(); * const event2 = makeEventSource(); * * event1.subscribe(foo); * event1.subscribe(bar); * event2.subscribe(qux); * * // Unsubscription is pretty standard * const unsub = event2.subscribe(foo); * unsub(); * * event1.notify(); // Now foo and bar will get called * event2.notify(); // Now qux will get called (but foo will not, since it's unsubscribed) * */ declare function makeEventSource<T>(): EventSource<T>; type BatchStore<O, I> = { subscribe: (callback: Callback<void>) => UnsubscribeCallback; enqueue: (input: I) => Promise<void>; getItemState: (input: I) => AsyncResult<O> | undefined; invalidate: (inputs?: I[]) => void; }; declare const kTrigger: unique symbol; /** * Runs a callback function that is allowed to change multiple signals. At the * end of the batch, all changed signals will be notified (at most once). * * Nesting batches is supported. */ declare function batch(callback: Callback<void>): void; type SignalType<S extends ISignal<any>> = S extends ISignal<infer T> ? T : never; interface ISignal<T> { get(): T; subscribe(callback: Callback<void>): UnsubscribeCallback; addSink(sink: DerivedSignal<unknown>): void; removeSink(sink: DerivedSignal<unknown>): void; } /** * Base functionality every Signal implementation needs. */ declare abstract class AbstractSignal<T> implements ISignal<T>, Observable<void> { #private; constructor(equals?: (a: T, b: T) => boolean); dispose(): void; abstract get(): T; get hasWatchers(): boolean; [kTrigger](): void; subscribe(callback: Callback<void>): UnsubscribeCallback; subscribeOnce(callback: Callback<void>): UnsubscribeCallback; waitUntil(): never; markSinksDirty(): void; addSink(sink: DerivedSignal<unknown>): void; removeSink(sink: DerivedSignal<unknown>): void; asReadonly(): ISignal<T>; } declare class Signal<T> extends AbstractSignal<T> { #private; constructor(value: T, equals?: (a: T, b: T) => boolean); dispose(): void; get(): T; set(newValue: T | ((oldValue: T) => T)): void; } declare class DerivedSignal<T> extends AbstractSignal<T> { #private; static from<Ts extends unknown[], V>(...args: [...signals: { [K in keyof Ts]: ISignal<Ts[K]>; }, transform: (...values: Ts) => V]): DerivedSignal<V>; static from<Ts extends unknown[], V>(...args: [...signals: { [K in keyof Ts]: ISignal<Ts[K]>; }, transform: (...values: Ts) => V, equals: (a: V, b: V) => boolean]): DerivedSignal<V>; private constructor(); dispose(): void; get isDirty(): boolean; markDirty(): void; get(): T; /** * Called by the Signal system if one or more of the dependent signals have * changed. In the case of a DerivedSignal, we'll only want to re-evaluate * the actual value if it's being watched, or any of their sinks are being * watched actively. */ [kTrigger](): void; } /** * A MutableSignal is a bit like Signal, except its state is managed by * a single value whose reference does not change but is mutated. * * Similar to how useSyncExternalState() works in React, there is a way to read * the current state at any point in time synchronously, and a way to update * its reference. */ declare class MutableSignal<T extends object> extends AbstractSignal<T> { #private; constructor(initialState: T); dispose(): void; get(): T; /** * Invokes a callback function that is allowed to mutate the given state * value. Do not change the value outside of the callback. * * If the callback explicitly returns `false`, it's assumed that the state * was not changed. */ mutate(callback?: (state: T) => void | boolean): void; } type ContextualPromptResponse = Relax<{ type: "insert"; text: string; } | { type: "replace"; text: string; } | { type: "other"; text: string; }>; type ContextualPromptContext = { beforeSelection: string; selection: string; afterSelection: string; }; declare enum OpCode { INIT = 0, SET_PARENT_KEY = 1, CREATE_LIST = 2, UPDATE_OBJECT = 3, CREATE_OBJECT = 4, DELETE_CRDT = 5, DELETE_OBJECT_KEY = 6, CREATE_MAP = 7, CREATE_REGISTER = 8 } /** * These operations are the payload for {@link UpdateStorageServerMsg} messages * only. */ type Op = AckOp | CreateOp | UpdateObjectOp | DeleteCrdtOp | SetParentKeyOp | DeleteObjectKeyOp; type CreateOp = CreateObjectOp | CreateRegisterOp | CreateMapOp | CreateListOp; type UpdateObjectOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.UPDATE_OBJECT; readonly data: Partial<JsonObject>; }; type CreateObjectOp = { readonly opId?: string; readonly id: string; readonly intent?: "set"; readonly deletedId?: string; readonly type: OpCode.CREATE_OBJECT; readonly parentId: string; readonly parentKey: string; readonly data: JsonObject; }; type CreateListOp = { readonly opId?: string; readonly id: string; readonly intent?: "set"; readonly deletedId?: string; readonly type: OpCode.CREATE_LIST; readonly parentId: string; readonly parentKey: string; }; type CreateMapOp = { readonly opId?: string; readonly id: string; readonly intent?: "set"; readonly deletedId?: string; readonly type: OpCode.CREATE_MAP; readonly parentId: string; readonly parentKey: string; }; type CreateRegisterOp = { readonly opId?: string; readonly id: string; readonly intent?: "set"; readonly deletedId?: string; readonly type: OpCode.CREATE_REGISTER; readonly parentId: string; readonly parentKey: string; readonly data: Json; }; type DeleteCrdtOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.DELETE_CRDT; }; type AckOp = { readonly type: OpCode.DELETE_CRDT; readonly id: "ACK"; readonly opId: string; }; /** * Create an Op that can be used as an acknowledgement for the given opId, to * send back to the originating client in cases where the server decided to * ignore the Op and not forward it. * * Why? * It's important for the client to receive an acknowledgement for this, so * that it can correctly update its own unacknowledged Ops administration. * Otherwise it could get in "synchronizing" state indefinitely. * * CLEVER HACK * Introducing a new Op type for this would not be backward-compatible as * receiving such Op would crash old clients :( * So the clever backward-compatible hack pulled here is that we codify the * acknowledgement as a "deletion Op" for the non-existing node id "ACK". In * old clients such Op is accepted, but will effectively be a no-op as that * node does not exist, but as a side-effect the Op will get acknowledged. */ declare function ackOp(opId: string): AckOp; type SetParentKeyOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.SET_PARENT_KEY; readonly parentKey: string; }; type DeleteObjectKeyOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.DELETE_OBJECT_KEY; readonly key: string; }; declare enum ClientMsgCode { UPDATE_PRESENCE = 100, BROADCAST_EVENT = 103, FETCH_STORAGE = 200, UPDATE_STORAGE = 201, FETCH_YDOC = 300, UPDATE_YDOC = 301 } /** * Messages that can be sent from the client to the server. */ type ClientMsg<P extends JsonObject, E extends Json> = BroadcastEventClientMsg<E> | UpdatePresenceClientMsg<P> | UpdateStorageClientMsg | FetchStorageClientMsg | FetchYDocClientMsg | UpdateYDocClientMsg; type BroadcastEventClientMsg<E extends Json> = { type: ClientMsgCode.BROADCAST_EVENT; event: E; }; type UpdatePresenceClientMsg<P extends JsonObject> = { readonly type: ClientMsgCode.UPDATE_PRESENCE; /** * Set this to any number to signify that this is a Full Presence™ * update, not a patch. * * The numeric value itself no longer has specific meaning. Historically, * this field was intended so that clients could ignore these broadcasted * full presence messages, but it turned out that getting a full presence * "keyframe" from time to time was useful. * * So nowadays, the presence (pun intended) of this `targetActor` field * is a backward-compatible way of expressing that the `data` contains * all presence fields, and isn't a partial "patch". */ readonly targetActor: number; readonly data: P; } | { readonly type: ClientMsgCode.UPDATE_PRESENCE; /** * Absence of the `targetActor` field signifies that this is a Partial * Presence™ "patch". */ readonly targetActor?: undefined; readonly data: Partial<P>; }; type UpdateStorageClientMsg = { readonly type: ClientMsgCode.UPDATE_STORAGE; readonly ops: Op[]; }; type FetchStorageClientMsg = { readonly type: ClientMsgCode.FETCH_STORAGE; }; type FetchYDocClientMsg = { readonly type: ClientMsgCode.FETCH_YDOC; readonly vector: string; readonly guid?: string; readonly v2?: boolean; }; type UpdateYDocClientMsg = { readonly type: ClientMsgCode.UPDATE_YDOC; readonly update: string; readonly guid?: string; readonly v2?: boolean; }; /** * Represents an indefinitely deep arbitrary immutable data * structure, as returned by the .toImmutable(). */ type Immutable = Scalar | ImmutableList | ImmutableObject | ImmutableMap; type Scalar = string | number | boolean | null; type ImmutableList = readonly Immutable[]; type ImmutableObject = { readonly [key: string]: Immutable | undefined; }; type ImmutableMap = ReadonlyMap<string, Immutable>; type UpdateDelta = { type: "update"; } | { type: "delete"; }; /** * "Plain LSON" is a JSON-based format that's used when serializing Live structures * to send them over HTTP (e.g. in the API endpoint to let users upload their initial * Room storage, in the API endpoint to fetch a Room's storage, ...). * * In the client, you would typically create LSON values using: * * new LiveObject({ x: 0, y: 0 }) * * But over HTTP, this has to be serialized somehow. The "Plain LSON" format * is what's used in the POST /init-storage-new endpoint, to allow users to * control which parts of their data structure should be considered "Live" * objects, and which parts are "normal" objects. * * So if they have a structure like: * * { x: 0, y: 0 } * * And want to make it a Live object, they can serialize it by wrapping it in * a special "annotation": * * { * "liveblocksType": "LiveObject", * "data": { x: 0, y: 0 }, * } * * This "Plain LSON" data format defines exactly those wrappings. * * To summarize: * * LSON value | Plain LSON equivalent * ----------------------+---------------------------------------------- * 42 | 42 * [1, 2, 3] | [1, 2, 3] * { x: 0, y: 0 } | { x: 0, y: 0 } * ----------------------+---------------------------------------------- * new LiveList(...) | { liveblocksType: "LiveList", data: ... } * new LiveMap(...) | { liveblocksType: "LiveMap", data: ... } * new LiveObject(...) | { liveblocksType: "LiveObject", data: ... } * */ type PlainLsonFields = Record<string, PlainLson>; type PlainLsonObject = { liveblocksType: "LiveObject"; data: PlainLsonFields; }; type PlainLsonMap = { liveblocksType: "LiveMap"; data: PlainLsonFields; }; type PlainLsonList = { liveblocksType: "LiveList"; data: PlainLson[]; }; type PlainLson = PlainLsonObject | PlainLsonMap | PlainLsonList | Json; type IdTuple<T> = [id: string, value: T]; declare enum CrdtType { OBJECT = 0, LIST = 1, MAP = 2, REGISTER = 3 } type SerializedCrdt = SerializedRootObject | SerializedChild; type SerializedChild = SerializedObject | SerializedList | SerializedMap | SerializedRegister; type SerializedRootObject = { readonly type: CrdtType.OBJECT; readonly data: JsonObject; readonly parentId?: never; readonly parentKey?: never; }; type SerializedObject = { readonly type: CrdtType.OBJECT; readonly parentId: string; readonly parentKey: string; readonly data: JsonObject; }; type SerializedList = { readonly type: CrdtType.LIST; readonly parentId: string; readonly parentKey: string; }; type SerializedMap = { readonly type: CrdtType.MAP; readonly parentId: string; readonly parentKey: string; }; type SerializedRegister = { readonly type: CrdtType.REGISTER; readonly parentId: string; readonly parentKey: string; readonly data: Json; }; declare function isRootCrdt(crdt: SerializedCrdt): crdt is SerializedRootObject; declare function isChildCrdt(crdt: SerializedCrdt): crdt is SerializedChild; type LiveObjectUpdateDelta<O extends { [key: string]: unknown; }> = { [K in keyof O]?: UpdateDelta | undefined; }; /** * A LiveObject notification that is sent in-client to any subscribers whenever * one or more of the entries inside the LiveObject instance have changed. */ type LiveObjectUpdates<TData extends LsonObject> = { type: "LiveObject"; node: LiveObject<TData>; updates: LiveObjectUpdateDelta<TData>; }; /** * The LiveObject class is similar to a JavaScript object that is synchronized on all clients. * Keys should be a string, and values should be serializable to JSON. * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner. */ declare class LiveObject<O extends LsonObject> extends AbstractCrdt { #private; /** * Enable or disable detection of too large LiveObjects. * When enabled, throws an error if LiveObject static data exceeds 128KB, which * is the maximum value the server will be able to accept. * By default, this behavior is disabled to avoid the runtime performance * overhead on every LiveObject.set() or LiveObject.update() call. * * @experimental */ static detectLargeObjects: boolean; /** @private Do not use this API directly */ static _fromItems<O extends LsonObject>(items: IdTuple<SerializedCrdt>[], pool: ManagedPool): LiveObject<O>; constructor(obj?: O); /** * Transform the LiveObject into a javascript object */ toObject(): O; /** * Adds or updates a property with a specified key and a value. * @param key The key of the property to add * @param value The value of the property to add */ set<TKey extends keyof O>(key: TKey, value: O[TKey]): void; /** * Returns a specified property from the LiveObject. * @param key The key of the property to get */ get<TKey extends keyof O>(key: TKey): O[TKey]; /** * Deletes a key from the LiveObject * @param key The key of the property to delete */ delete(key: keyof O): void; /** * Adds or updates multiple properties at once with an object. * @param patch The object used to overrides properties */ update(patch: Partial<O>): void; toImmutable(): ToImmutable<O>; clone(): LiveObject<O>; } /** * Helper type to convert any valid Lson type to the equivalent Json type. * * Examples: * * ToImmutable<42> // 42 * ToImmutable<'hi'> // 'hi' * ToImmutable<number> // number * ToImmutable<string> // string * ToImmutable<string | LiveList<number>> // string | readonly number[] * ToImmutable<LiveMap<string, LiveList<number>>> * // ReadonlyMap<string, readonly number[]> * ToImmutable<LiveObject<{ a: number, b: LiveList<string>, c?: number }>> * // { readonly a: null, readonly b: readonly string[], readonly c?: number } * */ type ToImmutable<L extends Lson | LsonObject> = L extends LiveList<infer I> ? readonly ToImmutable<I>[] : L extends LiveObject<infer O> ? ToImmutable<O> : L extends LiveMap<infer K, infer V> ? ReadonlyMap<K, ToImmutable<V>> : L extends LsonObject ? { readonly [K in keyof L]: ToImmutable<Exclude<L[K], undefined>> | (undefined extends L[K] ? undefined : never); } : L extends Json ? L : never; /** * Returns PlainLson for a given Json or LiveStructure, suitable for calling the storage init api */ declare function toPlainLson(lson: Lson): PlainLson; /** * A LiveMap notification that is sent in-client to any subscribers whenever * one or more of the values inside the LiveMap instance have changed. */ type LiveMapUpdates<TKey extends string, TValue extends Lson> = { type: "LiveMap"; node: LiveMap<TKey, TValue>; updates: { [key: string]: UpdateDelta; }; }; /** * The LiveMap class is similar to a JavaScript Map that is synchronized on all clients. * Keys should be a string, and values should be serializable to JSON. * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner. */ declare class LiveMap<TKey extends string, TValue extends Lson> extends AbstractCrdt { #private; constructor(entries?: readonly (readonly [TKey, TValue])[] | undefined); /** * Returns a specified element from the LiveMap. * @param key The key of the element to return. * @returns The element associated with the specified key, or undefined if the key can't be found in the LiveMap. */ get(key: TKey): TValue | undefined; /** * Adds or updates an element with a specified key and a value. * @param key The key of the element to add. Should be a string. * @param value The value of the element to add. Should be serializable to JSON. */ set(key: TKey, value: TValue): void; /** * Returns the number of elements in the LiveMap. */ get size(): number; /** * Returns a boolean indicating whether an element with the specified key exists or not. * @param key The key of the element to test for presence. */ has(key: TKey): boolean; /** * Removes the specified element by key. * @param key The key of the element to remove. * @returns true if an element existed and has been removed, or false if the element does not exist. */ delete(key: TKey): boolean; /** * Returns a new Iterator object that contains the [key, value] pairs for each element. */ entries(): IterableIterator<[TKey, TValue]>; /** * Same function object as the initial value of the entries method. */ [Symbol.iterator](): IterableIterator<[TKey, TValue]>; /** * Returns a new Iterator object that contains the keys for each element. */ keys(): IterableIterator<TKey>; /** * Returns a new Iterator object that contains the values for each element. */ values(): IterableIterator<TValue>; /** * Executes a provided function once per each key/value pair in the Map object, in insertion order. * @param callback Function to execute for each entry in the map. */ forEach(callback: (value: TValue, key: TKey, map: LiveMap<TKey, TValue>) => void): void; toImmutable(): ReadonlyMap<TKey, ToImmutable<TValue>>; clone(): LiveMap<TKey, TValue>; } type StorageCallback = (updates: StorageUpdate[]) => void; type LiveMapUpdate = LiveMapUpdates<string, Lson>; type LiveObjectUpdate = LiveObjectUpdates<LsonObject>; type LiveListUpdate = LiveListUpdates<Lson>; /** * The payload of notifications sent (in-client) when LiveStructures change. * Messages of this kind are not originating from the network, but are 100% * in-client. */ type StorageUpdate = LiveMapUpdate | LiveObjectUpdate | LiveListUpdate; /** * The managed pool is a namespace registry (i.e. a context) that "owns" all * the individual live nodes, ensuring each one has a unique ID, and holding on * to live nodes before and after they are inter-connected. */ interface ManagedPool { readonly roomId: string; readonly nodes: ReadonlyMap<string, LiveNode>; readonly generateId: () => string; readonly generateOpId: () => string; readonly getNode: (id: string) => LiveNode | undefined; readonly addNode: (id: string, node: LiveNode) => void; readonly deleteNode: (id: string) => void; /** * Dispatching has three responsibilities: * - Sends serialized ops to the WebSocket servers * - Add reverse operations to the undo/redo stack * - Notify room subscribers with updates (in-client, no networking) */ dispatch: (ops: Op[], reverseOps: Op[], storageUpdates: Map<string, StorageUpdate>) => void; /** * Ensures storage can be written to else throws an error. * This is used to prevent writing to storage when the user does not have * permission to do so. * @throws {Error} if storage is not writable * @returns {void} */ assertStorageIsWritable: () => void; } type CreateManagedPoolOptions = { /** * Returns the current connection ID. This is used to generate unique * prefixes for nodes created by this client. This number is allowed to * change over time (for example, when the client reconnects). */ getCurrentConnectionId(): number; /** * Will get invoked when any Live structure calls .dispatch() on the pool. */ onDispatch?: (ops: Op[], reverse: Op[], storageUpdates: Map<string, StorageUpdate>) => void; /** * Will get invoked when any Live structure calls .assertStorageIsWritable() * on the pool. Defaults to true when not provided. Return false if you want * to prevent writes to the pool locally early, because you know they won't * have an effect upstream. */ isStorageWritable?: () => boolean; }; /** * @private Private API, never use this API directly. */ declare function createManagedPool(roomId: string, options: CreateManagedPoolOptions): ManagedPool; declare abstract class AbstractCrdt { #private; get roomId(): string | null; /** * Return an immutable snapshot of this Live node and its children. */ toImmutable(): Immutable; /** * Returns a deep clone of the current LiveStructure, suitable for insertion * in the tree elsewhere. */ abstract clone(): Lson; } type LiveListUpdateDelta = { type: "insert"; index: number; item: Lson; } | { type: "delete"; index: number; deletedItem: Lson; } | { type: "move"; index: number; previousIndex: number; item: Lson; } | { type: "set"; index: number; item: Lson; }; /** * A LiveList notification that is sent in-client to any subscribers whenever * one or more of the items inside the LiveList instance have changed. */ type LiveListUpdates<TItem extends Lson> = { type: "LiveList"; node: LiveList<TItem>; updates: LiveListUpdateDelta[]; }; /** * The LiveList class represents an ordered collection of items that is synchronized across clients. */ declare class LiveList<TItem extends Lson> extends AbstractCrdt { #private; constructor(items: TItem[]); /** * Returns the number of elements. */ get length(): number; /** * Adds one element to the end of the LiveList. * @param element The element to add to the end of the LiveList. */ push(element: TItem): void; /** * Inserts one element at a specified index. * @param element The element to insert. * @param index The index at which you want to insert the element. */ insert(element: TItem, index: number): void; /** * Move one element from one index to another. * @param index The index of the element to move * @param targetIndex The index where the element should be after moving. */ move(index: number, targetIndex: number): void; /** * Deletes an element at the specified index * @param index The index of the element to delete */ delete(index: number): void; clear(): void; set(index: number, item: TItem): void; /** * Returns an Array of all the elements in the LiveList. */ toArray(): TItem[]; /** * Tests whether all elements pass the test implemented by the provided function. * @param predicate Function to test for each element, taking two arguments (the element and its index). * @returns true if the predicate function returns a truthy value for every element. Otherwise, false. */ every(predicate: (value: TItem, index: number) => unknown): boolean; /** * Creates an array with all elements that pass the test implemented by the provided function. * @param predicate Function to test each element of the LiveList. Return a value that coerces to true to keep the element, or to false otherwise. * @returns An array with the elements that pass the test. */ filter(predicate: (value: TItem, index: number) => unknown): TItem[]; /** * Returns the first element that satisfies the provided testing function. * @param predicate Function to execute on each value. * @returns The value of the first element in the LiveList that satisfies the provided testing function. Otherwise, undefined is returned. */ find(predicate: (value: TItem, index: number) => unknown): TItem | undefined; /** * Returns the index of the first element in the LiveList that satisfies the provided testing function. * @param predicate Function to execute on each value until the function returns true, indicating that the satisfying element was found. * @returns The index of the first element in the LiveList that passes the test. Otherwise, -1. */ findIndex(predicate: (value: TItem, index: number) => unknown): number; /** * Executes a provided function once for each element. * @param callbackfn Function to execute on each element. */ forEach(callbackfn: (value: TItem, index: number) => void): void; /** * Get the element at the specified index. * @param index The index on the element to get. * @returns The element at the specified index or undefined. */ get(index: number): TItem | undefined; /** * Returns the first index at which a given element can be found in the LiveList, or -1 if it is not present. * @param searchElement Element to locate. * @param fromIndex The index to start the search at. * @returns The first index of the element in the LiveList; -1 if not found. */ indexOf(searchElement: TItem, fromIndex?: number): number; /** * Returns the last index at which a given element can be found in the LiveList, or -1 if it is not present. The LiveLsit is searched backwards, starting at fromIndex. * @param searchElement Element to locate. * @param fromIndex The index at which to start searching backwards. * @returns */ lastIndexOf(searchElement: TItem, fromIndex?: number): number; /** * Creates an array populated with the results of calling a provided function on every element. * @param callback Function that is called for every element. * @returns An array with each element being the result of the callback function. */ map<U>(callback: (value: TItem, index: number) => U): U[]; /** * Tests whether at least one element in the LiveList passes the test implemented by the provided function. * @param predicate Function to test for each element. * @returns true if the callback function returns a truthy value for at least one element. Otherwise, false. */ some(predicate: (value: TItem, index: number) => unknown): boolean; [Symbol.iterator](): IterableIterator<TItem>; toImmutable(): readonly ToImmutable<TItem>[]; clone(): LiveList<TItem>; } /** * INTERNAL */ declare class LiveRegister<TValue extends Json> extends AbstractCrdt { #private; constructor(data: TValue); get data(): TValue; clone(): TValue; } type LiveStructure = LiveObject<LsonObject> | LiveList<Lson> | LiveMap<string, Lson>; /** * Think of Lson as a sibling of the Json data tree, except that the nested * data structure can contain a mix of Json values and LiveStructure instances. */ type Lson = Json | LiveStructure; /** * LiveNode is the internal tree for managing Live data structures. The key * difference with Lson is that all the Json values get represented in * a LiveRegister node. */ type LiveNode = LiveStructure | LiveRegister<Json>; /** * A mapping of keys to Lson values. A Lson value is any valid JSON * value or a Live storage data structure (LiveMap, LiveList, etc.) */ type LsonObject = { [key: string]: Lson | undefined; }; /** * Helper type to convert any valid Lson type to the equivalent Json type. * * Examples: * * ToJson<42> // 42 * ToJson<'hi'> // 'hi' * ToJson<number> // number * ToJson<string> // string * ToJson<string | LiveList<number>> // string | number[] * ToJson<LiveMap<string, LiveList<number>>> * // { [key: string]: number[] } * ToJson<LiveObject<{ a: number, b: LiveList<string>, c?: number }>> * // { a: null, b: string[], c?: number } * */ type ToJson<T extends Lson | LsonObject> = T extends Json ? T : T extends LsonObject ? { [K in keyof T]: ToJson<Exclude<T[K], undefined>> | (undefined extends T[K] ? undefined : never); } : T extends LiveList<infer I> ? ToJson<I>[] : T extends LiveObject<infer O> ? ToJson<O> : T extends LiveMap<infer KS, infer V> ? { [K in KS]: ToJson<V>; } : never; type DateToString<T> = { [P in keyof T]: T[P] extends Date ? string : T[P] extends Date | null ? string | null : T[P] extends Date | undefined ? string | undefined : T[P]; }; type InboxNotificationThreadData = { kind: "thread"; id: string; roomId: string; threadId: string; notifiedAt: Date; readAt: Date | null; }; type InboxNotificationTextMentionData = { kind: "textMention"; id: string; roomId: string; notifiedAt: Date; readAt: Date | null; createdBy: string; mentionId: string; }; type InboxNotificationTextMentionDataPlain = DateToString<InboxNotificationTextMentionData>; type ActivityData = Record<string, string | boolean | number | undefined>; type InboxNotificationActivity<K extends keyof DAD = keyof DAD> = { id: string; createdAt: Date; data: DAD[K]; }; type InboxNotificationCustomData<K extends keyof DAD = keyof DAD> = { kind: K; id: string; roomId?: string; subjectId: string; notifiedAt: Date; readAt: Date | null; activities: InboxNotificationActivity<K>[]; }; type InboxNotificationData = InboxNotificationThreadData | InboxNotificationCustomData | InboxNotificationTextMentionData; type InboxNotificationThreadDataPlain = DateToString<InboxNotificationThreadData>; type InboxNotificationCustomDataPlain = Omit<DateToString<InboxNotificationCustomData>, "activities"> & { activities: DateToString<InboxNotificationActivity>[]; }; type InboxNotificationDataPlain = InboxNotificationThreadDataPlain | InboxNotificationCustomDataPlain | InboxNotificationTextMentionDataPlain; type InboxNotificationDeleteInfo = { type: "deletedInboxNotification"; id: string; roomId: string; deletedAt: Date; }; type BaseActivitiesData = { [key: `$${string}`]: ActivityData; }; type BaseRoomInfo = { [key: string]: Json | undefined; /** * The name of the room. */ name?: string; /** * The URL of the room. */ url?: string; }; declare global { /** * Namespace for user-defined Liveblocks types. */ export interface Liveblocks { [key: string]: unknown; } } type ExtendableTypes = "Presence" | "Storage" | "UserMeta" | "RoomEvent" | "ThreadMetadata" | "RoomInfo" | "ActivitiesData"; type MakeErrorString<K extends ExtendableTypes, Reason extends string = "does not match its requirements"> = `The type you provided for '${K}' ${Reason}. To learn how to fix this, see https://liveblocks.io/docs/errors/${K}`; type GetOverride<K extends ExtendableTypes, B, Reason extends string = "does not match its requirements"> = GetOverrideOrErrorValue<K, B, MakeErrorString<K, Reason>>; type GetOverrideOrErrorValue<K extends ExtendableTypes, B, ErrorType> = unknown extends Liveblocks[K] ? B : Liveblocks[K] extends B ? Liveblocks[K] : ErrorType; type DP = GetOverride<"Presence", JsonObject, "is not a valid JSON object">; type DS = GetOverride<"Storage", LsonObject, "is not a valid LSON value">; type DU = GetOverrideOrErrorValue<"UserMeta", BaseUserMeta, Record<"id" | "info", MakeErrorString<"UserMeta">>>; type DE = GetOverride<"RoomEvent", Json, "is not a valid JSON value">; type DM = GetOverride<"ThreadMetadata", BaseMetadata>; type DRI = GetOverride<"RoomInfo", BaseRoomInfo>; type DAD = GetOverrideOrErrorValue<"ActivitiesData", BaseActivitiesData, { [K in keyof Liveblocks["ActivitiesData"]]: "At least one of the custom notification kinds you provided for 'ActivitiesData' does not match its requirements. To learn how to fix this, see https://liveblocks.io/docs/errors/ActivitiesData"; }>; type KDAD = keyof DAD extends `$${string}` ? keyof DAD : "Custom notification kinds must start with '$' but your custom 'ActivitiesData' type contains at least one kind which doesn't. To learn how to fix this, see https://liveblocks.io/docs/errors/ActivitiesData"; type BaseMetadata = Record<string, string | boolean | number | undefined>; type CommentReaction = { emoji: string; createdAt: Date; users: { id: string; }[]; }; type CommentAttachment = { type: "attachment"; id: string; name: string; size: number; mimeType: string; }; type CommentLocalAttachmentIdle = { type: "localAttachment"; status: "idle"; id: string; name: string; size: number; mimeType: string; file: File; }; type CommentLocalAttachmentUploading = { type: "localAttachment"; status: "uploading"; id: string; name: string; size: number; mimeType: string; file: File; }; type CommentLocalAttachmentUploaded = { type: "localAttachment"; status: "uploaded"; id: string; name: string; size: number; mimeType: string; file: File; }; type CommentLocalAttachmentError = { type: "localAttachment"; status: "error"; id: string; name: string; size: number; mimeType: string; file: File; error: Error; }; type CommentLocalAttachment = CommentLocalAttachmentIdle | CommentLocalAttachmentUploading | CommentLocalAttachmentUploaded | CommentLocalAttachmentError; type CommentMixedAttachment = CommentAttachment | CommentLocalAttachment; /** * Represents a comment. */ type CommentData = { type: "comment"; id: string; threadId: string; roomId: string; userId: string; createdAt: Date; editedAt?: Date; reactions: CommentReaction[]; attachments: CommentAttachment[]; } & Relax<{ body: CommentBody; } | { deletedAt: Date; }>; type CommentDataPlain = Omit<DateToString<CommentData>, "reactions" | "body"> & { reactions: DateToString<CommentReaction>[]; } & Relax<{ body: CommentBody; } | { deletedAt: string; }>; type CommentBodyBlockElement = CommentBodyParagraph; type CommentBodyInlineElement = CommentBodyText | CommentBodyMention | CommentBodyLink; type CommentBodyElement = CommentBodyBlockElement | CommentBodyInlineElement; type CommentBodyParagraph = { type: "paragraph"; children: CommentBodyInlineElement[]; }; type CommentBodyMention = Relax<CommentBodyUserMention>; type CommentBodyUserMention = { type: "mention"; kind: "user"; id: string; }; type CommentBodyLink = { type: "link"; url: string; text?: string; }; type CommentBodyText = { bold?: boolean; italic?: boolean; strikethrough?: boolean; code?: boolean; text: string; }; type CommentBody = { version: 1; content: CommentBodyBlockElement[]; }; type CommentUserReaction = { emoji: string; createdAt: Date; userId: string; }; type CommentUserReactionPlain = DateToString<CommentUserReaction>; /** * Represents a thread of comments. */ type ThreadData<M extends BaseMetadata = DM> = { type: "thread"; id: string; roomId: string; createdAt: Date; updatedAt: Date; comments: CommentData[]; metadata: M; resolved: boolean; }; interface ThreadDataWithDeleteInfo<M extends BaseMetadata = DM> extends ThreadData<M> { deletedAt?: Date; } type ThreadDataPlain<M extends BaseMetadata> = Omit<DateToString<ThreadData<M>>, "comments" | "metadata"> & { comments: CommentDataPlain[]; metadata: M; }; type ThreadDeleteInfo = { type: "deletedThread"; id: string; roomId: string; deletedAt: Date; }; type StringOperators<T> = T | { startsWith: string; }; /** * This type can be used to build a metadata query string (compatible * with `@liveblocks/query-parser`) through a type-safe API. * * In addition to exact values (`:` in query string), it adds: * - to strings: * - `startsWith` (`^` in query string) */ type QueryMetadata<M extends BaseMetadata> = { [K in keyof M]: (string extends M[K] ? StringOperators<M[K]> : M[K]) | null; }; /** * Pre-defined notification channels support list. */ type NotificationChannel = "email" | "slack" | "teams" | "webPush"; /** * `K` represents custom notification kinds * defined in the augmentation `ActivitiesData` (e.g `liveblocks.config.ts`). * It means the type `NotificationKind` will be shaped like: * thread | textMention | $customKind1 | $customKind2 | ... */ type NotificationKind<K extends keyof DAD = keyof DAD> = "thread" | "textMention" | K; /** * A notification channel settings is a set of notification kinds. * One setting can have multiple kinds (+ augmentation) */ type NotificationChannelSettings = { [K in NotificationKind]: boolean; }; /** * @private * * Base definition of notification settings. * Plain means it's a simple object coming from the remote backend. * * It's the raw settings object where somme channels cannot exists * because there are no notification kinds enabled on the dashboard. * And this object isn't yet proxied by the creator factory `createNotificationSettings`. */ type NotificationSettingsPlain = { [C in NotificationChannel]?: NotificationChannelSettings; }; /** * Notification settings. * One channel for one set of settings. */ type NotificationSettings = { [C in NotificationChannel]: NotificationChannelSettings | null; }; /** * It creates a deep partial specific for `NotificationSettings` * to offer a nice DX when updating the settings (e.g not being forced to define every keys) * and at the same the some preserver the augmentation for custom kinds (e.g `liveblocks.config.ts`). */ type DeepPartialWithAugmentation<T> = T extends object ? { [P in keyof T]?: T[P] extends { [K in NotificationKind]: boolean; } ? Partial<T[P]> & { [K in keyof DAD]?: boolean; } : DeepPartialWithAugmentation<T[P]>; } : T; /** * Partial notification settings with augmentation preserved gracefully. * It means you can update the settings without being forced to define every keys. * Useful when implementing update functions. */ type PartialNotificationSettings = DeepPartialWithAugmentation<NotificationSettingsPlain>; /** * @private * * Creates a `NotificationSettings` object with the given initial plain settings. * It defines a getter for each channel to access the settings and returns `null` with an error log * in case the required channel isn't enabled in the dashboard. * * You can see this function as `Proxy` like around `NotificationSettingsPlain` type. * We can't predict what will be enabled on the dashboard or not, so it's important * provide a good DX to developers by returning `null` completed by an error log * when they try to access a channel that isn't enabled in the dashboard. */ declare function createNotificationSettings(plain: NotificationSettingsPlain): NotificationSettings; /** * @private * * Patch a `NotificationSettings` object by applying notification kind updates * coming from a `PartialNotificationSettings` object. */ declare function patchNotificationSettings(existing: NotificationSettings, patch: PartialNotificationSettings): NotificationSettings; /** * * Utility to check if a notification channel settings * is enabled for every notification kinds. * * Usage: * ```ts * const isEmailChannelEnabled = isNotificationChannelEnabled(settings.email); * ``` */ declare function isNotificationChannelEnabled(settings: NotificationChannelSettings | null): boolean; type RoomThreadsSubscriptionSettings = "all" | "replies_and_mentions" | "none"; type RoomTextMentionsSubscriptionSettings = "mine" | "none"; type RoomSubscriptionSettings = { threads: RoomThreadsSubscriptionSettings; textMentions: RoomTextMentionsSubscriptionSettings; }; type UserRoomSubscriptionSettings = { roomId: string; } & RoomSubscriptionSettings; type SubscriptionData<K extends keyof DAD = keyof DAD> = { kind: NotificationKind<K>; subjectId: string; createdAt: Date; }; type SubscriptionDataPlain = DateToString<SubscriptionData>; type UserSubscriptionData<K extends keyof DAD = keyof DAD> = SubscriptionData<K> & { userId: string; }; type UserSubscriptionDataPlain = DateToString<UserSubscriptionData>; type SubscriptionDeleteInfo = { type: "deletedSubscription"; kind: NotificationKind; subjectId: string; deletedAt: Date; }; type SubscriptionDeleteInfoPlain = DateToString<SubscriptionDeleteInfo>; type SubscriptionKey = `${NotificationKind}:${string}`; declare function getSubscriptionKey(subscription: SubscriptionData | SubscriptionDeleteInfo): SubscriptionKey; declare function getSubscriptionKey(kind: NotificationKind, subjectId: string): SubscriptionKey; /** * Represents a user connected in a room. Treated as immutable. */ type User<P extends JsonObject = DP, U extends BaseUserMeta = DU> = { /** * The connection ID of the User. It is unique and increment at every new connection. */ readonly connectionId: number; /** * The ID of the User that has been set in the authentication endpoint. * Useful to get additional information about the connected user. */ readonly id: U["id"]; /** * Additional user information that has been set in the authentication endpoint. */ readonly info: U["info"]; /** * The user’s presence data. */ readonly presence: P; /** * True if the user can mutate the Room’s Storage and/or YDoc, false if they * can only read but not mutate it. */ readonly canWrite: boolean; /** * True if the user can comment on a thread */ readonly canComment: boolean; }; type InternalOthersEvent<P extends JsonObject, U exten