Skip to content

Commit

Permalink
feat: implement .delegate on capabilities (#110)
Browse files Browse the repository at this point in the history
* fix: optional caveats

backport #105 into 0.9

* stash: current status of schema code

* chore: increase coverage

* fix: get 100 coverage

* chore: optimize typings

* chore: rename decoder to schema

* feat: update decoders to new API

* feat: increase coverage to 100%

* fix: regressions in other packages

* fix: bundler issue

* chore: rip out my: and as: support

* feat: implement capability.delegate

* chore: add tests for .delegate
  • Loading branch information
Gozala authored Oct 16, 2022
1 parent 3ec8e64 commit fd0bb9d
Show file tree
Hide file tree
Showing 4 changed files with 385 additions and 2 deletions.
23 changes: 23 additions & 0 deletions packages/interface/src/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,23 @@ export interface TheCapabilityParser<M extends Match<ParsedCapability>>
input: InferCreateOptions<M['value']['with'], M['value']['nb']>
): M['value']

/**
* Creates an invocation of this capability. Function throws exception if
* non-optional fields are omitted.
*/

invoke(
options: InferInvokeOptions<M['value']['with'], M['value']['nb']>
): IssuedInvocationView<M['value']>

/**
* Creates a delegation of this capability. Please note that all the
* `nb` fields are optional in delegation and only provided ones will
* be validated.
*/
delegate(
options: InferDelegationOptions<M['value']['with'], M['value']['nb']>
): Promise<Delegation<[M['value']]>>
}

export type InferCreateOptions<R extends Resource, C extends {} | undefined> =
Expand All @@ -152,6 +166,15 @@ export type InferInvokeOptions<
C extends {} | undefined
> = UCANOptions & { issuer: Signer } & InferCreateOptions<R, C>

export type InferDelegationOptions<
R extends Resource,
C extends {} | undefined
> = UCANOptions & {
issuer: Signer
with: R
nb?: Partial<InferCreateOptions<R, C>['nb']>
}

export type EmptyObject = { [key: string | number | symbol]: never }
type Optionalize<T> = InferRequried<T> & InferOptional<T>

Expand Down
53 changes: 52 additions & 1 deletion packages/validator/src/capability.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
DelegationError as MatchError,
Failure,
} from './error.js'
import { invoke } from '@ucanto/core'
import { invoke, delegate } from '@ucanto/core'

/**
* @template {API.Ability} A
Expand Down Expand Up @@ -167,6 +167,51 @@ class Capability extends Unit {
})
}

/**
* @param {API.InferDelegationOptions<R, API.InferCaveats<C>>} options
*/
async delegate({ with: with_, nb, ...options }) {
const { descriptor, can } = this
const readers = descriptor.nb
const data = /** @type {API.InferCaveats<C>} */ (nb || {})

const resource = descriptor.with.read(with_)
if (resource.error) {
throw Object.assign(new Error(`Invalid 'with' - ${resource.message}`), {
cause: resource,
})
}

const capabality =
/** @type {API.ParsedCapability<A, R, API.InferCaveats<C>>} */
({ can, with: resource })

for (const [name, reader] of Object.entries(readers || {})) {
const key = /** @type {keyof data & string} */ (name)
const source = data[key]
// omit undefined fields in the delegation
const value = source === undefined ? source : reader.read(data[key])
if (value?.error) {
throw Object.assign(
new Error(`Invalid 'nb.${key}' - ${value.message}`),
{ cause: value }
)
} else if (value !== undefined) {
const nb =
capabality.nb ||
(capabality.nb = /** @type {API.InferCaveats<C>} */ ({}))

const key = /** @type {keyof nb} */ (name)
nb[key] = /** @type {typeof nb[key]} */ (value)
}
}

return await delegate({
capabilities: [capabality],
...options,
})
}

get can() {
return this.descriptor.can
}
Expand Down Expand Up @@ -311,6 +356,12 @@ class Derive extends Unit {
invoke(options) {
return this.to.invoke(options)
}
/**
* @type {typeof this.to['delegate']}
*/
delegate(options) {
return this.to.delegate(options)
}
get can() {
return this.to.can
}
Expand Down
Loading

0 comments on commit fd0bb9d

Please sign in to comment.