import jsonpatch from 'fast-json-patch' ; import type { Operation } from 'fast-json-patch' ; import * as uint8arrays from 'uint8arrays' ; import { randomBytes } from '@stablelib/random' ; import { CreateOpts , LoadOpts , UpdateOpts , Stream , StreamConstructor , StreamStatic , SyncOptions , CeramicCommit , CommitHeader , GenesisCommit , RawCommit , CeramicApi , SignedCommitContainer , StreamMetadata , CeramicSigner , GenesisHeader , } from '@ceramicnetwork/common' ; import { CommitID , StreamID , StreamRef } from '@ceramicnetwork/streamid' ; /**
 * Arguments used to generate the metadata for Tile documents
 */ export interface TileMetadataArgs { /**
     * The DID(s) that are allowed to author updates to this TileDocument
     */ controllers ? : Array < string > ; /**
     * Allows grouping similar documents into "families". Primarily used by indexing services.
     */ family ? : string ; /**
     * Allows tagging documents with additional information. Primarily used by indexing services.
     */ tags ? : Array < string > ; /**
     * If specified, must refer to another TileDocument whose contents are a JSON-schema specification.
     * The content of this document will then be enforced to conform to the linked schema.
     */ schema ? : CommitID | string ; deprecated deterministic ? : boolean ; /**
     * If true, all changes to the 'controllers' array are disallowed.  This guarantees that the
     * Stream will always have the same controller. Especially useful for Streams controlled by
     * DIDs that can have ownership changes within the DID itself, such as did:nft, as setting this
     * would prevent the current holder of the NFT from changing the Stream's controller to that
     * user's personal DID, which would prevent the ownership of the Stream from changing the next
     * time the NFT is traded.
     */ forbidControllerChange ? : boolean ; } const DEFAULT_CREATE_OPTS = { anchor : true , publish : true , sync : SyncOptions . PREFER_CACHE } ; const DEFAULT_LOAD_OPTS = { sync : SyncOptions . PREFER_CACHE } ; const DEFAULT_UPDATE_OPTS = { anchor : true , publish : true , throwOnInvalidCommit : true } ; param metadata param genesis function headerFromMetadata ( metadata , genesis ) { if ( typeof metadata ?. schema === 'string' ) { try { CommitID . fromString ( metadata . schema ) ; } catch { throw new Error ( 'Schema must be a CommitID' ) ; } } const header = { controllers : metadata ?. controllers , family : metadata ?. family , schema : metadata ?. schema ?. toString ( ) , tags : metadata ?. tags , } ; if ( genesis ) { if ( ! metadata ?. deterministic ) { header . unique = uint8arrays . toString ( randomBytes ( 12 ) , 'base64' ) ; } if ( metadata ?. forbidControllerChange ) { header . forbidControllerChange = true ; } } else { if ( metadata ?. deterministic !== undefined || ( metadata as any ) ?. unique !== undefined ) { throw new Error ( "Cannot change 'deterministic' or 'unique' properties on existing Streams" ) ; } if ( metadata ?. forbidControllerChange !== undefined ) { throw new Error ( "Cannot change 'forbidControllerChange' property on existing Streams" ) ; } } Object . keys ( header ) . forEach ( ( key ) => header [ key ] === undefined && delete header [ key ] ) ; return header ; } async function _ensureAuthenticated ( signer ) { if ( signer . did == null ) { throw new Error ( 'No DID provided' ) ; } if ( ! signer . did . authenticated ) { await signer . did . authenticate ( ) ; if ( signer . loggerProvider ) { signer . loggerProvider . getDiagnosticsLogger ( ) . imp ( `Now authenticated as DID ${ signer . did . id }` ) ; } } } param signer param commit private async function _signDagJWS ( signer , commit ) { await _ensureAuthenticated ( signer ) ; return signer . did . createDagJWS ( commit ) ; } async function throwReadOnlyError ( ) { throw new Error ( 'Historical stream commits cannot be modified. Load the stream without specifying a commit to make updates.' ) ; } /**
 * TileDocument stream implementation
 */ @ StreamStatic < StreamConstructor < TileDocument > > ( ) export class TileDocument < T = Record < string , any > > extends Stream { static STREAM_TYPE_NAME = 'tile' ; static STREAM_TYPE_ID = 0 ; private _isReadOnly = false ; get content ( ) : T { return super . content ; } param ceramic param content param metadata param opts static async create < T > ( ceramic , content , metadata ? , opts = { } ) { opts = { ... DEFAULT_CREATE_OPTS , ... opts } ; if ( ! metadata ?. deterministic && opts . syncTimeoutSeconds == undefined ) { opts . syncTimeoutSeconds = 0 ; } const commit = await TileDocument . makeGenesis ( ceramic , content , metadata ) ; return ceramic . createStreamFromGenesis < TileDocument < T > > ( TileDocument . STREAM_TYPE_ID , commit , opts ) ; } param ceramic param genesisCommit param opts static async createFromGenesis < T > ( ceramic , genesisCommit , opts = { } ) { opts = { ... DEFAULT_CREATE_OPTS , ... opts } ; if ( genesisCommit . header ?. unique && opts . syncTimeoutSeconds == undefined ) { opts . syncTimeoutSeconds = 0 ; } const commit = genesisCommit . data ? await _signDagJWS ( ceramic , genesisCommit ) : genesisCommit ; return ceramic . createStreamFromGenesis < TileDocument < T > > ( TileDocument . STREAM_TYPE_ID , commit , opts ) ; } param ceramic param metadata param opts static async deterministic < T > ( ceramic , metadata , opts = { } ) { opts = { ... DEFAULT_CREATE_OPTS , ... opts } ; metadata = { ... metadata , deterministic : true } ; if ( metadata . family == null && metadata . tags == null ) { throw new Error ( 'Family and/or tags are required when creating a deterministic tile document' ) ; } const commit = await TileDocument . makeGenesis ( ceramic , null , metadata ) ; return ceramic . createStreamFromGenesis < TileDocument < T > > ( TileDocument . STREAM_TYPE_ID , commit , opts ) ; } param ceramic param streamId param opts static async load < T > ( ceramic , streamId , opts = { } ) { opts = { ... DEFAULT_LOAD_OPTS , ... opts } ; const streamRef = StreamRef . from ( streamId ) ; if ( streamRef . type != TileDocument . STREAM_TYPE_ID ) { throw new Error ( `StreamID ${ streamRef . toString ( ) } does not refer to a '${ TileDocument . STREAM_TYPE_NAME }' stream, but to a ${ streamRef . typeName }` ) ; } return ceramic . loadStream < TileDocument < T > > ( streamRef , opts ) ; } param content param metadata param opts async update ( content , metadata ? , opts = { } ) { opts = { ... DEFAULT_UPDATE_OPTS , ... opts } ; const signer = opts . asDID ? { did : opts . asDID } : this . api ; const updateCommit = await this . makeCommit ( signer , content , metadata ) ; const updated = await this . api . applyCommit ( this . id , updateCommit , opts ) ; this . state$ . next ( updated . state ) ; } param jsonPatch param opts async patch ( jsonPatch , opts = { } ) { opts = { ... DEFAULT_UPDATE_OPTS , ... opts } ; const header = headerFromMetadata ( this . metadata , false ) ; const rawCommit = { header , data : jsonPatch , prev : this . tip , id : this . state$ . id . cid , } ; const commit = await _signDagJWS ( this . api , rawCommit ) ; const updated = await this . api . applyCommit ( this . id , commit , opts ) ; this . state$ . next ( updated . state ) ; } /**
     * Makes this document read-only. After this has been called any future attempts to call
     * mutation methods on the instance will throw.
     */ makeReadOnly ( ) { this . update = throwReadOnlyError ; this . patch = throwReadOnlyError ; this . sync = throwReadOnlyError ; this . _isReadOnly = true ; } get isReadOnly ( ) : boolean { return this . _isReadOnly ; } param signer param newContent param newMetadata async makeCommit ( signer , newContent , newMetadata ? ) { const header = headerFromMetadata ( newMetadata , false ) ; if ( newContent == null ) { newContent = this . content ; } if ( header . controllers && header . controllers ?. length !== 1 ) { throw new Error ( 'Exactly one controller must be specified' ) ; } const patch = jsonpatch . compare ( this . content , newContent ) ; const commit = { header , data : patch , prev : this . tip , id : this . state . log [ 0 ] . cid , } ; return _signDagJWS ( signer , commit ) ; } param signer param content param metadata static async makeGenesis < T > ( signer , content , metadata ? ) { if ( ! metadata ) { metadata = { } ; } if ( ! metadata . controllers || metadata . controllers . length === 0 ) { if ( signer . did ) { await _ensureAuthenticated ( signer ) ; metadata . controllers = [ signer . did . id ] ; } else { throw new Error ( 'No controllers specified' ) ; } } if ( metadata . controllers ?. length !== 1 ) { throw new Error ( 'Exactly one controller must be specified' ) ; } const header = headerFromMetadata ( metadata , true ) ; if ( metadata ?. deterministic && content ) { throw new Error ( 'Initial content must be null when creating a deterministic Tile document' ) ; } if ( content == null ) { return { header } ; } const commit = { data : content , header } ; return await _signDagJWS ( signer , commit ) ; } }