Skip to content
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
1 change: 1 addition & 0 deletions __tests__/cancellation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function makeMockHandler<T extends ValidProcType>(
) {
return vi.fn<
Procedure<
object,
object,
object,
T,
Expand Down
4 changes: 2 additions & 2 deletions __tests__/cleanup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ describe('request finishing triggers signal onabort', async () => {
const clientTransport = getClientTransport('client');
const serverTransport = getServerTransport();
const handler =
vi.fn<(ctx: ProcedureHandlerContext<object, object>) => void>();
vi.fn<(ctx: ProcedureHandlerContext<object, object, object>) => void>();
const serverId = serverTransport.clientId;
const serviceName = 'service';
const procedureName = procedureType;
Expand All @@ -523,7 +523,7 @@ describe('request finishing triggers signal onabort', async () => {
async handler({
ctx,
}: {
ctx: ProcedureHandlerContext<object, object>;
ctx: ProcedureHandlerContext<object, object, object>;
}) {
handler(ctx);

Expand Down
12 changes: 8 additions & 4 deletions __tests__/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,12 @@ describe.each(testMatrix())(
const requestSchema = Type.Object({
data: Type.String(),
});

interface ParsedMetadata {
data: string;
extra: number;
}

const clientTransport = getClientTransport(
'client',
createClientHandshakeOptions(requestSchema, () => ({ data: 'foobar' })),
Expand All @@ -949,17 +955,15 @@ describe.each(testMatrix())(
});

const services = {
test: createServiceSchema().define({
test: createServiceSchema<object, ParsedMetadata>().define({
getData: Procedure.rpc({
requestInit: Type.Object({}),
responseData: Type.Object({
data: Type.String(),
extra: Type.Number(),
}),
handler: async ({ ctx }) => {
// we haven't extended the interface
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Ok({ ...ctx.metadata } as { data: string; extra: number });
return Ok({ ...ctx.metadata });
},
}),
}),
Expand Down
29 changes: 17 additions & 12 deletions __tests__/typescript-stress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const fnBody = Procedure.rpc<
{
db: string;
},
object,
typeof requestData,
typeof responseData,
typeof responseError
Expand Down Expand Up @@ -429,18 +430,22 @@ describe('Handshake', () => {
},
);

createServer(mockTransportNetwork.getServerTransport(), services, {
handshakeOptions: createServerHandshakeOptions(
schema,
(metadata, _prev) => {
if (metadata.token !== '123') {
return false;
}

return {};
},
),
});
createServer(
mockTransportNetwork.getServerTransport<typeof schema>(),
services,
{
handshakeOptions: createServerHandshakeOptions(
schema,
(metadata, _prev) => {
if (metadata.token !== '123') {
return false;
}

return {};
},
),
},
);
});
});

Expand Down
4 changes: 4 additions & 0 deletions router/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,15 @@ export type Client<
// Context is a server-side implementation detail that doesn't affect the client interface
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
Services
> = InstantiatedServiceSchemaMap<
// Context is a server-side implementation detail that doesn't affect the client interface
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
Services
>,
> = {
Expand Down
124 changes: 53 additions & 71 deletions router/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,78 +5,60 @@ import { ErrResult } from './result';
import { CancelErrorSchema } from './errors';
import { Static } from '@sinclair/typebox';

/**
* The parsed metadata schema for a service. This is the
* return value of the {@link ServerHandshakeOptions.validate}
* if the handshake extension is used.
*
* You should use declaration merging to extend this interface
* with the sanitized metadata.
*
* ```ts
* declare module '@replit/river' {
* interface ParsedMetadata {
* userId: number;
* }
* }
* ```
*/
/* eslint-disable-next-line @typescript-eslint/no-empty-interface */
export interface ParsedMetadata extends Record<string, unknown> {}

/**
* This is passed to every procedure handler and contains various context-level
* information and utilities.
*/
export type ProcedureHandlerContext<State, Context> = Context & {
/**
* State for this service as defined by the service definition.
*/
state: State;
/**
* The span for this procedure call. You can use this to add attributes, events, and
* links to the span.
*/
span: Span;
/**
* Metadata parsed on the server. See {@link ParsedMetadata}
*/
metadata: ParsedMetadata;
/**
* The ID of the session that sent this request.
*/
sessionId: SessionId;
/**
* The ID of the client that sent this request. There may be multiple sessions per client.
*/
from: TransportClientId;
/**
* This is used to cancel the procedure call from the handler and notify the client that the
* call was cancelled.
*
* Cancelling is not the same as closing procedure calls gracefully, please refer to
* the river documentation to understand the difference between the two concepts.
*/
cancel: (message?: string) => ErrResult<Static<typeof CancelErrorSchema>>;
/**
* This signal is a standard [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
* triggered when the procedure invocation is done. This signal tracks the invocation/request finishing
* for _any_ reason, for example:
* - client explicit cancellation
* - procedure handler explicit cancellation via {@link cancel}
* - client session disconnect
* - server cancellation due to client invalid payload
* - invocation finishes cleanly, this depends on the type of the procedure (i.e. rpc handler return, or in a stream after the client-side has closed the request writable and the server-side has closed the response writable)
*
* You can use this to pass it on to asynchronous operations (such as fetch).
*
* You may also want to explicitly register callbacks on the
* ['abort' event](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/abort_event)
* as a way to cleanup after the request is finished.
*
* Note that (per standard AbortSignals) callbacks registered _after_ the procedure invocation
* is done are not triggered. In such cases, you can check the "aborted" property and cleanup
* immediately if needed.
*/
signal: AbortSignal;
};
export type ProcedureHandlerContext<State, Context, ParsedMetadata> =
Context & {
/**
* State for this service as defined by the service definition.
*/
state: State;
/**
* The span for this procedure call. You can use this to add attributes, events, and
* links to the span.
*/
span: Span;
/**
* Metadata parsed on the server. See {@link createServerHandshakeOptions}
*/
metadata: ParsedMetadata;
/**
* The ID of the session that sent this request.
*/
sessionId: SessionId;
/**
* The ID of the client that sent this request. There may be multiple sessions per client.
*/
from: TransportClientId;
/**
* This is used to cancel the procedure call from the handler and notify the client that the
* call was cancelled.
*
* Cancelling is not the same as closing procedure calls gracefully, please refer to
* the river documentation to understand the difference between the two concepts.
*/
cancel: (message?: string) => ErrResult<Static<typeof CancelErrorSchema>>;
/**
* This signal is a standard [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
* triggered when the procedure invocation is done. This signal tracks the invocation/request finishing
* for _any_ reason, for example:
* - client explicit cancellation
* - procedure handler explicit cancellation via {@link cancel}
* - client session disconnect
* - server cancellation due to client invalid payload
* - invocation finishes cleanly, this depends on the type of the procedure (i.e. rpc handler return, or in a stream after the client-side has closed the request writable and the server-side has closed the response writable)
*
* You can use this to pass it on to asynchronous operations (such as fetch).
*
* You may also want to explicitly register callbacks on the
* ['abort' event](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/abort_event)
* as a way to cleanup after the request is finished.
*
* Note that (per standard AbortSignals) callbacks registered _after_ the procedure invocation
* is done are not triggered. In such cases, you can check the "aborted" property and cleanup
* immediately if needed.
*/
signal: AbortSignal;
};
13 changes: 7 additions & 6 deletions router/handshake.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Static, TSchema } from '@sinclair/typebox';
import { ParsedMetadata } from './context';
import { HandshakeErrorCustomHandlerFatalResponseCodes } from '../transport/message';

type ConstructHandshake<T extends TSchema> = () =>
| Static<T>
| Promise<Static<T>>;

type ValidateHandshake<T extends TSchema> = (
type ValidateHandshake<T extends TSchema, ParsedMetadata> = (
metadata: Static<T>,
previousParsedMetadata?: ParsedMetadata,
) =>
Expand Down Expand Up @@ -34,6 +33,7 @@ export interface ClientHandshakeOptions<

export interface ServerHandshakeOptions<
MetadataSchema extends TSchema = TSchema,
ParsedMetadata extends object = object,
> {
/**
* Schema for the metadata that the server receives from the client
Expand All @@ -52,7 +52,7 @@ export interface ServerHandshakeOptions<
* @param isReconnect - Whether the client is reconnecting to the session,
* or if this is a new session.
*/
validate: ValidateHandshake<MetadataSchema>;
validate: ValidateHandshake<MetadataSchema, ParsedMetadata>;
}

export function createClientHandshakeOptions<
Expand All @@ -66,9 +66,10 @@ export function createClientHandshakeOptions<

export function createServerHandshakeOptions<
MetadataSchema extends TSchema = TSchema,
ParsedMetadata extends object = object,
>(
schema: MetadataSchema,
validate: ValidateHandshake<MetadataSchema>,
): ServerHandshakeOptions {
return { schema, validate: validate as ValidateHandshake<TSchema> };
validate: ValidateHandshake<MetadataSchema, ParsedMetadata>,
): ServerHandshakeOptions<MetadataSchema, ParsedMetadata> {
return { schema, validate };
}
2 changes: 1 addition & 1 deletion router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type {
MiddlewareParam,
MiddlewareContext,
} from './server';
export type { ParsedMetadata, ProcedureHandlerContext } from './context';
export type { ProcedureHandlerContext } from './context';
export { Ok, Err } from './result';
export type {
Result,
Expand Down
Loading
Loading