feat: add procedure to relay
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
import { ServerContext, UnauthorizedError } from "@spec/relay";
|
import { ServerContext } from "@spec/relay";
|
||||||
|
|
||||||
|
import type { Sockets } from "~libraries/socket/sockets.ts";
|
||||||
|
|
||||||
import { Session } from "../auth/auth.ts";
|
import { Session } from "../auth/auth.ts";
|
||||||
import { req } from "./request.ts";
|
import { req } from "./request.ts";
|
||||||
@@ -11,15 +13,25 @@ declare module "@spec/relay" {
|
|||||||
request: Request;
|
request: Request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get request session instance.
|
* Is the request authenticated.
|
||||||
*/
|
*/
|
||||||
session: Session;
|
isAuthenticated: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get account id from session, throws an error if the request
|
* Get account id from session, throws an error if the request
|
||||||
* does not have a valid session.
|
* does not have a valid session.
|
||||||
*/
|
*/
|
||||||
accountId: string;
|
accountId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get request session instance.
|
||||||
|
*/
|
||||||
|
session: Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sockets instance attached to the server.
|
||||||
|
*/
|
||||||
|
sockets: Sockets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,15 +39,20 @@ export function getRequestContext(request: Request): ServerContext {
|
|||||||
return {
|
return {
|
||||||
request,
|
request,
|
||||||
|
|
||||||
get session(): Session {
|
get isAuthenticated(): boolean {
|
||||||
if (req.session === undefined) {
|
return req.isAuthenticated;
|
||||||
throw new UnauthorizedError();
|
|
||||||
}
|
|
||||||
return req.session;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get accountId() {
|
get accountId() {
|
||||||
return this.session.accountId;
|
return this.session.accountId;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get session(): Session {
|
||||||
|
return req.session;
|
||||||
|
},
|
||||||
|
|
||||||
|
get sockets(): Sockets {
|
||||||
|
return req.sockets;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { InternalServerError, UnauthorizedError } from "@spec/relay";
|
||||||
|
|
||||||
import { Session } from "../auth/auth.ts";
|
import { Session } from "../auth/auth.ts";
|
||||||
import { asyncLocalStorage } from "./storage.ts";
|
import { asyncLocalStorage } from "./storage.ts";
|
||||||
|
|
||||||
@@ -5,21 +7,16 @@ export const req = {
|
|||||||
get store() {
|
get store() {
|
||||||
const store = asyncLocalStorage.getStore();
|
const store = asyncLocalStorage.getStore();
|
||||||
if (store === undefined) {
|
if (store === undefined) {
|
||||||
throw new Error("Request > AsyncLocalStorage not defined.");
|
throw new InternalServerError("AsyncLocalStorage not defined.");
|
||||||
}
|
}
|
||||||
return store;
|
return store;
|
||||||
},
|
},
|
||||||
|
|
||||||
get socket() {
|
get sockets() {
|
||||||
return this.store.socket;
|
if (this.store.sockets === undefined) {
|
||||||
},
|
throw new InternalServerError("Sockets not defined.");
|
||||||
|
}
|
||||||
/**
|
return this.store.sockets;
|
||||||
* Get store that is potentially undefined.
|
|
||||||
* Typically used when utility functions might run in and out of request scope.
|
|
||||||
*/
|
|
||||||
get unsafeStore() {
|
|
||||||
return asyncLocalStorage.getStore();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +29,10 @@ export const req = {
|
|||||||
/**
|
/**
|
||||||
* Get current session.
|
* Get current session.
|
||||||
*/
|
*/
|
||||||
get session(): Session | undefined {
|
get session(): Session {
|
||||||
|
if (this.store.session === undefined) {
|
||||||
|
throw new UnauthorizedError();
|
||||||
|
}
|
||||||
return this.store.session;
|
return this.store.session;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -44,14 +44,18 @@ export const req = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a JSON-RPC 2.0 notification to the request if sent through a
|
* Get current session.
|
||||||
* WebSocket connection.
|
|
||||||
*
|
|
||||||
* @param method - Method to send notification to.
|
|
||||||
* @param params - Params to pass to the method.
|
|
||||||
*/
|
*/
|
||||||
notify(method: string, params: any): void {
|
getSession(): Session | undefined {
|
||||||
this.socket?.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
|
return this.store.session;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get store that is potentially undefined.
|
||||||
|
* Typically used when utility functions might run in and out of request scope.
|
||||||
|
*/
|
||||||
|
getStore() {
|
||||||
|
return asyncLocalStorage.getStore();
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { AsyncLocalStorage } from "node:async_hooks";
|
import { AsyncLocalStorage } from "node:async_hooks";
|
||||||
|
|
||||||
import type { Session } from "~libraries/auth/mod.ts";
|
import type { Session } from "~libraries/auth/mod.ts";
|
||||||
|
import type { Sockets } from "~libraries/socket/sockets.ts";
|
||||||
|
|
||||||
export const asyncLocalStorage = new AsyncLocalStorage<{
|
export const asyncLocalStorage = new AsyncLocalStorage<{
|
||||||
session?: Session;
|
session?: Session;
|
||||||
@@ -9,7 +10,7 @@ export const asyncLocalStorage = new AsyncLocalStorage<{
|
|||||||
start: number;
|
start: number;
|
||||||
end?: number;
|
end?: number;
|
||||||
};
|
};
|
||||||
socket?: WebSocket;
|
sockets?: Sockets;
|
||||||
response: {
|
response: {
|
||||||
headers: Headers;
|
headers: Headers;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { Sockets } from "./sockets.ts";
|
|
||||||
@@ -2,6 +2,7 @@ import { toJsonRpc } from "@valkyr/json-rpc";
|
|||||||
|
|
||||||
import { Session } from "~libraries/auth/mod.ts";
|
import { Session } from "~libraries/auth/mod.ts";
|
||||||
import { logger } from "~libraries/logger/mod.ts";
|
import { logger } from "~libraries/logger/mod.ts";
|
||||||
|
import { asyncLocalStorage } from "~libraries/server/storage.ts";
|
||||||
|
|
||||||
import { sockets } from "./sockets.ts";
|
import { sockets } from "./sockets.ts";
|
||||||
|
|
||||||
@@ -23,35 +24,35 @@ export function upgrade(request: Request, session?: Session) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = toJsonRpc(event.data);
|
const message = toJsonRpc(event.data);
|
||||||
|
|
||||||
logger.prefix("Socket").info(body);
|
logger.prefix("Socket").info(message);
|
||||||
|
|
||||||
asyncLocalStorage.run(
|
asyncLocalStorage.run(
|
||||||
{
|
{
|
||||||
session,
|
session,
|
||||||
info: {
|
info: {
|
||||||
method: body.method!,
|
method: message.method!,
|
||||||
start: Date.now(),
|
start: Date.now(),
|
||||||
},
|
},
|
||||||
socket,
|
sockets,
|
||||||
response: {
|
response: {
|
||||||
headers: new Headers(),
|
headers: new Headers(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
api
|
// api
|
||||||
.handleCommand(body)
|
// .send(body)
|
||||||
.then((response) => {
|
// .then((response) => {
|
||||||
if (response !== undefined) {
|
// if (response !== undefined) {
|
||||||
logger.info({ response });
|
// logger.info({ response });
|
||||||
socket.send(JSON.stringify(response));
|
// socket.send(JSON.stringify(response));
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.catch((error) => {
|
// .catch((error) => {
|
||||||
logger.info({ error });
|
// logger.info({ error });
|
||||||
socket.send(JSON.stringify(error));
|
// socket.send(JSON.stringify(error));
|
||||||
});
|
// });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@valkyr/auth": "npm:@jsr/valkyr__auth@2",
|
"@valkyr/auth": "npm:@jsr/valkyr__auth@2",
|
||||||
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.0-beta.6",
|
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.0-beta.6",
|
||||||
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1",
|
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1",
|
||||||
|
"@valkyr/json-rpc": "npm:@jsr/valkyr__json-rpc@1",
|
||||||
"cookie": "1",
|
"cookie": "1",
|
||||||
"mongodb": "6",
|
"mongodb": "6",
|
||||||
"zod": "4"
|
"zod": "4"
|
||||||
|
|||||||
22
api/procedures/event.ts
Normal file
22
api/procedures/event.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { procedure } from "@spec/relay/mod.ts";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
const EventSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
stream: z.uuid(),
|
||||||
|
type: z.string(),
|
||||||
|
data: z.any(),
|
||||||
|
meta: z.any(),
|
||||||
|
recorded: z.string(),
|
||||||
|
created: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default procedure
|
||||||
|
.method("event")
|
||||||
|
.access("public")
|
||||||
|
.params(EventSchema)
|
||||||
|
.response(z.uuid())
|
||||||
|
.handle(async (event) => {
|
||||||
|
console.log(event);
|
||||||
|
return crypto.randomUUID();
|
||||||
|
});
|
||||||
9
deno.lock
generated
9
deno.lock
generated
@@ -14,6 +14,7 @@
|
|||||||
"npm:@jsr/valkyr__event-emitter@1": "1.0.1",
|
"npm:@jsr/valkyr__event-emitter@1": "1.0.1",
|
||||||
"npm:@jsr/valkyr__event-store@2.0.0-beta.6": "2.0.0-beta.6",
|
"npm:@jsr/valkyr__event-store@2.0.0-beta.6": "2.0.0-beta.6",
|
||||||
"npm:@jsr/valkyr__inverse@1": "1.0.1",
|
"npm:@jsr/valkyr__inverse@1": "1.0.1",
|
||||||
|
"npm:@jsr/valkyr__json-rpc@1": "1.1.0",
|
||||||
"npm:@tailwindcss/vite@4": "4.1.12_vite@7.1.2__picomatch@4.0.3_@types+node@22.15.15",
|
"npm:@tailwindcss/vite@4": "4.1.12_vite@7.1.2__picomatch@4.0.3_@types+node@22.15.15",
|
||||||
"npm:@tanstack/react-query@5": "5.85.3_react@19.1.1",
|
"npm:@tanstack/react-query@5": "5.85.3_react@19.1.1",
|
||||||
"npm:@tanstack/react-router-devtools@1": "1.131.18_@tanstack+react-router@1.131.18__react@19.1.1__react-dom@19.1.1___react@19.1.1_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@tanstack/react-router-devtools@1": "1.131.18_@tanstack+react-router@1.131.18__react@19.1.1__react-dom@19.1.1___react@19.1.1_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
@@ -567,6 +568,13 @@
|
|||||||
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
||||||
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__inverse/1.0.1.tgz"
|
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__inverse/1.0.1.tgz"
|
||||||
},
|
},
|
||||||
|
"@jsr/valkyr__json-rpc@1.1.0": {
|
||||||
|
"integrity": "sha512-i1dwWLI29i5mqRvS2NbI3jUyw8uZuO71hJRvT5+sGAexG8RmQJP4N+ETJkxq0RNwNAGGG1bocuzdqnawa2ahIA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@jsr/valkyr__event-emitter"
|
||||||
|
],
|
||||||
|
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__json-rpc/1.1.0.tgz"
|
||||||
|
},
|
||||||
"@jsr/valkyr__testcontainers@2.0.2": {
|
"@jsr/valkyr__testcontainers@2.0.2": {
|
||||||
"integrity": "sha512-YnmfraYFr3msoUGrIFeElm03nbQqXOaPu0QUT6JI3w6/mIYpVfzPxghkB7gn2RIc81QgrqjwKJE/AL3dltlR1w==",
|
"integrity": "sha512-YnmfraYFr3msoUGrIFeElm03nbQqXOaPu0QUT6JI3w6/mIYpVfzPxghkB7gn2RIc81QgrqjwKJE/AL3dltlR1w==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -2136,6 +2144,7 @@
|
|||||||
"npm:@jsr/valkyr__auth@2",
|
"npm:@jsr/valkyr__auth@2",
|
||||||
"npm:@jsr/valkyr__event-store@2.0.0-beta.6",
|
"npm:@jsr/valkyr__event-store@2.0.0-beta.6",
|
||||||
"npm:@jsr/valkyr__inverse@1",
|
"npm:@jsr/valkyr__inverse@1",
|
||||||
|
"npm:@jsr/valkyr__json-rpc@1",
|
||||||
"npm:cookie@1",
|
"npm:cookie@1",
|
||||||
"npm:mongodb@6",
|
"npm:mongodb@6",
|
||||||
"npm:zod@4"
|
"npm:zod@4"
|
||||||
|
|||||||
239
spec/relay/libraries/procedure.ts
Normal file
239
spec/relay/libraries/procedure.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import z, { ZodType } from "zod";
|
||||||
|
|
||||||
|
import { ServerError, ServerErrorClass } from "./errors.ts";
|
||||||
|
import { Access, ServerContext } from "./types.ts";
|
||||||
|
|
||||||
|
export class Procedure<const TState extends State = State> {
|
||||||
|
readonly type = "procedure" as const;
|
||||||
|
|
||||||
|
declare readonly $params: TState["params"] extends ZodType ? z.input<TState["params"]> : never;
|
||||||
|
declare readonly $response: TState["response"] extends ZodType ? z.output<TState["response"]> : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new Procedure instance.
|
||||||
|
*
|
||||||
|
* @param state - Procedure state.
|
||||||
|
*/
|
||||||
|
constructor(readonly state: TState) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procedure method value.
|
||||||
|
*/
|
||||||
|
get method(): State["method"] {
|
||||||
|
return this.state.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access level of the procedure which acts as the first barrier of entry
|
||||||
|
* to ensure that requests are valid.
|
||||||
|
*
|
||||||
|
* By default on the server the lack of access definition will result
|
||||||
|
* in an error as all procedures needs an access definition.
|
||||||
|
*
|
||||||
|
* @param access - Access level of the procedure.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* procedure
|
||||||
|
* .method("users:create")
|
||||||
|
* .access("public")
|
||||||
|
* .handle(async () => {
|
||||||
|
* // ...
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* procedure
|
||||||
|
* .method("users:get-by-id")
|
||||||
|
* .access("session")
|
||||||
|
* .params(z.string())
|
||||||
|
* .handle(async (userId, context) => {
|
||||||
|
* if (userId !== context.session.userId) {
|
||||||
|
* return new ForbiddenError("Cannot read other users details.");
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* procedure
|
||||||
|
* .method("users:update")
|
||||||
|
* .access([resource("users", "update")])
|
||||||
|
* .params(z.array(z.string(), z.object({ name: z.string() })))
|
||||||
|
* .handle(async ([userId, payload], context) => {
|
||||||
|
* if (userId !== context.session.userId) {
|
||||||
|
* return new ForbiddenError("Cannot update other users details.");
|
||||||
|
* }
|
||||||
|
* console.log(userId, payload); // => string, { name: string }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
access<TAccess extends Access>(access: TAccess): Procedure<Omit<TState, "access"> & { access: TAccess }> {
|
||||||
|
return new Procedure({ ...this.state, access: access as TAccess });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the payload forwarded to the handler.
|
||||||
|
*
|
||||||
|
* @param params - Method payload.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* procedure
|
||||||
|
* .method("users:create")
|
||||||
|
* .access([resource("users", "create")])
|
||||||
|
* .params(z.object({
|
||||||
|
* name: z.string(),
|
||||||
|
* email: z.email(),
|
||||||
|
* }))
|
||||||
|
* .handle(async ({ name, email }, context) => {
|
||||||
|
* return { name, email, createdBy: context.session.userId };
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
params<TParams extends ZodType>(params: TParams): Procedure<Omit<TState, "params"> & { params: TParams }> {
|
||||||
|
return new Procedure({ ...this.state, params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of the possible error responses this procedure produces.
|
||||||
|
*
|
||||||
|
* @param errors - Error shapes of the procedure.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* procedure
|
||||||
|
* .method("users:list")
|
||||||
|
* .errors([
|
||||||
|
* BadRequestError
|
||||||
|
* ])
|
||||||
|
* .handle(async () => {
|
||||||
|
* return new BadRequestError();
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
errors<TErrors extends ServerErrorClass[]>(errors: TErrors): Procedure<Omit<TState, "errors"> & { errors: TErrors }> {
|
||||||
|
return new Procedure({ ...this.state, errors });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the success response this procedure produces. This is used by the transform
|
||||||
|
* tools to ensure the client receives parsed data.
|
||||||
|
*
|
||||||
|
* @param response - Response shape of the procedure.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* procedure
|
||||||
|
* .post("users:list")
|
||||||
|
* .response(
|
||||||
|
* z.array(
|
||||||
|
* z.object({
|
||||||
|
* name: z.string()
|
||||||
|
* }),
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* .handle(async () => {
|
||||||
|
* return [{ name: "John Doe" }];
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
response<TResponse extends ZodType>(
|
||||||
|
response: TResponse,
|
||||||
|
): Procedure<Omit<TState, "response"> & { response: TResponse }> {
|
||||||
|
return new Procedure({ ...this.state, response });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server handler callback method.
|
||||||
|
*
|
||||||
|
* Handler receives the params, query, body, actions in order of definition.
|
||||||
|
* So if your route has params, and body the route handle method will
|
||||||
|
* receive (params, body) as arguments.
|
||||||
|
*
|
||||||
|
* @param handle - Handle function to trigger when the route is executed.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* procedure
|
||||||
|
* .method("users:list")
|
||||||
|
* .response(
|
||||||
|
* z.array(
|
||||||
|
* z.object({
|
||||||
|
* name: z.string()
|
||||||
|
* }),
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* .handle(async () => {
|
||||||
|
* return [{ name: "John Doe" }];
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
handle<THandleFn extends HandleFn<ServerArgs<TState>, TState["response"]>>(
|
||||||
|
handle: THandleFn,
|
||||||
|
): Procedure<Omit<TState, "handle"> & { handle: THandleFn }> {
|
||||||
|
return new Procedure({ ...this.state, handle });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Factories
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route factories allowing for easy generation of relay compliant routes.
|
||||||
|
*/
|
||||||
|
export const procedure: {
|
||||||
|
/**
|
||||||
|
* Create a new procedure with given method name.
|
||||||
|
*
|
||||||
|
* @param method Name of the procedure used to match requests against.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* procedure
|
||||||
|
* .method("users:get-by-id")
|
||||||
|
* .params(
|
||||||
|
* z.string().describe("Users unique identifier")
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
method<TMethod extends string>(method: TMethod): Procedure<{ method: TMethod }>;
|
||||||
|
} = {
|
||||||
|
method<TMethod extends string>(method: TMethod) {
|
||||||
|
return new Procedure({ method });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Types
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Procedures = {
|
||||||
|
[key: string]: Procedures | Procedure;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
method: string;
|
||||||
|
access?: Access;
|
||||||
|
params?: ZodType;
|
||||||
|
errors?: ServerErrorClass[];
|
||||||
|
response?: ZodType;
|
||||||
|
handle?: HandleFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
type HandleFn<TArgs extends Array<any> = any[], TResponse = any> = (
|
||||||
|
...args: TArgs
|
||||||
|
) => TResponse extends ZodType
|
||||||
|
? Promise<z.infer<TResponse> | Response | ServerError>
|
||||||
|
: Promise<Response | ServerError | void>;
|
||||||
|
|
||||||
|
type ServerArgs<TState extends State> =
|
||||||
|
HasInputArgs<TState> extends true ? [z.output<TState["params"]>, ServerContext] : [ServerContext];
|
||||||
|
|
||||||
|
type HasInputArgs<TState extends State> = TState["params"] extends ZodType ? true : false;
|
||||||
@@ -3,6 +3,7 @@ import z, { ZodObject, ZodRawShape, ZodType } from "zod";
|
|||||||
|
|
||||||
import { ServerError, ServerErrorClass } from "./errors.ts";
|
import { ServerError, ServerErrorClass } from "./errors.ts";
|
||||||
import { Hooks } from "./hooks.ts";
|
import { Hooks } from "./hooks.ts";
|
||||||
|
import { ServerContext } from "./types.ts";
|
||||||
|
|
||||||
export class Route<const TState extends RouteState = RouteState> {
|
export class Route<const TState extends RouteState = RouteState> {
|
||||||
readonly type = "route" as const;
|
readonly type = "route" as const;
|
||||||
@@ -447,9 +448,6 @@ export type RouteAccess = "public" | "session" | (() => boolean)[];
|
|||||||
|
|
||||||
export type AccessFn = (resource: string, action: string) => () => boolean;
|
export type AccessFn = (resource: string, action: string) => () => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
||||||
export interface ServerContext {}
|
|
||||||
|
|
||||||
type HandleFn<TArgs extends Array<any> = any[], TResponse = any> = (
|
type HandleFn<TArgs extends Array<any> = any[], TResponse = any> = (
|
||||||
...args: TArgs
|
...args: TArgs
|
||||||
) => TResponse extends ZodType
|
) => TResponse extends ZodType
|
||||||
|
|||||||
6
spec/relay/libraries/types.ts
Normal file
6
spec/relay/libraries/types.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type Access = "public" | "session" | (() => boolean)[];
|
||||||
|
|
||||||
|
export type AccessFn = (resource: string, action: string) => () => boolean;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
export interface ServerContext {}
|
||||||
@@ -2,4 +2,5 @@ export * from "./libraries/adapter.ts";
|
|||||||
export * from "./libraries/client.ts";
|
export * from "./libraries/client.ts";
|
||||||
export * from "./libraries/errors.ts";
|
export * from "./libraries/errors.ts";
|
||||||
export * from "./libraries/hooks.ts";
|
export * from "./libraries/hooks.ts";
|
||||||
|
export * from "./libraries/procedure.ts";
|
||||||
export * from "./libraries/route.ts";
|
export * from "./libraries/route.ts";
|
||||||
|
|||||||
Reference in New Issue
Block a user