diff --git a/.github/workflows/tests-ci.yml b/.github/workflows/tests-ci.yml index cf98b6b5f..04c379a58 100644 --- a/.github/workflows/tests-ci.yml +++ b/.github/workflows/tests-ci.yml @@ -107,3 +107,36 @@ jobs: run: npm run test:browser --ws -- --color env: TEST_DWN_URL: http://localhost:3000 + + tbdocs-reporter: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + + - name: Set up Node.js + uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + + - name: Install latest npm + run: npm install -g npm@latest + + - name: Install dependencies + run: npm ci + + - name: Build all workspace packages + run: npm run build + + - name: TBDocs Reporter + id: tbdocs-reporter-protocol + uses: TBD54566975/tbdocs@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + report_changed_scope_only: false + fail_on_error: false + entry_points: | + - file: packages/api/src/index.ts + docsReporter: api-extractor + docsGenerator: typedoc-markdown diff --git a/packages/api/src/did-api.ts b/packages/api/src/did-api.ts index d28474556..ab51e3f87 100644 --- a/packages/api/src/did-api.ts +++ b/packages/api/src/did-api.ts @@ -28,6 +28,12 @@ import type { Web5Agent } from '@web5/agent'; // didMethodApis: DidMethodApi[]; // cache?: DidResolverCache; // } + +/** + * The DID API is used to create and resolve DIDs. + * + * @beta + */ export class DidApi { // private didResolver: DidResolver; // private methodCreatorMap: Map = new Map(); diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts index 11bccc120..8ce82ec0f 100644 --- a/packages/api/src/dwn-api.ts +++ b/packages/api/src/dwn-api.ts @@ -20,33 +20,73 @@ import { Record } from './record.js'; import { Protocol } from './protocol.js'; import { dataToBlob } from './utils.js'; +/** + * Request to setup a protocol with its definitions + * + * @beta + */ export type ProtocolsConfigureRequest = { message: Omit; } +/** + * Response for the protocol configure request + * + * @beta + */ export type ProtocolsConfigureResponse = { status: UnionMessageReply['status']; protocol?: Protocol; } +/** + * Represents each entry on the protocols query reply + * + * @beta + */ export type ProtocolsQueryReplyEntry = { descriptor: ProtocolsConfigureDescriptor; }; +/** + * Request to query protocols + * + * @beta + */ export type ProtocolsQueryRequest = { from?: string; message: Omit } +/** + * Response with the retrieved protocols + * + * @beta + */ export type ProtocolsQueryResponse = { protocols: Protocol[]; status: UnionMessageReply['status']; } +/** + * Type alias for {@link RecordsWriteRequest} + * + * @beta + */ export type RecordsCreateRequest = RecordsWriteRequest; +/** + * Type alias for {@link RecordsWriteResponse} + * + * @beta + */ export type RecordsCreateResponse = RecordsWriteResponse; +/** + * Request to create a record from an existing one (useful for updating an existing record) + * + * @beta + */ export type RecordsCreateFromRequest = { author: string; data: unknown; @@ -54,50 +94,92 @@ export type RecordsCreateFromRequest = { record: Record; } +/** + * Request to delete a record from the DWN + * + * @beta + */ export type RecordsDeleteRequest = { from?: string; message: Omit; } +/** + * Response for the delete request + * + * @beta + */ export type RecordsDeleteResponse = { status: UnionMessageReply['status']; }; +/** + * Request to query records from the DWN + * + * @beta + */ export type RecordsQueryRequest = { /** The from property indicates the DID to query from and return results. */ from?: string; message: Omit; } +/** + * Response for the query request + * + * @beta + */ export type RecordsQueryResponse = { status: UnionMessageReply['status']; records?: Record[] }; +/** + * Request to read a record from the DWN + * + * @beta + */ export type RecordsReadRequest = { /** The from property indicates the DID to read from and return results fro. */ from?: string; message: Omit; } +/** + * Response for the read request + * + * @beta + */ export type RecordsReadResponse = { status: UnionMessageReply['status']; record: Record; }; +/** + * Request to write a record to the DWN + * + * @beta + */ export type RecordsWriteRequest = { data: unknown; message?: Omit, 'authorizationSigner'>; store?: boolean; } +/** + * Response for the write request + * + * @beta + */ export type RecordsWriteResponse = { status: UnionMessageReply['status']; record?: Record }; /** - * TODO: Document class. + * Interface to interact with DWN Records and Protocols + * + * @beta */ export class DwnApi { private agent: Web5Agent; @@ -109,12 +191,12 @@ export class DwnApi { } /** - * TODO: Document namespace. - */ + * API to interact with DWN protocols (e.g., `dwn.protocols.configure()`). + */ get protocols() { return { /** - * TODO: Document method. + * Configure method, used to setup a new protocol (or update) with the passed definitions */ configure: async (request: ProtocolsConfigureRequest): Promise => { const agentResponse = await this.agent.processDwnRequest({ @@ -136,7 +218,7 @@ export class DwnApi { }, /** - * TODO: Document method. + * Query the available protocols */ query: async (request: ProtocolsQueryRequest): Promise => { const agentRequest = { @@ -171,19 +253,19 @@ export class DwnApi { } /** - * TODO: Document namespace. + * API to interact with DWN records (e.g., `dwn.records.create()`). */ get records() { return { /** - * TODO: Document method. + * Alias for the `write` method */ create: async (request: RecordsCreateRequest): Promise => { return this.records.write(request); }, /** - * TODO: Document method. + * Write a record based on an existing one (useful for updating an existing record) */ createFrom: async (request: RecordsCreateFromRequest): Promise => { const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON(); @@ -221,7 +303,7 @@ export class DwnApi { }, /** - * TODO: Document method. + * Delete a record */ delete: async (request: RecordsDeleteRequest): Promise => { const agentRequest = { @@ -253,7 +335,7 @@ export class DwnApi { }, /** - * TODO: Document method. + * Query a single or multiple records based on the given filter */ query: async (request: RecordsQueryRequest): Promise => { const agentRequest = { @@ -287,7 +369,7 @@ export class DwnApi { }, /** - * TODO: Document method. + * Read a single record based on the given filter */ read: async (request: RecordsReadRequest): Promise => { const agentRequest = { @@ -331,7 +413,7 @@ export class DwnApi { }, /** - * TODO: Document method. + * Writes a record to the DWN * * As a convenience, the Record instance returned will cache a copy of the data if the * data size, in bytes, is less than the DWN 'max data size allowed to be encoded' diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 7d4221c35..9c420aba6 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,8 +1,33 @@ +/** + * Making developing with Web5 components at least 5 times easier to work with. + * + * Web5 consists of the following components: + * - Decentralized Identifiers + * - Verifiable Credentials + * - DWeb Node personal datastores + * + * The SDK sets out to gather the most oft used functionality from all three of + * these pillar technologies to provide a simple library that is as close to + * effortless as possible. + * + * The SDK is currently still under active development, but having entered the + * Tech Preview phase there is now a drive to avoid unnecessary changes unless + * backwards compatibility is provided. Additional functionality will be added + * in the lead up to 1.0 final, and modifications will be made to address + * issues and community feedback. + * + * [Link to GitHub Repo](https://github.com/TBD54566975/web5-js) + * + * @packageDocumentation + */ + export * from './did-api.js'; export * from './dwn-api.js'; export * from './protocol.js'; export * from './record.js'; -export * as utils from './utils.js'; export * from './vc-api.js'; export * from './web5.js'; -export * from './tech-preview.js'; \ No newline at end of file +export * from './tech-preview.js'; + +import * as utils from './utils.js'; +export { utils }; \ No newline at end of file diff --git a/packages/api/src/protocol.ts b/packages/api/src/protocol.ts index c22181eb3..8d15f5c82 100644 --- a/packages/api/src/protocol.ts +++ b/packages/api/src/protocol.ts @@ -2,17 +2,38 @@ import type { Web5Agent } from '@web5/agent'; import type { ProtocolsConfigure } from '@tbd54566975/dwn-sdk-js'; // TODO: export ProtocolsConfigureMessage from dwn-sdk-js +/** + * The protocol configure message carries the protocol definition and is used + * to setup the protocol. + * + * @beta + */ export type ProtocolsConfigureMessage = ProtocolsConfigure['message']; + +/** + * Metadata of the protocol + * + * @beta + */ type ProtocolMetadata = { author: string; messageCid?: string; }; +/** + * The Protocol API abstraction class. It's used to represent and retrieve a protocol and + * also to install (send) protocols to other DIDs. + * + * @beta + */ export class Protocol { private _agent: Web5Agent; private _metadata: ProtocolMetadata; private _protocolsConfigureMessage: ProtocolsConfigureMessage; + /** + * The protocol definition: types, structure and publish status + */ get definition() { return this._protocolsConfigureMessage.descriptor.definition; } @@ -23,10 +44,18 @@ export class Protocol { this._protocolsConfigureMessage = protocolsConfigureMessage; } + /** + * Returns the protocol as a JSON object. + */ toJSON() { return this._protocolsConfigureMessage; } + /** + * Sends the protocol to a remote DWN by specifying their DID + * @param target - the DID to send the protocol to + * @returns the status of the send protocols request + */ async send(target: string) { const { reply } = await this._agent.sendDwnRequest({ author : this._metadata.author, diff --git a/packages/api/src/record.ts b/packages/api/src/record.ts index a3a72e8fa..2ef88634c 100644 --- a/packages/api/src/record.ts +++ b/packages/api/src/record.ts @@ -8,6 +8,11 @@ import { DataStream, DwnInterfaceName, DwnMethodName, Encoder } from '@tbd545669 import { dataToBlob } from './utils.js'; import type { RecordsDeleteResponse } from './dwn-api.js'; +/** + * Options that are passed to Record constructor. + * + * @beta + */ export type RecordOptions = RecordsWriteMessage & { author: string; target: string; @@ -15,6 +20,12 @@ export type RecordOptions = RecordsWriteMessage & { data?: Readable | ReadableStream; }; +/** + * Represents the record data model, without the auxiliary properties such as + * the `descriptor` and the `authorization` + * + * @beta + */ export type RecordModel = RecordsWriteDescriptor & Omit & { @@ -23,6 +34,11 @@ export type RecordModel = RecordsWriteDescriptor target: string; } +/** + * Options that are passed to update the record on the DWN + * + * @beta + */ export type RecordUpdateOptions = { data?: unknown; dataCid?: RecordsWriteDescriptor['dataCid']; @@ -33,19 +49,28 @@ export type RecordUpdateOptions = { } /** - * TODO: Document class. - * - * Note: The `messageTimestamp` of the most recent RecordsWrite message is - * logically equivalent to the date/time at which a Record was most - * recently modified. Since this Record class implementation is - * intended to simplify the developer experience of working with - * logical records (and not individual DWN messages) the - * `messageTimestamp` is mapped to `dateModified`. - */ + * Record wrapper class with convenience methods to send, update, + * and delete itself, aside from manipulating and reading the record data. + * + * Note: The `messageTimestamp` of the most recent RecordsWrite message is + * logically equivalent to the date/time at which a Record was most + * recently modified. Since this Record class implementation is + * intended to simplify the developer experience of working with + * logical records (and not individual DWN messages) the + * `messageTimestamp` is mapped to `dateModified`. + * + * @beta + */ export class Record implements RecordModel { // mutable properties + + /** Record's author */ author: string; + + /** Record's target (for sent records) */ target: string; + + /** Record deleted status */ isDeleted = false; private _agent: Web5Agent; @@ -58,26 +83,64 @@ export class Record implements RecordModel { private _recordId: string; // Immutable DWN Record properties. + + /** Record's signatures attestation */ get attestation(): RecordsWriteMessage['attestation'] { return this._attestation; } + + /** Record's context ID */ get contextId() { return this._contextId; } + + /** Record's data format */ get dataFormat() { return this._descriptor.dataFormat; } + + /** Record's creation date */ get dateCreated() { return this._descriptor.dateCreated; } + + /** Record's encryption */ get encryption(): RecordsWriteMessage['encryption'] { return this._encryption; } + + /** Record's ID */ get id() { return this._recordId; } + + /** Interface is always `Records` */ get interface() { return this._descriptor.interface; } + + /** Method is always `Write` */ get method() { return this._descriptor.method; } + + /** Record's parent ID */ get parentId() { return this._descriptor.parentId; } + + /** Record's protocol */ get protocol() { return this._descriptor.protocol; } + + /** Record's protocol path */ get protocolPath() { return this._descriptor.protocolPath; } + + /** Record's recipient */ get recipient() { return this._descriptor.recipient; } + + /** Record's schema */ get schema() { return this._descriptor.schema; } // Mutable DWN Record properties. + + /** Record's CID */ get dataCid() { return this._descriptor.dataCid; } + + /** Record's data size */ get dataSize() { return this._descriptor.dataSize; } + + /** Record's modified date */ get dateModified() { return this._descriptor.messageTimestamp; } + + /** Record's published date */ get datePublished() { return this._descriptor.datePublished; } + + /** Record's published status */ get messageTimestamp() { return this._descriptor.messageTimestamp; } + + /** Record's published status (true/false) */ get published() { return this._descriptor.published; } constructor(agent: Web5Agent, options: RecordOptions) { @@ -107,7 +170,11 @@ export class Record implements RecordModel { } /** - * TODO: Document method. + * Returns the data of the current record. + * If the record data is not available, it attempts to fetch the data from the DWN. + * @returns a data stream with convenience methods such as `blob()`, `json()`, `text()`, and `stream()`, similar to the fetch API response + * @throws `Error` if the record has already been deleted. + * */ get data() { if (this.isDeleted) throw new Error('Operation failed: Attempted to access `data` of a record that has already been deleted.'); @@ -172,7 +239,9 @@ export class Record implements RecordModel { } /** - * TODO: Document method. + * Delete the current record from the DWN. + * @returns the status of the delete request + * @throws `Error` if the record has already been deleted. */ async delete(): Promise { if (this.isDeleted) throw new Error('Operation failed: Attempted to call `delete()` on a record that has already been deleted.'); @@ -196,7 +265,11 @@ export class Record implements RecordModel { } /** - * TODO: Document method. + * Send the current record to a remote DWN by specifying their DID + * (vs waiting for the regular DWN sync) + * @param target - the DID to send the record to + * @returns the status of the send record request + * @throws `Error` if the record has already been deleted. */ async send(target: string): Promise { if (this.isDeleted) throw new Error('Operation failed: Attempted to call `send()` on a record that has already been deleted.'); @@ -213,9 +286,8 @@ export class Record implements RecordModel { } /** - * TODO: Document method. - * - * Called by `JSON.stringify(...)` automatically. + * Returns a JSON representation of the Record instance. + * It's called by `JSON.stringify(...)` automatically. */ toJSON(): RecordModel { return { @@ -243,8 +315,7 @@ export class Record implements RecordModel { } /** - * TODO: Document method. - * + * Convenience method to return the string representation of the Record instance. * Called automatically in string concatenation, String() type conversion, and template literals. */ toString() { @@ -263,7 +334,10 @@ export class Record implements RecordModel { } /** - * TODO: Document method. + * Update the current record on the DWN. + * @param options - options to update the record, including the new data + * @returns the status of the update request + * @throws `Error` if the record has already been deleted. */ async update(options: RecordUpdateOptions = {}) { if (this.isDeleted) throw new Error('Operation failed: Attempted to call `update()` on a record that has already been deleted.'); diff --git a/packages/api/src/tech-preview.ts b/packages/api/src/tech-preview.ts index 071636f3d..227c4a9f0 100644 --- a/packages/api/src/tech-preview.ts +++ b/packages/api/src/tech-preview.ts @@ -3,6 +3,8 @@ import * as didUtils from '@web5/dids/utils'; /** * Dynamically selects up to 2 DWN endpoints that are provided * by default during the Tech Preview period. + * + * @beta */ export async function getTechPreviewDwnEndpoints(): Promise { let response: Response; diff --git a/packages/api/src/utils.ts b/packages/api/src/utils.ts index 18048903f..bb8c3d8e5 100644 --- a/packages/api/src/utils.ts +++ b/packages/api/src/utils.ts @@ -2,6 +2,8 @@ import { Convert, universalTypeOf } from '@web5/common'; /** * Set/detect the media type and return the data as bytes. + * + * @beta */ export const dataToBlob = (data: any, dataFormat?: string) => { let dataBlob: Blob; diff --git a/packages/api/src/vc-api.ts b/packages/api/src/vc-api.ts index 7dd6620e1..ccf346be2 100644 --- a/packages/api/src/vc-api.ts +++ b/packages/api/src/vc-api.ts @@ -1,5 +1,10 @@ import type { Web5Agent } from '@web5/agent'; +/** + * The VC API is used to issue, present and verify VCs + * + * @beta + */ export class VcApi { private agent: Web5Agent; private connectedDid: string; @@ -9,6 +14,9 @@ export class VcApi { this.connectedDid = options.connectedDid; } + /** + * Issues a VC (Not implemented yet) + */ async create() { // TODO: implement throw new Error('Not implemented.'); diff --git a/packages/api/src/web5.ts b/packages/api/src/web5.ts index ee32880bc..1431e0d4a 100644 --- a/packages/api/src/web5.ts +++ b/packages/api/src/web5.ts @@ -11,6 +11,8 @@ import { DidIonMethod } from '@web5/dids'; /** * Override defaults configured during the technical preview phase. + * + * @beta */ export type TechPreviewOptions = { // Override default dwnEndpoints provided for technical preview. @@ -19,16 +21,22 @@ export type TechPreviewOptions = { /** * Optional overrides that can be provided when calling {@link Web5.connect}. + * + * @beta */ export type Web5ConnectOptions = { - /** Provide a {@link Web5Agent} implementation. Defaults to creating a local - * {@link Web5UserAgent} if one isn't provided */ + /** + * Provide a {@link @web5/agent#Web5Agent} implementation. Defaults to creating a local + * {@link @web5/user-agent#Web5UserAgent} if one isn't provided + **/ agent?: Web5Agent; - /** Provide an instance of a {@link AppDataStore} implementation. Defaults to + /** + * Provide an instance of a {@link @web5/agent#AppDataStore} implementation. Defaults to * a LevelDB-backed store with an insecure, static unlock passphrase if one * isn't provided. To allow the app user to enter a secure passphrase of - * their choosing, provide an initialized {@link AppDataStore} instance. */ + * their choosing, provide an initialized {@link @web5/agent#AppDataStore} instance. + **/ appData?: AppDataStore; // Specify an existing DID to connect to. @@ -45,18 +53,39 @@ export type Web5ConnectOptions = { } /** + * Options that are passed to Web5 constructor. + * * @see {@link Web5ConnectOptions} + * @beta */ type Web5Options = { agent: Web5Agent; connectedDid: string; }; +/** + * The main Web5 API interface. It manages the creation of a DID if needed, the + * connection to the local DWN and all the web5 main foundational APIs such as VC, + * syncing, etc. + * + * @beta + */ export class Web5 { + /** + * Web5 Agent knows how to handle DIDs, DWNs and VCs requests. The agent manages the + * user keys and identities, and is responsible to sign and verify messages. + */ agent: Web5Agent; + + /** Exposed instance to the DID APIs, allow users to create and resolve DIDs */ did: DidApi; + + /** Exposed instance to the DWN APIs, allow users to read/write records */ dwn: DwnApi; + + /** Exposed instance to the VC APIs, allow users to issue, present and verify VCs */ vc: VcApi; + private connectedDid: string; constructor(options: Web5Options) { @@ -69,7 +98,7 @@ export class Web5 { } /** - * Connects to a {@link Web5Agent}. Defaults to creating a local {@link Web5UserAgent} + * Connects to a {@link @web5/agent#Web5Agent}. Defaults to creating a local {@link @web5/user-agent#Web5UserAgent} * if one isn't provided. * * @param options - optional overrides