-
Notifications
You must be signed in to change notification settings - Fork 13
docs: Fix outdated READMEs for @ucanto/client and @ucanto/transport #388
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
base: main
Are you sure you want to change the base?
Changes from all commits
038b3c4
6fd66a3
d80540e
4a0a38d
2a89648
7abf3c3
5dc05d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,11 +4,12 @@ | |
|
|
||
| ## What It Provides | ||
| - **UCAN Invocation Handling**: Creates and signs capability invocations. | ||
| - **Connection Management**: Manages communication with UCAN services. | ||
| - **Batch Invocation Support**: Enables multiple invocations in a single request. | ||
| - **Secure Communication**: Ensures interactions are cryptographically signed and verified. | ||
| - **Delegation Support**: Creates and uses authorization delegations. | ||
|
|
||
| ## How It Fits with Other Modules | ||
| - [`@ucanto/core`](../core/README.md): Defines capability structures and execution logic. | ||
| - [`@ucanto/core`](../core/README.md): Provides capability invocation and delegation logic. | ||
| - [`@ucanto/server`](../server/README.md): Processes invocations received from the client. | ||
| - [`@ucanto/interface`](../interface/README.md): Provides shared types for request and response handling. | ||
| - [`@ucanto/principal`](../principal/README.md): Manages cryptographic signing for invocations. | ||
|
|
@@ -22,28 +23,114 @@ npm install @ucanto/client | |
| ``` | ||
|
|
||
| ## Example Usage | ||
| ```ts | ||
| import * as Client from '@ucanto/client'; | ||
| import { ed25519 } from '@ucanto/principal'; | ||
| ```js | ||
| import * as Client from '@ucanto/client' | ||
| import * as HTTP from '@ucanto/transport/http' | ||
| import { CAR } from '@ucanto/transport' | ||
| import { ed25519 } from '@ucanto/principal' | ||
| import { Receipt, Message } from '@ucanto/core' | ||
|
|
||
| const service = ed25519.Verifier.parse(process.env.SERVICE_ID); | ||
| const issuer = ed25519.Signer.parse(process.env.CLIENT_KEYPAIR); | ||
| // Parse the service DID (public key) | ||
| // SERVICE_DID should be a DID like: did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi | ||
| const service = ed25519.Verifier.parse(process.env.SERVICE_DID) | ||
| // Parse the agent's private key | ||
| // AGENT_PRIVATE_KEY should be a base64 private key like: Mg.. | ||
| const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY) | ||
|
|
||
| const invocation = await Client.invoke({ | ||
| // Mock fetch that simulates a UCAN service | ||
| const mockFetch = async (url, input) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if it's a good idea for a mock to be in the README, perhaps in a runnable code example in the repo. IMHO the README should shown how to use the library against an already running server - i.e. just the code you'd need to use the client. I'd link to the server README for actually setting up and running a server component. |
||
| const { invocations } = await CAR.request.decode(input) | ||
|
|
||
| const receipts = await Promise.all( | ||
| invocations.map(inv => Receipt.issue({ | ||
| ran: inv.cid, // Link to the invocation | ||
| issuer: service, // Service signs the receipt | ||
| result: { ok: { status: 'success' } } // Fake success | ||
| })) | ||
| ) | ||
|
|
||
| const message = await Message.build({ receipts }) | ||
| const response = await CAR.response.encode(message) | ||
|
|
||
| return new Response(response.body, { headers: response.headers }) | ||
| } | ||
|
|
||
| // Connect to mock service | ||
| const connection = Client.connect({ | ||
| id: service, | ||
| channel: HTTP.open({ url: new URL('https://api.example.com'), fetch: mockFetch }), | ||
| codec: CAR.outbound, | ||
| }) | ||
|
|
||
| // Create and execute invocation | ||
| const invocation = Client.invoke({ | ||
| issuer, | ||
| audience: service, | ||
| capability: { | ||
| can: 'file/read', | ||
| with: 'file://example.txt' | ||
| can: 'store/add', | ||
| with: issuer.did(), | ||
| nb: { link: 'bafybeigwflfnv7tjgpuy52ep45cbbgkkb2makd3bwhbj3ueabvt3eq43ca' } | ||
| } | ||
| }); | ||
| }) | ||
|
|
||
| const [receipt] = await connection.execute(invocation) | ||
| // A receipt is a signed result from the service proving the invocation was processed. | ||
| console.log(receipt.out.error ? 'Failed:' : 'Success:', receipt.out) | ||
| ``` | ||
|
|
||
| ## Setup Instructions | ||
|
|
||
| ### Environment Variables | ||
|
|
||
| **AGENT_PRIVATE_KEY** | ||
| Set the key your client should use to sign UCAN invocations. You can generate Ed25519 keys with the ucanto library. | ||
|
|
||
| #### Usage | ||
|
|
||
| Create a file called `generate-keys.js`: | ||
|
|
||
| ```javascript | ||
| import { ed25519 } from '@ucanto/principal' | ||
|
|
||
| const response = await client.execute(invocation); | ||
| if (response.error) { | ||
| console.error('Invocation failed:', response.error); | ||
| } else { | ||
| console.log('Invocation succeeded:', response.result); | ||
| async function generateKeys() { | ||
| const keypair = await ed25519.generate() | ||
|
|
||
| const privateKey = ed25519.format(keypair) | ||
|
|
||
| console.log('AGENT_PRIVATE_KEY=' + privateKey) | ||
| } | ||
|
|
||
| generateKeys().catch(console.error) | ||
| ``` | ||
|
|
||
| Then run it: | ||
|
|
||
| ```bash | ||
| node generate-keys.js | ||
| ``` | ||
|
|
||
| **SERVICE_DID** | ||
| Set the DID of the service you want to connect to. Check the service's documentation for their public DID. | ||
|
|
||
| **SERVICE_URL** (Optional) | ||
| If you're connecting to a custom service, set both `SERVICE_DID` and `SERVICE_URL` environment variables. | ||
|
|
||
|
|
||
| For example, Storacha has following `SERVICE_DID` and `SERVICE_URL`: | ||
|
|
||
| ```bash | ||
| # Storacha uses these default values: | ||
| SERVICE_DID="did:web:up.storacha.network" | ||
| SERVICE_URL="https://up.storacha.network" | ||
| ``` | ||
|
|
||
| Set your environment variables like so: | ||
| ```bash | ||
| AGENT_PRIVATE_KEY="your_generated_private_key_here" \ | ||
| SERVICE_DID="did:key:service_provider_did_here" \ | ||
| ``` | ||
|
|
||
|
|
||
| **What's happening:** UCAN services expect CAR-encoded requests and return CAR-encoded receipts with cryptographic signatures. The mock simulates this entire flow so the example works without a real service. | ||
|
|
||
| For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,18 @@ | ||
| # @ucanto/transport | ||
|
|
||
| `@ucanto/transport` provides encoding, decoding, and transport mechanisms for UCAN-based RPC. It ensures reliable communication between clients and servers using standardized serialization formats. | ||
| `@ucanto/transport` provides encoding, decoding, and transport mechanisms for UCAN-based RPC. It handles the serialization and network communication needed for secure UCAN message exchange between clients and servers. | ||
|
|
||
| ## What It Provides | ||
| - **Pluggable Transport Layer**: Supports multiple encoding formats like CAR and CBOR. | ||
| - **Standardized Encoding**: Ensures consistency in UCAN invocation serialization. | ||
| - **Extensible Communication**: Enables integration with various network protocols. | ||
| - **CAR Encoding/Decoding**: Serializes UCAN messages in Content Addressable Archive format. | ||
| - **HTTP Transport**: Enables UCAN communication over HTTP with proper content negotiation. | ||
| - **Pluggable Codec System**: Supports multiple encoding formats with inbound/outbound codecs. | ||
| - **Legacy Support**: Maintains compatibility with older CBOR-based UCAN message formats. | ||
|
|
||
| ## How It Fits with Other Modules | ||
| - [`@ucanto/core`](../core/README.md): Uses transport mechanisms for executing capabilities. | ||
| - [`@ucanto/server`](../server/README.md): Relies on transport modules for request handling. | ||
| - [`@ucanto/interface`](../interface/README.md): Defines standard transport-related types. | ||
| - [`@ucanto/principal`](../principal/README.md): Facilitates secure communication using identity-based encryption. | ||
| - [`@ucanto/client`](../client/README.md): Uses transport to communicate with services. | ||
| - [`@ucanto/server`](../server/README.md): Uses transport to receive and respond to requests. | ||
| - [`@ucanto/core`](../core/README.md): Provides the UCAN message structures that get transported. | ||
| - [`@ucanto/interface`](../interface/README.md): Defines transport-related types and interfaces. | ||
|
|
||
| For an overview and detailed usage information, refer to the [main `ucanto` README](../../Readme.md). | ||
|
|
||
|
|
@@ -21,12 +22,139 @@ npm install @ucanto/transport | |
| ``` | ||
|
|
||
| ## Example Usage | ||
| ```ts | ||
| import * as CAR from '@ucanto/transport/car'; | ||
| import * as CBOR from '@ucanto/transport/cbor'; | ||
| ```js | ||
| import * as HTTP from '@ucanto/transport/http' | ||
| import { CAR } from '@ucanto/transport' | ||
| import { ed25519 } from '@ucanto/principal' | ||
| import { invoke, Message, Receipt } from '@ucanto/core' | ||
|
|
||
| const encoded = CAR.encode({ invocations: [] }); | ||
| const decoded = CBOR.decode(encoded); | ||
| // Parse the service DID (public key) | ||
| // SERVICE_DID should be a DID like: did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi | ||
| const service = ed25519.Verifier.parse(process.env.SERVICE_DID) | ||
| // Parse the agent's private key | ||
| // AGENT_PRIVATE_KEY should be a base64 private key starting with: Mg.. | ||
| const issuer = ed25519.parse(process.env.AGENT_PRIVATE_KEY) | ||
|
|
||
| // Mock fetch that simulates a UCAN service | ||
| const mockFetch = async (url, init) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I'd save this for a runnable example in the repo, or better yet a runnable example with an actual server. Mocking the server like this is not great even for tests as it bypasses all delegation chain validation. Note: in tests, you don't even need a HTTP server running - a Ucanto server is a channel, so you can pass it to the client as the channel to use. |
||
| console.log('Sending request to:', url) | ||
| console.log('Request headers:', init.headers) | ||
|
|
||
| // Simulate a service response with a receipt | ||
| const { invocations } = await CAR.request.decode(init) | ||
| const receipts = await Promise.all( | ||
| invocations.map(inv => Receipt.issue({ | ||
| ran: inv.cid, | ||
| issuer: service, | ||
| result: { ok: { status: 'added' } } | ||
| })) | ||
| ) | ||
|
|
||
| const responseMessage = await Message.build({ receipts }) | ||
| const response = await CAR.response.encode(responseMessage) | ||
| return new Response(response.body, { headers: response.headers }) | ||
| } | ||
|
|
||
| // Create UCAN invocation | ||
| const invocation = invoke({ | ||
| issuer, | ||
| audience: service, | ||
| capability: { | ||
| can: 'store/add', | ||
| with: issuer.did(), | ||
| nb: { link: 'bafybeigwflfnv7tjgpuy52ep45cbbgkkb2makd3bwhbj3ueabvt3eq43ca' } | ||
| } | ||
| }) | ||
|
|
||
| // Package for transport | ||
| const message = await Message.build({ invocations: [invocation] }) | ||
| const request = await CAR.request.encode(message) | ||
|
|
||
| // Create HTTP channel and send | ||
| const channel = HTTP.open({ | ||
| url: new URL('https://api.example.com'), | ||
| fetch: mockFetch | ||
| }) | ||
| const response = await channel.request(request) | ||
|
|
||
| // Unpack response | ||
| const replyMessage = await CAR.response.decode(response) | ||
| console.log('Received:', replyMessage.receipts.size, 'receipts') | ||
| ``` | ||
|
|
||
| ## Setup Instructions | ||
|
|
||
| ### Environment Variables | ||
|
|
||
| **AGENT_PRIVATE_KEY** | ||
| Set the key your client should use to sign UCAN invocations. You can generate Ed25519 keys with the ucanto library. | ||
|
|
||
| #### Usage | ||
|
|
||
| Create a file called `generate-keys.js`: | ||
|
|
||
| ```javascript | ||
| import { ed25519 } from '@ucanto/principal' | ||
|
|
||
| async function generateKeys() { | ||
| const keypair = await ed25519.generate() | ||
|
|
||
| const privateKey = ed25519.format(keypair) | ||
|
|
||
| console.log('AGENT_PRIVATE_KEY=' + privateKey) | ||
| } | ||
|
|
||
| generateKeys().catch(console.error) | ||
| ``` | ||
|
|
||
| Then run it: | ||
|
|
||
| ```bash | ||
| node generate-keys.js | ||
| ``` | ||
|
|
||
| **SERVICE_DID** | ||
| Set the DID of the service you want to connect to. Check the service's documentation for their public DID. | ||
|
|
||
| **SERVICE_URL** (Optional) | ||
| If you're connecting to a custom service, set both `SERVICE_DID` and `SERVICE_URL` environment variables. | ||
|
|
||
|
|
||
| For example, Storacha has following `SERVICE_DID` and `SERVICE_URL`: | ||
|
|
||
| ```bash | ||
| # Storacha uses these default values: | ||
| SERVICE_DID="did:web:up.storacha.network" | ||
| SERVICE_URL="https://up.storacha.network" | ||
| ``` | ||
|
|
||
| Set your environment variables like so: | ||
| ```bash | ||
| AGENT_PRIVATE_KEY="your_generated_private_key_here" \ | ||
| SERVICE_DID="did:key:service_provider_did_here" \ | ||
| ``` | ||
|
|
||
|
|
||
| ### Advanced: Pluggable Codecs | ||
|
|
||
| Transport provides a codec system for different encoding strategies: | ||
|
|
||
| ```js | ||
| import { Codec, CAR } from '@ucanto/transport' | ||
|
|
||
| // Outbound codec (client-side) | ||
| const outbound = Codec.outbound({ | ||
| encoders: { 'application/vnd.ipld.car': CAR.request }, | ||
| decoders: { 'application/vnd.ipld.car': CAR.response }, | ||
| }) | ||
|
|
||
| // Inbound codec (server-side) | ||
| const inbound = Codec.inbound({ | ||
| decoders: { 'application/vnd.ipld.car': CAR.request }, | ||
| encoders: { 'application/vnd.ipld.car': CAR.response }, | ||
| }) | ||
| ``` | ||
|
|
||
| **What's happening:** Transport handles the low-level details of UCAN communication - encoding messages into CAR format, managing HTTP headers, content negotiation, and error handling. Most developers use `@ucanto/client` which handles this automatically. | ||
|
|
||
| For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto). | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.