// // Copyright 2021 DXOS.org // import * as Schema from 'effect/Schema'; import { type MulticastObservable } from '@dxos/async'; import { type SpecificCredential } from '@dxos/credentials'; import { type Obj } from '@dxos/echo'; import { type EchoDatabase, type SpaceSyncState } from '@dxos/echo-client'; import { type PublicKey, type SpaceId } from '@dxos/keys'; import { type Messenger } from '@dxos/protocols'; import { type Contact, type CreateEpochRequest, type Invitation, SpaceArchive, type Space as SpaceData, type SpaceMember, type SpaceState, type UpdateMemberRoleRequest, } from '@dxos/protocols/proto/dxos/client/services'; import { type EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata'; import { type SpaceSnapshot } from '@dxos/protocols/proto/dxos/echo/snapshot'; import { type Credential, type Epoch, type MembershipPolicy } from '@dxos/protocols/proto/dxos/halo/credentials'; import { type CancellableInvitation } from './invitations'; import { type SpaceProperties } from './types'; export type CreateEpochOptions = { migration?: CreateEpochRequest.Migration; automergeRootUrl?: string; }; export type ExportSpaceOptions = { /** * Archive format. * @default SpaceArchive.Format.BINARY */ format?: SpaceArchive.Format; }; export interface SpaceInternal { get db(): EchoDatabase; get data(): SpaceData; getCredentials(): Promise; getEpochs(): Promise[]>; // TODO(dmaretskyi): Return epoch info. createEpoch(options?: CreateEpochOptions): Promise; // TOOD(burdon): Start to factor out credentials. removeMember(memberKey: PublicKey): Promise; export(options?: ExportSpaceOptions): Promise; /** * Migrate space data to the latest version. */ migrate(): Promise; setEdgeReplicationPreference(setting: EdgeReplicationSetting): Promise; /** * Waits until the space is fully synced with EDGE. * @throws If the EDGE sync is disabled. */ syncToEdge(opts?: { onProgress: (state: SpaceSyncState.PeerState | undefined) => void; timeout?: number; }): Promise; } export const SPACE_TAG = Symbol('dxos.client.protocol.Space'); export interface Space extends Messenger { readonly [SPACE_TAG]: true; /** * @deprecated Use `id`. */ get key(): PublicKey; /** * Unique space identifier. */ get id(): SpaceId; /** * Echo database. */ get db(): EchoDatabase; // TODO(burdon): Replace with state? get isOpen(): boolean; /** * Immutable tags assigned at space creation time. * Available on closed spaces. */ get tags(): string[]; /** * Immutable membership policy assigned at space creation time. * Available on closed spaces. */ get membershipPolicy(): MembershipPolicy; /** * Current state of the space. * The database is ready to be used in `SpaceState.SPACE_READY` state. * Presence is available in `SpaceState.SPACE_CONTROL_ONLY` state. */ get state(): MulticastObservable; /** * Properties object. */ get properties(): Obj.OfShape; /** * Current state of space pipeline. */ get pipeline(): MulticastObservable; get invitations(): MulticastObservable; get members(): MulticastObservable; /** * @deprecated */ // TODO(wittjosiah): Audit and remove. This should not be exposed (or marked as internal). get internal(): SpaceInternal; /** * Activates the space enabling the use of the database and starts replication with peers. * The setting is persisted on the local device. */ // TODO(wittjosiah): Rename activate/deactivate? open(): Promise; /** * Deactivates the space stopping replication with other peers. * The space will not auto-open on the next app launch. * The setting is persisted on the local device. */ close(): Promise; /** * Tombstones (soft-deletes) the space and replicates the deletion to the user's other devices. * Every device stops replicating and unloads the space. Underlying data is not removed until * garbage collection (future work). This is terminal: the space cannot be re-opened via {@link open}. */ delete(): Promise; /** * Waits until the space is in the ready state, with database initialized. */ waitUntilReady(): Promise; createSnapshot(): Promise; // TODO(burdon): Create invitation? // TODO(burdon): Factor out membership, etc. share(options?: Partial): CancellableInvitation; admitContact(contact: Contact): Promise; updateMemberRole(request: Omit): Promise; } export const isSpace = (object: unknown): object is Space => typeof object === 'object' && object != null && (object as Space)[SPACE_TAG] === true; // TODO(burdon): Create lower-level definition (HasId, db, etc.) and move to @dxos/echo. export const SpaceSchema: Schema.Schema = Schema.Any.pipe( Schema.filter((space) => isSpace(space)), Schema.annotations({ title: 'Space' }), );