Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
if: ${{needs.release.outputs.releases_created}}
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC
id-token: write # Required for OIDC
steps:
# The logic below handles the npm publication:
- name: Checkout
Expand All @@ -56,4 +56,3 @@ jobs:

- name: Publish
run: pnpm -r publish --access=public

15 changes: 8 additions & 7 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ const result = await invocation.execute(connection)
```

> 📝 **Tested in**:

> - [`packages/client/test/client.spec.js:70`](./packages/client/test/client.spec.js#L70) - Delegation creation and usage
> - [`packages/server/test/readme-integration.spec.js:160`](./packages/server/test/readme-integration.spec.js#L160) - Delegation with server validation
> - [`packages/server/test/server-readme-integration.spec.js:160`](./packages/server/test/server-readme-integration.spec.js#L160) - Delegation with server validation

### Batch Operations

Expand Down Expand Up @@ -205,7 +206,7 @@ This demonstrates how UCAN's delegation system provides fine-grained access cont
- ❌ **Mallory fails** - Bob doesn't have permission for Mallory's namespace
- 🔒 **Security** - The service validates the delegation chain and resource ownership

> 📝 **Tested in**: [`packages/server/test/readme-integration.spec.js:99`](./packages/server/test/readme-integration.spec.js#L99) - Advanced delegation patterns with namespace validation
> 📝 **Tested in**: [`packages/server/test/server-readme-integration.spec.js:99`](./packages/server/test/server-readme-integration.spec.js#L99) - Advanced delegation patterns with namespace validation

## Service-Specific Examples

Expand Down Expand Up @@ -246,24 +247,24 @@ const connection = Client.connect({
import { ed25519 } from '@ucanto/principal'

// Generate new keys
const agent = await ed25519.generate()
const agent = await ed25519.generate({ extractable: true })

// Save keys (browser)
localStorage.setItem('agent', agent.toString())
localStorage.setItem('agent', ed25519.format(agent))

// Load keys (browser)
const savedAgent = ed25519.parse(localStorage.getItem('agent'))

// Save keys (Node.js)
import fs from 'fs/promises'
await fs.writeFile('agent.key', agent.toString())
await fs.writeFile('agent.key', ed25519.format(agent))

// Load keys (Node.js)
const keyData = await fs.readFile('agent.key', 'utf-8')
const loadedAgent = ed25519.parse(keyData)
```

> 📝 **Tested in**: [`packages/server/test/readme-examples.spec.js:54`](./packages/server/test/readme-examples.spec.js#L54) - Key generation, formatting, and parsing
> 📝 **Tested in**: [`packages/server/test/server-readme-snippets.spec.js:54`](./packages/server/test/server-readme-snippets.spec.js#L54) - Key generation, formatting, and parsing

## Package Overview

Expand All @@ -283,4 +284,4 @@ const loadedAgent = ed25519.parse(keyData)
[car]: https://ipld.io/specs/transport/car/carv1/
[dag-cbor]: https://ipld.io/specs/codecs/dag-cbor/
[cid]: https://docs.ipfs.io/concepts/content-addressing/
[did:key]: https://w3c-ccg.github.io/did-method-key/
[did:key]: https://w3c-ccg.github.io/did-method-key/
9 changes: 7 additions & 2 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const [receipt] = await connection.execute(invocation)
console.log(receipt.out.error ? 'Failed:' : 'Success:', receipt.out)
```

> 📝 **Tested in**: [`client-readme-snippets.spec.js`](./test/client-readme-snippets.spec.js)

### Using Server as Channel (for Testing)

For testing or local development, you can use a UCAN server directly as a channel without HTTP. See the [`@ucanto/server` README](../server/README.md) for details on setting up a server.
Expand All @@ -85,7 +87,8 @@ Create a file called `generate-keys.js`:
import { ed25519 } from '@ucanto/principal'

async function generateKeys() {
const keypair = await ed25519.generate()
// Use extractable keys if you need to serialize for env vars.
const keypair = await ed25519.generate({ extractable: true })

const privateKey = ed25519.format(keypair)

Expand All @@ -95,6 +98,8 @@ async function generateKeys() {
generateKeys().catch(console.error)
```

> 📝 **Tested in**: [`client-readme-snippets.spec.js`](./test/client-readme-snippets.spec.js)

Then run it:

```bash
Expand Down Expand Up @@ -123,4 +128,4 @@ SERVICE_DID="did:key:service_provider_did_here" \
```


For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto).
For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto).
73 changes: 73 additions & 0 deletions packages/client/test/client-readme-snippets.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { test, assert } from './test.js'
import * as Client from '../src/lib.js'
import * as HTTP from '@ucanto/transport/http'
import { CAR } from '@ucanto/transport'
import { ed25519 } from '@ucanto/principal'
import { DID, Message, Receipt } from '@ucanto/core'

