import { TerminalError, ensureError } from "./errors.js";

//#region src/types/rpc.ts
var Opts = class Opts {
	/**
	* Create a call configuration from the provided options.
	*
	* @param opts the call configuration
	*/
	static from(opts) {
		return new Opts(opts);
	}
	constructor(opts) {
		this.opts = opts;
	}
	getOpts() {
		return this.opts;
	}
};
var SendOpts = class SendOpts {
	static from(opts) {
		return new SendOpts(opts);
	}
	getOpts() {
		return this.opts;
	}
	constructor(opts) {
		this.opts = opts;
	}
};
let rpc;
(function(_rpc) {
	_rpc.opts = (opts) => Opts.from(opts);
	_rpc.sendOpts = (opts) => SendOpts.from(opts);
})(rpc || (rpc = {}));
function optsFromArgs(args) {
	let parameter;
	let opts;
	switch (args.length) {
		case 0: break;
		case 1:
			if (args[0] instanceof Opts) opts = args[0].getOpts();
			else if (args[0] instanceof SendOpts) opts = args[0].getOpts();
			else parameter = args[0];
			break;
		case 2:
			parameter = args[0];
			if (args[1] instanceof Opts) opts = args[1].getOpts();
			else if (args[1] instanceof SendOpts) opts = args[1].getOpts();
			else throw new TypeError("The second argument must be either Opts or SendOpts");
			break;
		default: throw new TypeError("unexpected number of arguments");
	}
	return {
		parameter,
		opts
	};
}
const makeRpcCallProxy = (genericCall, defaultSerde, service$1, key) => {
	return new Proxy({}, { get: (_target, prop) => {
		const method = prop;
		return (...args) => {
			const { parameter, opts } = optsFromArgs(args);
			const requestSerde = opts?.input ?? defaultSerde;
			const responseSerde = opts?.output ?? defaultSerde;
			return genericCall({
				service: service$1,
				method,
				parameter,
				key,
				headers: opts?.headers,
				inputSerde: requestSerde,
				outputSerde: responseSerde,
				idempotencyKey: opts?.idempotencyKey,
				name: opts?.name
			});
		};
	} });
};
const makeRpcSendProxy = (genericSend, defaultSerde, service$1, key) => {
	return new Proxy({}, { get: (_target, prop) => {
		const method = prop;
		return (...args) => {
			const { parameter, opts } = optsFromArgs(args);
			const requestSerde = opts?.input ?? defaultSerde;
			const delay = opts?.delay;
			return genericSend({
				service: service$1,
				method,
				parameter,
				key,
				headers: opts?.headers,
				delay,
				inputSerde: requestSerde,
				idempotencyKey: opts?.idempotencyKey,
				name: opts?.name
			});
		};
	} });
};
let HandlerKind = /* @__PURE__ */ function(HandlerKind$1) {
	HandlerKind$1[HandlerKind$1["SERVICE"] = 0] = "SERVICE";
	HandlerKind$1[HandlerKind$1["EXCLUSIVE"] = 1] = "EXCLUSIVE";
	HandlerKind$1[HandlerKind$1["SHARED"] = 2] = "SHARED";
	HandlerKind$1[HandlerKind$1["WORKFLOW"] = 3] = "WORKFLOW";
	return HandlerKind$1;
}({});
const HANDLER_SYMBOL = Symbol("Handler");
var HandlerWrapper = class HandlerWrapper {
	static from(kind, handler, opts) {
		const handlerCopy = function(...args) {
			return handler.apply(this, args);
		};
		return new HandlerWrapper(kind, handlerCopy, opts);
	}
	static fromHandler(handler) {
		return handler[HANDLER_SYMBOL];
	}
	constructor(kind, handler, options) {
		this.kind = kind;
		this.handler = handler;
		this.options = options;
	}
	bindInstance(t) {
		this.handler = this.handler.bind(t);
	}
	async invoke(context, input) {
		let req;
		try {
			req = (this.options?.input ?? context.defaultSerde).deserialize(input);
		} catch (e) {
			throw new TerminalError(`Failed to deserialize input: ${ensureError(e).message}`, { errorCode: 400 });
		}
		const res = await this.handler(context, req);
		return (this.options?.output ?? context.defaultSerde).serialize(res);
	}
	/**
	* Instead of a HandlerWrapper with a handler property,
	* return the original handler with a HandlerWrapper property.
	* This is needed to keep the appearance of regular functions
	* bound to an object, so that for example, `this.foo(ctx, arg)` would
	* work.
	*/
	transpose() {
		const handler = this.handler;
		if (HandlerWrapper.fromHandler(handler) !== void 0) return handler;
		Object.defineProperty(handler, HANDLER_SYMBOL, { value: this });
		return handler;
	}
};
let handlers;
(function(_handlers) {
	function handler(opts, fn) {
		return HandlerWrapper.from(HandlerKind.SERVICE, fn, opts).transpose();
	}
	_handlers.handler = handler;
	let workflow$1;
	(function(_workflow) {
		function workflow$2(optsOrFn, fn) {
			if (typeof optsOrFn === "function") return HandlerWrapper.from(HandlerKind.WORKFLOW, optsOrFn).transpose();
			const opts = optsOrFn;
			if (typeof fn !== "function") throw new TypeError("The second argument must be a function");
			return HandlerWrapper.from(HandlerKind.WORKFLOW, fn, opts).transpose();
		}
		_workflow.workflow = workflow$2;
		function shared(optsOrFn, fn) {
			if (typeof optsOrFn === "function") return HandlerWrapper.from(HandlerKind.SHARED, optsOrFn).transpose();
			const opts = optsOrFn;
			if (typeof fn !== "function") throw new TypeError("The second argument must be a function");
			return HandlerWrapper.from(HandlerKind.SHARED, fn, opts).transpose();
		}
		_workflow.shared = shared;
	})(workflow$1 || (workflow$1 = _handlers.workflow || (_handlers.workflow = {})));
	let object$1;
	(function(_object) {
		function exclusive(optsOrFn, fn) {
			if (typeof optsOrFn === "function") return HandlerWrapper.from(HandlerKind.EXCLUSIVE, optsOrFn).transpose();
			const opts = optsOrFn;
			if (typeof fn !== "function") throw new TypeError("The second argument must be a function");
			return HandlerWrapper.from(HandlerKind.EXCLUSIVE, fn, opts).transpose();
		}
		_object.exclusive = exclusive;
		function shared(optsOrFn, fn) {
			if (typeof optsOrFn === "function") return HandlerWrapper.from(HandlerKind.SHARED, optsOrFn).transpose();
			const opts = optsOrFn;
			if (typeof fn !== "function") throw new TypeError("The second argument must be a function");
			return HandlerWrapper.from(HandlerKind.SHARED, fn, opts).transpose();
		}
		_object.shared = shared;
	})(object$1 || (object$1 = _handlers.object || (_handlers.object = {})));
})(handlers || (handlers = {}));
/**
* Define a Restate service.
*
* @example Here is an example of how to define a service:
*
* ```ts
* const greeter = service({
*   name: "greeter",
*   handlers: {
*     greet: async (ctx: Context, name: string) => {
*       return `Hello ${name}`;
*     }
*   }
* });
* ```
*
* To use the service, you can bind it to an endpoint:
* ```
* ...
* endpoint.bind(greeter)
* ```
* @example To use a service, you can export its type to be used in a client:
* ```
* export type Greeter = typeof greeter;
* ...
* ...
* import type { Greeter } from "./greeter";
* const client = ctx.serviceClient<Greeter>({ name : "greeter"});
* client.greet("World").then(console.log);
* ```
*
* @example Alternatively to avoid repeating the service name, you can:
* ```
* import type {Greeter} from "./greeter";
* const Greeter: Greeter = { name : "greeter"};
*
* // now you can reference the service like this:
* const client = ctx.serviceClient(Greeter);
* ```
*
* @param name the service name
* @param handlers the handlers for the service
* @param description an optional description for the service
* @param metadata an optional metadata for the service
* @type P the name of the service
* @type M the handlers for the service
*/
const service = (service$1) => {
	if (!service$1.handlers) throw new Error("service must be defined");
	const handlers$1 = Object.entries(service$1.handlers).map(([name, handler]) => {
		if (handler instanceof Function) {
			if (HandlerWrapper.fromHandler(handler) !== void 0) return [name, handler];
			return [name, HandlerWrapper.from(HandlerKind.SERVICE, handler).transpose()];
		}
		throw new TypeError(`Unexpected handler type ${name}`);
	});
	return {
		name: service$1.name,
		service: Object.fromEntries(handlers$1),
		metadata: service$1.metadata,
		description: service$1.description,
		options: service$1.options
	};
};
/**
* Define a Restate virtual object.
*
* @example Here is an example of how to define a virtual object:
* ```ts
*        const counter = object({
*            name: "counter",
*            handlers: {
*                  add: async (ctx: ObjectContext, amount: number) => {},
*                  get: async (ctx: ObjectContext) => {}
*            }
*        })
*  ```
*
* @example To use the object, you can bind it to an endpoint:
* ```ts
* ...
* endpoint.bind(counter)
* ```
*
*  @see to interact with the object, you can use the object client:
* ```ts
* ...
* const client = ctx.objectClient<typeof counter>({ name: "counter"});
* const res = await client.add(1)
* ```
*
* ### Shared handlers
*
* Shared handlers are used to allow concurrent read-only access to the object.
* This is useful when you want to allow multiple clients to read the object's state at the same time.
* To define a shared handler, you can use the `shared` decorator as shown below:
*
* ```ts
*      const counter = object({
*          name: "counter",
*          handlers: {
*
*            add: async (ctx: ObjectContext, amount: number) => { .. },
*
*            get: handlers.object.shared(async (ctx: ObjectSharedContext) => {
*                  return ctx.get<number>("count");
*            })
*       }
*     });
* ```
*
* @param name the name of the object
* @param handlers the handlers for the object
* @param description an optional description for the object
* @param metadata an optional metadata for the object
* @type P the name of the object
* @type M the handlers for the object
*/
const object = (object$1) => {
	if (!object$1.handlers) throw new Error("object options must be defined");
	const handlers$1 = Object.entries(object$1.handlers).map(([name, handler]) => {
		if (handler instanceof Function) {
			if (HandlerWrapper.fromHandler(handler) !== void 0) return [name, handler];
			return [name, HandlerWrapper.from(HandlerKind.EXCLUSIVE, handler).transpose()];
		}
		throw new TypeError(`Unexpected handler type ${name}`);
	});
	return {
		name: object$1.name,
		object: Object.fromEntries(handlers$1),
		metadata: object$1.metadata,
		description: object$1.description,
		options: object$1.options
	};
};
/**
* Define a Restate workflow.
*
*
* @example Here is an example of how to define a workflow:
* ```ts
* const mywf = workflow({
*   name: "mywf",
*   handlers: {
*     run: async (ctx: WorkflowContext, argument: any) => {
*       return "Hello World";
*     }
*   }
* });
* ```
*
* ### Note:
* * That a workflow must contain exactly one handler named 'run', and this handler must accept as a first argument a WorkflowContext.
* * The workflow handlers other than 'run' must accept as a first argument a WorkflowSharedContext.
* * The workflow handlers can not be named 'workflowSubmit', 'workflowAttach', 'workflowOutput' - as these are reserved keywords.
*
* @example To use the workflow, you can bind it to an endpoint:
* ```ts
* endpoint.bind(mywf)
* ```
*
* @example To interact with the workflow, you can use the workflow client:
* ```ts
* const client = ctx.workflowClient<typeof mywf>({ name: "mywf"});
* const res = await client.run("Hello");
* ```
*
* To use the workflow client from any other environment (like a browser), please refer to the documentation.
* https://docs.restate.dev
*
*
*
* @param name the workflow name
* @param handlers the handlers for the workflow.
*/
const workflow = (workflow$1) => {
	if (!workflow$1.handlers) throw new Error("workflow must contain handlers");
	const runHandler = workflow$1.handlers["run"];
	let runWrapper;
	if (runHandler instanceof HandlerWrapper) runWrapper = runHandler;
	else if (runHandler instanceof Function) runWrapper = HandlerWrapper.fromHandler(runHandler) ?? HandlerWrapper.from(HandlerKind.WORKFLOW, runHandler);
	else throw new TypeError(`Missing main workflow handler, named 'run'`);
	if (runWrapper.kind !== HandlerKind.WORKFLOW) throw new TypeError(`Workflow's main handler handler run, must be of type workflow'`);
	const handlers$1 = [["run", runWrapper.transpose()]];
	for (const [name, handler] of Object.entries(workflow$1.handlers)) {
		if (name === "run") continue;
		let wrapper;
		if (handler instanceof HandlerWrapper) wrapper = handler;
		else if (handler instanceof Function) wrapper = HandlerWrapper.fromHandler(handler) ?? HandlerWrapper.from(HandlerKind.SHARED, handler);
		else throw new TypeError(`Unexpected handler type ${name}`);
		if (wrapper.kind === HandlerKind.WORKFLOW) throw new TypeError(`A workflow must contain exactly one handler annotated as workflow, named 'run'. Please use a shared handler for any additional handlers`);
		handlers$1.push([name, wrapper.transpose()]);
	}
	return {
		name: workflow$1.name,
		workflow: Object.fromEntries(handlers$1),
		metadata: workflow$1.metadata,
		description: workflow$1.description,
		options: workflow$1.options
	};
};

//#endregion
export { HandlerKind, HandlerWrapper, Opts, SendOpts, handlers, makeRpcCallProxy, makeRpcSendProxy, object, rpc, service, workflow };
//# sourceMappingURL=rpc.js.map