Skip to content

Commit

Permalink
fix(server): loosen requirements on statics (#353)
Browse files Browse the repository at this point in the history
* fix(server): loosen requirements on statics

* chore: expose more error classes

* fix: export collisions
  • Loading branch information
Gozala authored May 10, 2024
1 parent 6c447dc commit 6de27c0
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 136 deletions.
21 changes: 11 additions & 10 deletions packages/interface/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,14 +988,21 @@ export interface ValidatorOptions {
validateAuthorization: (proofs: Authorization) => Await<Result<Unit, Revoked>>
}

export interface ServerOptions extends ValidatorOptions {
export interface ServerOptions<T> extends ValidatorOptions {
/**
* Service DID which will be used to verify that received invocation
* audience matches it.
*/
readonly id: Signer

readonly codec: InboundCodec

/**
* Actual service providing capability handlers.
*/
readonly service: T

readonly catch?: (err: HandlerExecutionError) => void
}

/**
Expand All @@ -1005,13 +1012,9 @@ export interface ServerOptions extends ValidatorOptions {
* Used as input to {@link @ucanto/server#create | `Server.create` } when
* defining a service implementation.
*/
export interface Server<T> extends ServerOptions {
/**
* Actual service providing capability handlers.
*/
readonly service: T

readonly catch?: (err: HandlerExecutionError) => void
export interface Server<T> extends ServerOptions<T> {
readonly context: InvocationContext
readonly catch: (err: HandlerExecutionError) => void
}

/**
Expand All @@ -1025,8 +1028,6 @@ export interface Server<T> extends ServerOptions {
export interface ServerView<T extends Record<string, any>>
extends Server<T>,
Transport.Channel<T> {
context: InvocationContext
catch: (err: HandlerExecutionError) => void
run<C extends Capability>(
invocation: ServiceInvocation<C, T>
): Await<InferReceipt<C, T>>
Expand Down
103 changes: 103 additions & 0 deletions packages/server/src/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as API from '@ucanto/interface'
import { Failure } from '@ucanto/core'
export { MalformedCapability } from '@ucanto/validator'

/**
* @implements {API.HandlerNotFound}
*/
export class HandlerNotFound extends RangeError {
/**
* @param {API.Capability} capability
*/
constructor(capability) {
super()
/** @type {true} */
this.error = true
this.capability = capability
}
/** @type {'HandlerNotFound'} */
get name() {
return 'HandlerNotFound'
}
get message() {
return `service does not implement {can: "${this.capability.can}"} handler`
}
toJSON() {
return {
name: this.name,
error: this.error,
capability: {
can: this.capability.can,
with: this.capability.with,
},
message: this.message,
stack: this.stack,
}
}
}

export class HandlerExecutionError extends Failure {
/**
* @param {API.Capability} capability
* @param {Error} cause
*/
constructor(capability, cause) {
super()
this.capability = capability
this.cause = cause
/** @type { true } */
this.error = true
}

/** @type {'HandlerExecutionError'} */
get name() {
return 'HandlerExecutionError'
}
get message() {
return `service handler {can: "${this.capability.can}"} error: ${this.cause.message}`
}
toJSON() {
return {
name: this.name,
error: this.error,
capability: {
can: this.capability.can,
with: this.capability.with,
},
cause: {
...this.cause,
name: this.cause.name,
message: this.cause.message,
stack: this.cause.stack,
},
message: this.message,
stack: this.stack,
}
}
}

export class InvocationCapabilityError extends Error {
/**
* @param {any} caps
*/
constructor(caps) {
super()
/** @type {true} */
this.error = true
this.caps = caps
}
get name() {
return 'InvocationCapabilityError'
}
get message() {
return `Invocation is required to have a single capability.`
}
toJSON() {
return {
name: this.name,
error: this.error,
message: this.message,
capabilities: this.caps,
}
}
}
11 changes: 2 additions & 9 deletions packages/server/src/lib.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
export * from './api.js'
export * from '@ucanto/core'
export * from './server.js'
export {
Failure,
MalformedCapability,
HandlerNotFound,
Link,
URI,
ok,
error,
} from './server.js'
export { Failure, Link, URI, ok, error } from './server.js'
export {
invoke,
Invocation,
Expand All @@ -23,3 +15,4 @@ export { access, claim, Schema } from '@ucanto/validator'

export * from './handler.js'
export * as API from './api.js'
export * as Error from './error.js'
137 changes: 20 additions & 117 deletions packages/server/src/server.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
import * as API from '@ucanto/interface'
import { Verifier } from '@ucanto/principal'
export {
capability,
URI,
Link,
Failure,
MalformedCapability,
} from '@ucanto/validator'
import { Receipt, Message, Failure, fail } from '@ucanto/core'
export { capability, URI, Link, Failure } from '@ucanto/validator'
import { Receipt, Message, fail } from '@ucanto/core'
import {
HandlerExecutionError,
HandlerNotFound,
InvocationCapabilityError,
} from './error.js'
export { ok, error } from './handler.js'
export { fail }
/**
* Creates a connection to a service.
*
* @template {Record<string, any>} Service
* @param {API.Server<Service>} options
* @param {API.ServerOptions<Service>} options
* @returns {API.ServerView<Service>}
*/
export const create = options => {
const server = new Server(options)
return server
}
export const create = options => new Server(options)

/**
* @template {Record<string, any>} S
* @implements {API.ServerView<S>}
*/
class Server {
/**
* @param {API.Server<S>} options
* @param {API.ServerOptions <S>} options
*/
constructor({ id, service, codec, principal = Verifier, ...rest }) {
const { catch: fail, ...context } = rest
Expand Down Expand Up @@ -69,7 +65,7 @@ class Server {
/**
* @template {Record<string, any>} S
* @template {API.Tuple<API.ServiceInvocation<API.Capability, S>>} I
* @param {API.ServerView<S>} server
* @param {API.Server<S>} server
* @param {API.HTTPRequest<API.AgentMessage<{ In: API.InferInvocations<I>, Out: API.Tuple<API.Receipt> }>>} request
*/
export const handle = async (server, request) => {
Expand All @@ -94,11 +90,11 @@ export const handle = async (server, request) => {
* @template {Record<string, any>} S
* @template {API.Tuple} I
* @param {API.AgentMessage<{ In: API.InferInvocations<I>, Out: API.Tuple<API.Receipt> }>} input
* @param {API.ServerView<S>} server
* @param {API.Server<S>} server
* @returns {Promise<API.AgentMessage<{ Out: API.InferReceipts<I, S>, In: API.Tuple<API.Invocation> }>>}
*/
export const execute = async (input, server) => {
const promises = input.invocations.map($ => invoke($, server))
const promises = input.invocations.map($ => run($, server))

const receipts = /** @type {API.InferReceipts<I, S>} */ (
await Promise.all(promises)
Expand All @@ -108,13 +104,15 @@ export const execute = async (input, server) => {
}

/**
* Executes a single invocation and returns a receipt.
*
* @template {Record<string, any>} Service
* @template {API.Capability} C
* @param {API.Invocation<C>} invocation
* @param {API.ServerView<Service>} server
* @param {API.Server<Service>} server
* @returns {Promise<API.Receipt>}
*/
export const invoke = async (invocation, server) => {
export const run = async (invocation, server) => {
// Invocation needs to have one single capability
if (invocation.capabilities.length !== 1) {
return await Receipt.issue({
Expand Down Expand Up @@ -171,112 +169,17 @@ export const invoke = async (invocation, server) => {
}

/**
* @implements {API.HandlerNotFound}
* @deprecated Use `run` instead.
*/
export class HandlerNotFound extends RangeError {
/**
* @param {API.Capability} capability
*/
constructor(capability) {
super()
/** @type {true} */
this.error = true
this.capability = capability
}
/** @type {'HandlerNotFound'} */
get name() {
return 'HandlerNotFound'
}
get message() {
return `service does not implement {can: "${this.capability.can}"} handler`
}
toJSON() {
return {
name: this.name,
error: this.error,
capability: {
can: this.capability.can,
with: this.capability.with,
},
message: this.message,
stack: this.stack,
}
}
}

class HandlerExecutionError extends Failure {
/**
* @param {API.Capability} capability
* @param {Error} cause
*/
constructor(capability, cause) {
super()
this.capability = capability
this.cause = cause
/** @type { true } */
this.error = true
}

/** @type {'HandlerExecutionError'} */
get name() {
return 'HandlerExecutionError'
}
get message() {
return `service handler {can: "${this.capability.can}"} error: ${this.cause.message}`
}
toJSON() {
return {
name: this.name,
error: this.error,
capability: {
can: this.capability.can,
with: this.capability.with,
},
cause: {
...this.cause,
name: this.cause.name,
message: this.cause.message,
stack: this.cause.stack,
},
message: this.message,
stack: this.stack,
}
}
}

class InvocationCapabilityError extends Error {
/**
* @param {any} caps
*/
constructor(caps) {
super()
/** @type {true} */
this.error = true
this.caps = caps
}
get name() {
return 'InvocationCapabilityError'
}
get message() {
return `Invocation is required to have a single capability.`
}
toJSON() {
return {
name: this.name,
error: this.error,
message: this.message,
capabilities: this.caps,
}
}
}
export const invoke = run

/**
* @param {Record<string, any>} service
* @param {string[]} path
* @returns {null|Record<string, API.ServiceMethod<API.Capability, {}, API.Failure>>}
*/

const resolve = (service, path) => {
export const resolve = (service, path) => {
let target = service
for (const key of path) {
target = target[key]
Expand Down

0 comments on commit 6de27c0

Please sign in to comment.