test('README connection and invocation example works', async () => {
const serviceSigner = ed25519.parse(
'MgCYKXoHVy7Vk4/QjcEGi+MCqjntUiasxXJ8uJKY0qh11e+0Bs8WsdqGK7xothgrDzzWD0ME7ynPjz2okXDh8537lId8='
)
const service = DID.parse(serviceSigner.did())
const issuer = ed25519.parse(
'MgCZT5vOnYZoVAeyjnzuJIVY9J4LNtJ+f8Js0cTPuKUpFne0BVEDJjEu6quFIU8yp91/TY/+MYK8GvlKoTDnqOCovCVM='
)

const channel = HTTP.open({
url: new URL('about:blank'),
fetch: async (url, init) => {
assert.equal(url, 'about:blank')
const request = await CAR.request.decode({
headers: /** @type {Record<string, string>} */ (init.headers),
body: /** @type {Uint8Array} */ (init.body),
})
const [invocation] = request.invocations

const receipt = await Receipt.issue({
issuer: serviceSigner,
ran: invocation.link(),
result: { ok: { accepted: true } },
})
const response = await CAR.response.encode(
await Message.build({ receipts: [receipt] })
)

return {
ok: true,
arrayBuffer: () => response.body.buffer,
headers: new Map([['content-type', CAR.contentType]]),
}
},
})

const connection = Client.connect({
id: service,
channel,
codec: CAR.outbound,
})

const invocation = Client.invoke({
issuer,
audience: service,
capability: {
can: 'store/add',
with: issuer.did(),
nb: {
link: 'bafybeigwflfnv7tjgpuy52ep45cbbgkkb2makd3bwhbj3ueabvt3eq43ca',
},
},
})

const [receipt] = await connection.execute(invocation)
assert.ok(!receipt.out.error, `Expected no error, got: ${receipt.out.error}`)
assert.deepEqual(receipt.out.ok, { accepted: true })
})

test('README AGENT_PRIVATE_KEY generation snippet works', async () => {
const keypair = await ed25519.generate({ extractable: true })
const privateKey = ed25519.format(keypair)
const parsed = ed25519.parse(privateKey)

assert.equal(parsed.did(), keypair.did())
})
6 changes: 3 additions & 3 deletions packages/client/test/services/util.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const ok =
/** @type {<T, Args extends []|[T]>(...args:Args) => Args extends [T] ? {ok:true, value:T extends undefined ? null : T} : {ok:true, value:null}}} */ (
(value) => (value == undefined ? Ok : { ok: true, value })
value => (value == undefined ? Ok : { ok: true, value })
)

const Ok = { ok: true, value: null }
Expand All @@ -9,13 +9,13 @@ const Ok = { ok: true, value: null }
* @param {T} value
* @returns {T}
*/
export const the = (value) => value
export const the = value => value

/**
* @param {string} reason
* @returns {never}
*/
export const panic = (reason) => {
export const panic = reason => {
throw new Error(reason)
}

Expand Down
24 changes: 16 additions & 8 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,21 @@ npm install @ucanto/core

## Example Usage
```ts
import { capability, URI, Link } from '@ucanto/core';

const AddFile = capability({
can: 'file/add',
with: URI.match({ protocol: 'file:' }),
nb: { link: Link }
});
import { Schema, parseLink } from '@ucanto/core';

const AddFile = Schema.struct({
with: Schema.uri({ protocol: 'file:' }),
nb: Schema.struct({
link: Schema.link(),
}),
})

const parsed = AddFile.read({
with: 'file:///tmp/example.txt',
nb: { link: parseLink('bafkqaaa') },
})
```

For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto).
> 📝 **Tested in**: [`core-readme-snippets.spec.js`](./test/core-readme-snippets.spec.js)

For more details, see the [`ucanto` documentation](https://github.com/storacha/ucanto).
2 changes: 1 addition & 1 deletion packages/core/src/schema/did.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class DIDBytesSchema extends Schema.API {
return Schema.error(`Expected a ${prefix} but got "${did}" instead`)
} else {
return { ok: /** @type {API.DID<Method>} */ (did) }
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/cbor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test('encode / decode', async () => {
const o = {}
const data = {
a: o,
b: o
b: o,
}

assert.doesNotThrow(() => transcode(data))
Expand Down
18 changes: 18 additions & 0 deletions packages/core/test/core-readme-snippets.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { test, assert } from './test.js'
import { Schema, parseLink } from '../src/lib.js'

test('README schema example works', async () => {
const AddFile = Schema.struct({
with: Schema.uri({ protocol: 'file:' }),
nb: Schema.struct({
link: Schema.link(),
}),
})

const result = AddFile.read({
with: 'file:///tmp/example.txt',
nb: { link: parseLink('bafkqaaa') },
})

assert.ok(!result.error, `Expected no error, got: ${result.error?.message}`)
})
12 changes: 6 additions & 6 deletions packages/core/test/delegation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,8 @@ test('delegation.attach block in capabiliy', async () => {
can: 'store/add',
with: alice.did(),
nb: {
inlineBlock: block.cid.link()
}
inlineBlock: block.cid.link(),
},
},
],
})
Expand Down Expand Up @@ -467,8 +467,8 @@ test('delegation.attach block in facts', async () => {
facts: [
{ [`${block.cid.link()}`]: block.cid.link() },
// @ts-expect-error Link has fact entry
block.cid.link()
]
block.cid.link(),
],
})

ucan.attach(block)
Expand All @@ -488,11 +488,11 @@ test('delegation.attach fails to attach block with not attached link', async ()
capabilities: [
{
can: 'store/add',
with: alice.did()
with: alice.did(),
},
],
})

const block = await getBlock({ test: 'inlineBlock' })
assert.throws(() => ucan.attach(block))
})
})
Loading