Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Improve Commenting #45

Merged
merged 8 commits into from
Dec 19, 2024
72 changes: 64 additions & 8 deletions packages/events/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ import type { CID } from 'multiformats/cid'
import { SignedEvent } from './codecs.js'
import { getSignedEventPayload } from './signing.js'

/** Container for a signed Ceramic event */
/**
* A container for a signed Ceramic event.
*
* @typeParam Payload - The type of the event's payload.
*
* @property signed - Indicates that the event is signed (`true`).
* @property cid - The CID of the linked block for the event.
* @property payload - The decoded payload of the event.
* @property verified - The result of verifying the event's JWS.
* @property cacaoBlock - (Optional) A block containing a Cacao (Capability Object).
*/
export type SignedEventContainer<Payload> = {
signed: true
cid: CID
Expand All @@ -14,26 +24,60 @@ export type SignedEventContainer<Payload> = {
cacaoBlock?: Uint8Array
}

/** Container for an unsigned Ceramic event */
/**
* A container for an unsigned Ceramic event.
*
* @typeParam Payload - The type of the event's payload.
*
* @property signed - Indicates that the event is unsigned (`false`).
* @property payload - The decoded payload of the event.
*/
export type UnsignedEventContainer<Payload> = {
signed: false
payload: Payload
}

/** Container for a Ceramic event, providing additional metadata about the event */
/**
* A container for a Ceramic event, which can be either signed or unsigned.
*
* @typeParam Payload - The type of the event's payload.
*/
export type EventContainer<Payload> =
| SignedEventContainer<Payload>
| UnsignedEventContainer<Payload>

/** Decode an unsigned Ceramic event into a container using the provided payload decoder */
/**
* Decodes an unsigned Ceramic event into a container.
*
* @param codec - The codec used to decode the event's payload.
* @param event - The unsigned Ceramic event to decode.
* @returns An `UnsignedEventContainer` containing the decoded payload.
*
* @remarks
* - This function assumes that the event is unsigned and decodes it accordingly.
* - Use `eventToContainer` if the event type (signed or unsigned) is unknown.
*/
export function unsignedEventToContainer<Payload>(
codec: Decoder<unknown, Payload>,
event: unknown,
): UnsignedEventContainer<Payload> {
return { signed: false, payload: decode(codec, event) }
}

/** Decode a signed Ceramic event into a container using the provided verifier DID and payload decoder */
/**
* Decodes a signed Ceramic event into a container.
*
* @param did - The DID used to verify the event's JWS.
* @param codec - The codec used to decode the event's payload.
* @param event - The signed Ceramic event to decode.
* @returns A promise that resolves to a `SignedEventContainer` containing the decoded payload and metadata.
*
* @throws Will throw an error if the linked block CID is missing or if verification fails.
*
* @remarks
* - This function verifies the event's JWS and decodes its payload simultaneously.
* - It also includes additional metadata such as the verification result and `cacaoBlock` if present.
*/
export async function signedEventToContainer<Payload>(
did: DID,
codec: Decoder<unknown, Payload>,
Expand All @@ -44,13 +88,25 @@ export async function signedEventToContainer<Payload>(
throw new Error('Missing linked block CID')
}
const [verified, payload] = await Promise.all([
did.verifyJWS(event.jws),
getSignedEventPayload(codec, event),
did.verifyJWS(event.jws), // Verify the JWS signature.
getSignedEventPayload(codec, event), // Decode the payload.
])
return { signed: true, cid, verified, payload, cacaoBlock: event.cacaoBlock }
}

/** Decode a Ceramic event into a container using the provided verifier DID and payload decoder */
/**
* Decodes a Ceramic event (signed or unsigned) into a container.
*
* @param did - The DID used to verify the event's JWS if it is signed.
* @param codec - The codec used to decode the event's payload.
* @param event - The Ceramic event to decode (can be signed or unsigned).
* @returns A promise that resolves to an `EventContainer` containing the decoded payload and metadata.
*
* @remarks
* - This function determines the type of the event (signed or unsigned) and processes it accordingly.
* - For signed events, it verifies the JWS and decodes the payload.
* - For unsigned events, it simply decodes the payload.
*/
export async function eventToContainer<Payload>(
did: DID,
codec: Decoder<unknown, Payload>,
Expand Down
85 changes: 76 additions & 9 deletions packages/events/src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { sha256 } from 'multihashes-sync/sha2'
import { SignedEvent } from './codecs.js'
import { base64urlToJSON, restrictBlockSize } from './utils.js'

// Initialize a CAR factory with support for DAG-JOSE and DAG-JSON codecs.
const carFactory = new CARFactory()
carFactory.codecs.add(dagJose)
carFactory.codecs.add(dagJson)
Expand All @@ -18,15 +19,31 @@ carFactory.hashers.add(sha256)
/** @internal */
export type Base = keyof typeof bases

/** @internal */
/** Default base encoding for CAR files (Base64) */
export const DEFAULT_BASE: Base = 'base64'

/** Encode a CAR into a string, using the given base (defaults to base64) */
/**
* Encodes a CAR into a string using the specified base encoding.
*
* @param car - The CAR to encode.
* @param base - The base encoding to use (defaults to Base64).
* @returns The string-encoded CAR.
*
* @throws Will throw an error if the base encoding is not supported.
*/
export function carToString(car: CAR, base: Base = DEFAULT_BASE): string {
return car.toString(base)
}

/** Decode a CAR from a string, using the given base (defaults to base64) */
/**
* Decodes a CAR from a string using the specified base encoding.
*
* @param value - The string-encoded CAR.
* @param base - The base encoding used to decode the CAR (defaults to Base64).
* @returns The decoded CAR object.
*
* @throws Will throw an error if the base encoding is not supported.
*/
export function carFromString(value: string, base: Base = DEFAULT_BASE): CAR {
const codec = bases[base]
if (codec == null) {
Expand All @@ -35,7 +52,16 @@ export function carFromString(value: string, base: Base = DEFAULT_BASE): CAR {
return carFactory.fromBytes(codec.decode(value))
}

/** Encode a signed event into a CAR */
/**
* Encodes a signed event into a CAR format.
*
* @param event - The signed event to encode.
* @returns A CAR object representing the signed event.
*
* @remarks
* - Encodes the JWS, linked block, and optional `cacaoBlock` into the CAR.
* - Validates block sizes using `restrictBlockSize` to ensure consistency.
*/
export function signedEventToCAR(event: SignedEvent): CAR {
const { jws, linkedBlock, cacaoBlock } = event
const car = carFactory.build()
Expand Down Expand Up @@ -68,7 +94,16 @@ export function signedEventToCAR(event: SignedEvent): CAR {
return car
}

/** Encode an unsigned event into a CAR using the provided codec */
/**
* Encodes an unsigned event into a CAR using the provided codec.
*
* @param codec - The codec used to encode the event.
* @param event - The unsigned event to encode.
* @returns A CAR object representing the unsigned event.
*
* @remarks
* Encodes the event as the root of the CAR file using the specified codec.
*/
export function encodeEventToCAR(codec: Codec<unknown>, event: unknown): CAR {
const car = carFactory.build()
const cid = car.put(codec.encode(event), { isRoot: true })
Expand All @@ -78,14 +113,30 @@ export function encodeEventToCAR(codec: Codec<unknown>, event: unknown): CAR {
return car
}

/** Encode an event into a CAR using the provided codec for unsigned events */
/**
* Encodes an event into a CAR. Supports both signed and unsigned events.
*
* @param codec - The codec used for unsigned events.
* @param event - The event to encode (signed or unsigned).
* @returns A CAR object representing the event.
*
* @remarks
* Uses `signedEventToCAR` for signed events and `encodeEventToCAR` for unsigned events.
*/
export function eventToCAR(codec: Codec<unknown>, event: unknown): CAR {
return SignedEvent.is(event)
? signedEventToCAR(event)
: encodeEventToCAR(codec, event)
}

/** Encode an event into a string using the provided codec for unsigned events and the given base (defaults to base64) */
/**
* Encodes an event into a string using the specified codec and base encoding.
*
* @param codec - The codec used for unsigned events.
* @param event - The event to encode (signed or unsigned).
* @param base - The base encoding to use (defaults to Base64).
* @returns The string-encoded CAR representing the event.
*/
export function eventToString(
codec: Codec<unknown>,
event: unknown,
Expand All @@ -94,7 +145,16 @@ export function eventToString(
return carToString(eventToCAR(codec, event), base)
}

/** Decode an event from a string using the provided codec for unsigned events */
/**
* Decodes an event from a CAR object using the specified decoder.
*
* @param decoder - The decoder to use for unsigned events.
* @param car - The CAR object containing the event.
* @param eventCID - (Optional) The CID of the event to decode.
* @returns The decoded event, either a `SignedEvent` or a custom payload.
*
* @throws Will throw an error if the linked block is missing or decoding fails.
*/
export function eventFromCAR<Payload = unknown>(
decoder: Decoder<unknown, Payload>,
car: CAR,
Expand Down Expand Up @@ -124,7 +184,14 @@ export function eventFromCAR<Payload = unknown>(
return decode(decoder, root)
}

/** Decode an event from a string using the provided codec for unsigned events and the given base (defaults to base64) */
/**
* Decodes an event from a string using the specified decoder and base encoding.
*
* @param decoder - The decoder to use for unsigned events.
* @param value - The string-encoded CAR containing the event.
* @param base - The base encoding used (defaults to Base64).
* @returns The decoded event, either a `SignedEvent` or a custom payload.
*/
export function eventFromString<Payload = unknown>(
decoder: Decoder<unknown, Payload>,
value: string,
Expand Down
55 changes: 51 additions & 4 deletions packages/events/src/signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@ import {
type SignedEvent,
} from './codecs.js'

/** Sign an event payload using the provided DID */
/**
* Signs an event payload using the provided DID.
*
* @param did - The DID instance used to sign the payload.
* @param payload - The data to be signed, provided as a key-value map.
* @param options - (Optional) Additional options for creating the JWS.
* @returns A promise that resolves to a `SignedEvent` containing the JWS and linked block.
*
* @throws Will throw an error if the DID is not authenticated.
*
* @remarks
* This function uses the DID's `createDagJWS` method to sign the payload and
* returns the signed event, including the linked block as a `Uint8Array`.
*/
export async function signEvent(
did: DID,
payload: Record<string, unknown>,
Expand All @@ -24,12 +37,33 @@ export async function signEvent(
return { ...rest, linkedBlock: new Uint8Array(linkedBlock) }
}

// Make controllers optional in the event header as createSignedEvent() will inject it as needed
/**
* A partial version of the initialization event header, with optional controllers.
*
* @remarks
* This type is used to allow the creation of initialization events without requiring
* the `controllers` field, as it will be injected automatically during the signing process.
*/
export type PartialInitEventHeader = Omit<InitEventHeader, 'controllers'> & {
controllers?: [DIDString]
}

/** Create a signed init event using the provided DID, data and header */
/**
* Creates a signed initialization event using the provided DID, data, and header.
*
* @param did - The DID instance used to sign the initialization event.
* @param data - The initialization data to be included in the event.
* @param header - The header for the initialization event, with optional controllers.
* @param options - (Optional) Additional options for creating the JWS.
* @returns A promise that resolves to a `SignedEvent` representing the initialization event.
*
* @throws Will throw an error if the DID is not authenticated.
*
* @remarks
* - If `controllers` are not provided in the header, they will be automatically set
* based on the DID's parent (if available) or the DID itself.
* - The payload is encoded as an `InitEventPayload` before signing.
*/
export async function createSignedInitEvent<T>(
did: DID,
data: T,
Expand All @@ -49,7 +83,20 @@ export async function createSignedInitEvent<T>(
return await signEvent(did, payload, options)
}

/** Decode the payload of a signed event using the provided decoder */
/**
* Decodes the payload of a signed event using the provided decoder.
*
* @param decoder - The decoder used to interpret the event's payload.
* @param event - The signed event containing the payload to decode.
* @returns A promise that resolves to the decoded payload.
*
* @throws Will throw an error if the linked block CID is missing or if decoding fails.
*
* @remarks
* - The function reconstructs the block from the event's linked block and CID,
* using the DAG-CBOR codec and SHA-256 hasher.
* - The payload is decoded using the provided decoder to ensure its validity and type safety.
*/
export async function getSignedEventPayload<Payload = Record<string, unknown>>(
decoder: Decoder<unknown, Payload>,
event: SignedEvent,
Expand Down
Loading
Loading