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

fix(server): loosen requirements on statics #353

Merged
merged 4 commits into from
May 10, 2024
Merged
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
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
Loading