UNPKG

@liveblocks/core

Version:

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

1,369 lines (1,336 loc) 216 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; declare const Permission: { /** * Default permission for a room. */ readonly Read: "*:read"; readonly Write: "*:write"; /** * Legacy aliases for default room permissions. */ readonly RoomWrite: "room:write"; readonly RoomRead: "room:read"; /** * Storage */ readonly StorageRead: "storage:read"; readonly StorageWrite: "storage:write"; readonly StorageNone: "storage:none"; /** * Comments */ readonly CommentsWrite: "comments:write"; readonly CommentsRead: "comments:read"; readonly CommentsNone: "comments:none"; readonly CommentsPublicWrite: "comments:public:write"; readonly CommentsPublicRead: "comments:public:read"; readonly CommentsPublicNone: "comments:public:none"; readonly CommentsPrivateWrite: "comments:private:write"; readonly CommentsPrivateRead: "comments:private:read"; readonly CommentsPrivateNone: "comments:private:none"; /** * Feeds */ readonly FeedsRead: "feeds:read"; readonly FeedsWrite: "feeds:write"; readonly FeedsNone: "feeds:none"; /** * Legacy */ readonly LegacyRoomPresenceWrite: "room:presence:write"; }; type Permission = (typeof Permission)[keyof typeof Permission]; declare const ACCESS_LEVELS: readonly ["none", "read", "write"]; type AccessLevel = (typeof ACCESS_LEVELS)[number]; type RequiredAccessLevel = "read" | "write"; type PermissionMatrix = { room: AccessLevel; storage: AccessLevel; comments: AccessLevel; "comments:public": AccessLevel; "comments:private": AccessLevel; feeds: AccessLevel; personal: AccessLevel; }; type PermissionResources = keyof PermissionMatrix; type RoomPermissions = Permission[]; type RoomAccesses = Record<string, RoomPermissions>; type UpdateRoomAccesses = Record<string, RoomPermissions | null>; declare function permissionMatrixFromScopes(scopes: RoomPermissions): PermissionMatrix; declare function hasPermissionAccess(matrix: Partial<PermissionMatrix>, resource: PermissionResources, requiredAccess: RequiredAccessLevel): boolean; declare function normalizeRoomPermissions(permissions: string[] | readonly string[]): RoomPermissions; declare function normalizeRoomAccesses(accesses: RoomAccesses | undefined): RoomAccesses | undefined; declare function normalizeUpdateRoomAccesses(accesses: UpdateRoomAccesses | undefined): UpdateRoomAccesses | undefined; /** * Merges permission scopes from multiple sources, by priority: explicit user * accesses override group accesses, which override the room defaults. Groups * all share the same priority, so they are first merged together by taking * the highest access level per feature (and base). */ declare function mergeRoomPermissionScopes({ defaultAccesses, groupsAccesses, userAccesses, }: { defaultAccesses: RoomPermissions; groupsAccesses: RoomPermissions[]; userAccesses: RoomPermissions; }): RoomPermissions; /** * Validates a set of permissions: * - every scope must be a known permission scope, * - exactly one base permission is required (*:read, *:write, or the legacy * aliases room:read, room:write), * - at most one scope per feature (storage, comments, feeds, ...), * - room:presence:write is accepted as an extra legacy scope. * * Returns `true` when the set is valid, or an error message otherwise. */ declare function validatePermissionsSet(scopes: readonly string[]): true | string; 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; }; /** * Like Json, but with readonly arrays and objects. */ type ReadonlyJson = JsonScalar | readonly ReadonlyJson[] | ReadonlyJsonObject; /** * Like JsonObject, but readonly. */ type ReadonlyJsonObject = { readonly [key: string]: ReadonlyJson | 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; }; 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>; setData: (entries: [I, O][]) => void; getItemState: (input: I) => AsyncResult<O> | undefined; getData: (input: I) => O | undefined; invalidate: (inputs?: I[]) => 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 const brand: unique symbol; type Brand<T, TBrand extends string> = T & { [brand]: TBrand; }; type ISODateString = Brand<string, "ISODateString">; type DistributiveOmit<T, K extends PropertyKey> = T extends any ? Omit<T, K> : never; type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P]; }; type WithOptional<T, K extends keyof T> = Omit<T, K> & { [P in K]?: T[P]; }; /** * Throw an error, but as an expression instead of a statement. */ declare function raise(msg: string): never; /** * Drop-in replacement for Object.entries() that retains better types. */ declare function entries<O extends { [key: string]: unknown; }, K extends keyof O>(obj: O): [K, O[K]][]; /** * Drop-in replacement for Object.keys() that retains better types. */ declare function keys<O extends { [key: string]: unknown; }, K extends keyof O>(obj: O): K[]; /** * Creates a new object by mapping a function over all values. Keys remain the * same. Think Array.prototype.map(), but for values in an object. */ declare function mapValues<V, O extends Record<string, unknown>>(obj: O, mapFn: (value: O[keyof O], key: keyof O) => V): { [K in keyof O]: V; }; /** * Alternative to JSON.parse() that will not throw in production. If the passed * string cannot be parsed, this will return `undefined`. */ declare function tryParseJson(rawMessage: string): Json | undefined; /** * Decode base64 string. */ declare function b64decode(b64value: string): string; type RemoveUndefinedValues<T> = { [K in keyof T]-?: Exclude<T[K], undefined>; }; /** * Returns a new object instance where all explictly-undefined values are * removed. */ declare function compactObject<O extends Record<string, unknown>>(obj: O): RemoveUndefinedValues<O>; /** * Returns a promise that resolves after the given number of milliseconds. */ declare function wait(millis: number): Promise<void>; /** * Returns whatever the given promise returns, but will be rejected with * a "Timed out" error if the given promise does not return or reject within * the given timeout period (in milliseconds). */ declare function withTimeout<T>(promise: Promise<T>, millis: number, errmsg: string): Promise<T>; /** * Memoize a promise factory, so that each subsequent call will return the same * pending or success promise. If the promise rejects, will retain that failed * promise for a small time period, after which the next attempt will reset the * memoized value. */ declare function memoizeOnSuccess<T>(factoryFn: () => Promise<T>): () => Promise<T>; /** * Polyfill for Array.prototype.findLastIndex() */ declare function findLastIndex<T>(arr: T[], predicate: (value: T, index: number, obj: T[]) => boolean): number; type OpCode = (typeof OpCode)[keyof typeof OpCode]; declare const OpCode: Readonly<{ 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; }>; declare namespace OpCode { type INIT = typeof OpCode.INIT; type SET_PARENT_KEY = typeof OpCode.SET_PARENT_KEY; type CREATE_LIST = typeof OpCode.CREATE_LIST; type UPDATE_OBJECT = typeof OpCode.UPDATE_OBJECT; type CREATE_OBJECT = typeof OpCode.CREATE_OBJECT; type DELETE_CRDT = typeof OpCode.DELETE_CRDT; type DELETE_OBJECT_KEY = typeof OpCode.DELETE_OBJECT_KEY; type CREATE_MAP = typeof OpCode.CREATE_MAP; type CREATE_REGISTER = typeof OpCode.CREATE_REGISTER; } /** * These operations are the payload for {@link UpdateStorageServerMsg} messages * only. */ type Op = 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 type: OpCode.CREATE_OBJECT; readonly parentId: string; readonly parentKey: string; readonly data: JsonObject; readonly intent?: "set" | "push"; readonly deletedId?: string; }; type CreateListOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.CREATE_LIST; readonly parentId: string; readonly parentKey: string; readonly intent?: "set" | "push"; readonly deletedId?: string; }; type CreateMapOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.CREATE_MAP; readonly parentId: string; readonly parentKey: string; readonly intent?: "set" | "push"; readonly deletedId?: string; }; type CreateRegisterOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.CREATE_REGISTER; readonly parentId: string; readonly parentKey: string; readonly data: Json; readonly intent?: "set" | "push"; readonly deletedId?: string; }; type DeleteCrdtOp = { readonly opId?: string; readonly id: string; readonly type: OpCode.DELETE_CRDT; }; type IgnoredOp = { readonly type: OpCode.DELETE_CRDT; readonly id: "ACK"; readonly opId: string; }; 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; }; type HasOpId = { opId: string; }; /** * Ops sent from client → server. Always includes an opId so the server can * acknowledge the receipt. */ type ClientWireOp = Op & HasOpId; type ClientWireCreateOp = CreateOp & HasOpId; /** * ServerWireOp: Ops sent from server → client. Three variants: * 1. ClientWireOp — Full echo back of our own op, confirming it was applied * 2. IgnoredOp — Our op was seen but intentionally ignored (still counts as ack) * 3. Op without opId — Another client's op being forwarded to us */ type ServerWireOp = ClientWireOp | IgnoredOp | TheirOp; type TheirOp = DistributiveOmit<Op, "opId"> & { opId?: undefined; }; type UpdateDelta = { type: "update"; } | { type: "delete"; deletedItem: Lson; }; /** * 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; toJSON(): { readonly [P in TKey]: ToJson<TValue>; }; clone(): LiveMap<TKey, TValue>; } type CrdtType = (typeof CrdtType)[keyof typeof CrdtType]; declare const CrdtType: Readonly<{ OBJECT: 0; LIST: 1; MAP: 2; REGISTER: 3; }>; declare namespace CrdtType { type OBJECT = typeof CrdtType.OBJECT; type LIST = typeof CrdtType.LIST; type MAP = typeof CrdtType.MAP; type REGISTER = typeof CrdtType.REGISTER; } 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; }; type StorageNode = RootStorageNode | ChildStorageNode; type ChildStorageNode = ObjectStorageNode | ListStorageNode | MapStorageNode | RegisterStorageNode; type RootStorageNode = [id: "root", value: SerializedRootObject]; type ObjectStorageNode = [id: string, value: SerializedObject]; type ListStorageNode = [id: string, value: SerializedList]; type MapStorageNode = [id: string, value: SerializedMap]; type RegisterStorageNode = [id: string, value: SerializedRegister]; type NodeMap = Map<string, SerializedCrdt>; type NodeStream = Iterable<StorageNode>; declare function isRootStorageNode(node: StorageNode): node is RootStorageNode; declare function isObjectStorageNode(node: StorageNode): node is RootStorageNode | ObjectStorageNode; declare function isListStorageNode(node: StorageNode): node is ListStorageNode; declare function isMapStorageNode(node: StorageNode): node is MapStorageNode; declare function isRegisterStorageNode(node: StorageNode): node is RegisterStorageNode; type CompactNode = CompactRootNode | CompactChildNode; type CompactChildNode = CompactObjectNode | CompactListNode | CompactMapNode | CompactRegisterNode; type CompactRootNode = readonly [id: "root", data: JsonObject]; type CompactObjectNode = readonly [ id: string, type: CrdtType.OBJECT, parentId: string, parentKey: string, data: JsonObject ]; type CompactListNode = readonly [ id: string, type: CrdtType.LIST, parentId: string, parentKey: string ]; type CompactMapNode = readonly [ id: string, type: CrdtType.MAP, parentId: string, parentKey: string ]; type CompactRegisterNode = readonly [ id: string, type: CrdtType.REGISTER, parentId: string, parentKey: string, data: Json ]; declare function compactNodesToNodeStream(compactNodes: CompactNode[]): NodeStream; declare function nodeStreamToCompactNodes(nodes: NodeStream): Iterable<CompactNode>; /** * Extracts only the explicitly-named string keys of a type, filtering out * any index signature (e.g. `[key: string]: ...`). */ type KnownKeys<T> = keyof { [K in keyof T as {} extends Record<K, 1> ? never : K]: true; } & string; /** * Per-key sync configuration for node/edge `data` properties. * * true * Sync this property to Liveblocks Storage. Arrays and objects in the value * will be stored as LiveLists and LiveObjects, enabling fine-grained * conflict-free merging. This is the default for all keys. * * false * Don't sync this property. It stays local to the current client. This * property will be `undefined` on other clients. * * "atomic" * Sync this property, but treat it as an indivisible value. The entire value * is replaced as a whole (last-writer-wins) instead of being recursively * converted to LiveObjects/LiveLists. Use this when clients always replace * the value entirely and never need concurrent sub-key merging. * * { ... } * A nested config object for recursively configuring sub-keys of an object. * * @example * ```ts * const sync: SyncConfig = { * label: true, // sync (default) * createdAt: false, // local-only * shape: "atomic", // replaced as a whole, no deep merge * nested: { // recursive config * deep: false, * }, * }; * ``` */ type SyncMode = boolean | "atomic" | SyncConfig; type SyncConfig = { [key: string]: SyncMode | undefined; }; /** * Deeply converts all nested lists to LiveLists, and all nested objects to * LiveObjects. * * As such, the returned result will not contain any Json arrays or Json * objects anymore. */ declare function deepLiveify(value: Json, config?: SyncMode): Lson; /** * Optional keys of O whose non-undefined type is plain Json (not a * LiveStructure). These are the only keys eligible for setLocal(). * Uses KnownKeys to only consider explicitly-named keys, not index signatures. * Checks optionality inline to avoid index signature pollution of OptionalKeys. */ type OptionalJsonKeys<O> = { [K in KnownKeys<O>]: undefined extends O[K] ? Exclude<O[K], undefined> extends Json ? K : never : never; }[KnownKeys<O>]; 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>(nodes: NodeStream, pool: ManagedPool): LiveObject<O>; constructor(obj?: O); /** @private */ keys(): Set<string>; /** * 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; /** * @experimental * * Sets a local-only property that is not synchronized over the wire. The * value will be visible via get(), and toJSON() on this client only. Other * clients and the server will see `undefined` for this key. * * Caveat: this method will not add changes to the undo/redo stack. */ setLocal<TKey extends OptionalJsonKeys<O>>(key: TKey, value: Extract<Exclude<O[TKey], undefined>, Json>): 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; /** * Creates a new LiveObject from a plain JSON object, recursively converting * nested objects to LiveObjects and arrays to LiveLists. */ static from(obj: JsonObject): LiveObject<LsonObject>; /** @private */ static from(obj: JsonObject, config?: SyncConfig): LiveObject<LsonObject>; /** * Reconciles this LiveObject tree to match the given JSON object. Only * mutates keys that actually changed. Keys present on this LiveObject but * absent from `jsonObj` will be deleted. Nested structures are recursively * reconciled. */ reconcile(jsonObj: JsonObject): void; /** @private */ reconcile(jsonObj: JsonObject, config?: SyncConfig): void; /** * Like reconcile(), but only touches the top-level keys present in * `partialObj`. Keys on this LiveObject that are absent from `partialObj` * are left untouched. Typically called on the storage root when * reconciling a subset of keys without affecting other keys on the root. * * Note: the partial behavior only applies to the top-level keys of this * object. Nested structures are always fully reconciled. * * @private */ reconcilePartially(partialObj: JsonObject, config?: SyncConfig): void; toJSON(): ToJson<O>; clone(): LiveObject<O>; } 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; /** * Read-only query surface over {@link UnacknowledgedOps}, handed to CRDTs so * they can look up their own still-pending Create ops without being able to * mutate the set (only the room adds/acks). */ interface ReadonlyUnacknowledgedOps { /** Still-unacknowledged Create ops whose `parentId` is the given one. */ getByParentId(parentId: string): Iterable<ClientWireCreateOp>; /** * Still-unacknowledged Create ops whose `parentId` and `parentKey` are both * the given ones (i.e. targeting one exact position). */ getByParentIdAndKey(parentId: string, parentKey: string): Iterable<ClientWireCreateOp>; /** * Whether the given pending op may already have been processed by the * server. True for ops that were in flight when a connection died: the * server may have stored them with the ack getting lost in the disconnect, * or may never have received them. Until the (re-sent) op's ack arrives, * the client cannot know which, so optimistic predictions that assume the * op has not been processed yet are unsound for these ops. */ isPossiblyStored(opId: string): boolean; } /** * 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 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: ClientWireOp[], 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; /** * Read-only view of the client's still-unacknowledged ops (sent or * pending-send, not yet confirmed by the server). */ readonly unacknowledgedOps: ReadonlyUnacknowledgedOps; } 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: ClientWireOp[], 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; /** * Read-only view of the client's still-unacknowledged ops. Used by CRDTs * (e.g. LiveList) to know which of their optimistic mutations the server * hasn't confirmed yet. Defaults to an empty view (e.g. server-side pools * that dispatch-and-flush have no optimistic state to track). */ unacknowledgedOps?: ReadonlyUnacknowledgedOps; }; /** * @private Private API, never use this API directly. */ declare function createManagedPool(options: CreateManagedPoolOptions): ManagedPool; declare abstract class AbstractCrdt { #private; /** * @private * Returns true if the cached JSON snapshot exists and is reference-equal * to the given value. Does not trigger a recompute. */ hasCache(value: unknown): boolean; /** * Return a JSON-compatible snapshot of this Live node and its children. * LiveObject values become plain objects, LiveList values become arrays, * and LiveMap values also become plain objects (not Map instances). * The result is cached and only recomputed when the contents change. */ toJSON(): ReadonlyJson; /** * 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; /** * 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 LiveList is searched backwards, starting at fromIndex. * @param searchElement Element to locate. * @param fromIndex The index at which to start searching backwards. * @returns The last index of the element in the LiveList; -1 if not found. */ 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>; toJSON(): readonly ToJson<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 = Record<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 | readonly number[] * ToJson<LiveMap<string, LiveList<number>>> * // { readonly [key: string]: readonly number[] } * ToJson<LiveObject<{ a: number, b: LiveList<string>, c?: number }>> * // { readonly a: null, readonly b: readonly string[], readonly c?: number } */ type ToJson<L extends Lson | LsonObject> = L extends LiveList<infer I extends Lson> ? Lson extends I ? readonly ReadonlyJson[] : readonly ToJson<I>[] : L extends LiveObject<infer O extends LsonObject> ? LsonObject extends O ? ReadonlyJsonObject : { readonly [K in keyof O]: ToJson<Exclude<O[K], undefined>> | (undefined extends O[K] ? undefined : never); } : L extends LiveMap<infer KS extends string, infer V extends Lson> ? Lson extends V ? ReadonlyJsonObject : { readonly [K in KS]: ToJson<V>; } : L extends LsonObject ? string extends keyof L ? ReadonlyJsonObject : { readonly [K in keyof L]: ToJson<Exclude<L[K], undefined>> | (undefined extends L[K] ? undefined : never); } : L extends Json ? L : 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 MentionData = Relax<UserMentionData | GroupMentionData>; type UserMentionData = { kind: "user"; id: string; }; type GroupMentionData = { kind: "group"; id: string; userIds?: string[]; }; 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; mention: MentionData; }; 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 BaseGroupInfo = { [key: string]: Json | undefined; /** * The name of the group. */ name?: string; /** * The avatar of the group. */ avatar?: string; /** * The description of the group. */ description?: string; }; 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" | "CommentMetadata" | "FeedMetadata" | "FeedMessageData" | "RoomInfo" | "GroupInfo" | "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 DTM = GetOverride<"ThreadMetadata", BaseMetadata>; type DCM = GetOverride<"CommentMetadata", BaseMetadata>; type DFM = GetOverride<"FeedMetadata", Json, "is not a valid JSON value">; type DFMD = GetOverride<"FeedMessageData", Json, "is not a valid JSON value">; type DRI = GetOverride<"RoomInfo", BaseRoomInfo>; type DGI = GetOverride<"GroupInfo", BaseGroupInfo>; 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<CM extends BaseMetadata = DCM> = { type: "comment"; id: string; threadId: string; roomId: string; userId: string; createdAt: Date; editedAt?: Date; reactions: CommentReaction[]; attachments: CommentAttachment[]; metadata: CM; } & Relax<{ body: CommentBody; } | { deletedAt: Date; }>; type CommentDataPlain<CM extends BaseMetadata = DCM> = Omit<DateToString<CommentData<CM>>, "reactions" | "body" | "metadata"> & { reactions: DateToString<CommentReaction>[]; metadata: CM; } & 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 | CommentBodyGroupMention>; type CommentBodyUserMention = { type: "mention"; kind: "user"; id: string; }; type CommentBodyGroupMention =