Skip to content

Commit

Permalink
feat: Add generics for expected JSON types for Abstract Requests (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjholm authored Apr 20, 2023
2 parents ded1901 + 5d53e50 commit 04df14c
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 32 deletions.
10 changes: 5 additions & 5 deletions src/api/events/v0/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const DEFAULT_PUBLISH_OPTS: PublishOptions = {
delay: 0,
};

export class Topic {
export class Topic<T extends NitricEvent = NitricEvent> {
eventing: Eventing;
name: string;

Expand Down Expand Up @@ -90,9 +90,9 @@ export class Topic {
* ```
*/
async publish(
event: NitricEvent,
event: T,
opts: PublishOptions = DEFAULT_PUBLISH_OPTS
): Promise<NitricEvent> {
): Promise<T> {
const { id, payloadType = 'none', payload } = event;
const publishOpts = {
...DEFAULT_PUBLISH_OPTS,
Expand All @@ -109,7 +109,7 @@ export class Topic {
request.setEvent(evt);
request.setDelay(publishOpts.delay);

return new Promise<NitricEvent>((resolve, reject) => {
return new Promise<T>((resolve, reject) => {
this.eventing.EventServiceClient.publish(request, (error, response) => {
if (error) {
reject(fromGrpcError(error));
Expand Down Expand Up @@ -167,7 +167,7 @@ export class Eventing {
* const topic = eventing.topic('notifications');
* ```
*/
public topic(name: string): Topic {
public topic<T extends NitricEvent = NitricEvent>(name: string): Topic<T> {
if (!name) {
throw new InvalidArgumentError('A topic name is needed to use a Topic.');
}
Expand Down
23 changes: 16 additions & 7 deletions src/faas/v0/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export abstract class TriggerContext<
*
* @returns undefined
*/
public get event(): EventContext | undefined {
public get event(): EventContext<unknown> | undefined {
return undefined;
}

Expand Down Expand Up @@ -89,7 +89,11 @@ export abstract class TriggerContext<
}
}

export abstract class AbstractRequest {
export type JSONTypes = Record<string, any> | Array<any> | string;

export abstract class AbstractRequest<
JSONT extends JSONTypes = Record<string, any>
> {
readonly data: string | Uint8Array;
readonly traceContext: api.Context;

Expand All @@ -107,7 +111,7 @@ export abstract class AbstractRequest {
return stringPayload;
}

json(): Record<string, any> {
json(): JSONT {
// attempt to deserialize as a JSON object
return this.text() ? JSON.parse(this.text()) : {};
}
Expand Down Expand Up @@ -184,7 +188,7 @@ export class HttpResponse {
}
}

export class EventRequest extends AbstractRequest {
export class EventRequest<T> extends AbstractRequest<T> {
public readonly topic: string;

constructor(
Expand Down Expand Up @@ -355,12 +359,17 @@ export class HttpContext extends TriggerContext<HttpRequest, HttpResponse> {
}
}

export class EventContext extends TriggerContext<EventRequest, EventResponse> {
public get event(): EventContext {
export class EventContext<T> extends TriggerContext<
EventRequest<T>,
EventResponse
> {
public get event(): EventContext<T> {
return this;
}

static fromGrpcTriggerRequest(trigger: TriggerRequest): EventContext {
static fromGrpcTriggerRequest(
trigger: TriggerRequest
): EventContext<unknown> {
const topic = trigger.getTopic();
const ctx = new EventContext();

Expand Down
9 changes: 7 additions & 2 deletions src/faas/v0/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NitricEvent } from '../../types';
import { TriggerContext, HttpContext, EventContext } from '.';

export type GenericHandler<Ctx> = (ctx: Ctx) => Promise<Ctx> | Ctx;

export type TriggerHandler = GenericHandler<TriggerContext>;
export type HttpHandler = GenericHandler<HttpContext>;
export type EventHandler = GenericHandler<EventContext>;
export type EventHandler<T extends NitricEvent = NitricEvent> = GenericHandler<
EventContext<T>
>;

export type GenericMiddleware<Ctx> = (
ctx: Ctx,
Expand All @@ -26,7 +29,9 @@ export type GenericMiddleware<Ctx> = (

export type TriggerMiddleware = GenericMiddleware<TriggerContext>;
export type HttpMiddleware = GenericMiddleware<HttpContext>;
export type EventMiddleware = GenericMiddleware<EventContext>;
export type EventMiddleware<T extends NitricEvent = NitricEvent> =
GenericMiddleware<EventContext<T>>;
export type ScheduleMiddleware = GenericMiddleware<EventContext<undefined>>;

/**
* createHandler
Expand Down
7 changes: 4 additions & 3 deletions src/faas/v0/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
EventMiddleware,
GenericMiddleware,
HttpMiddleware,
ScheduleMiddleware,
TriggerContext,
TriggerMiddleware,
} from '.';
Expand Down Expand Up @@ -64,7 +65,7 @@ type FaasClientOptions =
*/
export class Faas {
private httpHandler?: HttpMiddleware;
private eventHandler?: EventMiddleware;
private eventHandler?: EventMiddleware | ScheduleMiddleware;
private anyHandler?: TriggerMiddleware;
private readonly options: FaasClientOptions;

Expand All @@ -78,8 +79,8 @@ export class Faas {
* @param handlers the functions to call to respond to events
* @returns self
*/
event(...handlers: EventMiddleware[]): Faas {
this.eventHandler = createHandler(...handlers);
event(...handlers: EventMiddleware[] | ScheduleMiddleware[]): Faas {
this.eventHandler = createHandler<any>(...handlers);
return this;
}

Expand Down
1 change: 1 addition & 0 deletions src/faas/v0/traceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-node';
/**
* Creates a new node tracer provider
* If it is a local run, it will output to the console. If it is run on the cloud it will output to localhost:4317
*
* @returns a tracer provider
*/
const newTracerProvider = (): NodeTracerProvider => {
Expand Down
10 changes: 5 additions & 5 deletions src/resources/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { EventMiddleware, Faas } from '../faas';
import { EventMiddleware, Faas, ScheduleMiddleware } from '../faas';

type Frequency = 'days' | 'hours' | 'minutes';

Expand Down Expand Up @@ -48,7 +48,7 @@ class Rate {
public readonly schedule: Schedule;
private readonly faas: Faas;

constructor(schedule: Schedule, rate: string, ...mw: EventMiddleware[]) {
constructor(schedule: Schedule, rate: string, ...mw: ScheduleMiddleware[]) {
const [, frequency] = rate.split(' ');
const normalizedFrequency = frequency.toLocaleLowerCase() as Frequency;

Expand Down Expand Up @@ -90,7 +90,7 @@ class Cron {
public readonly schedule: Schedule;
private readonly faas: Faas;

constructor(schedule: Schedule, cron: string, ...mw: EventMiddleware[]) {
constructor(schedule: Schedule, cron: string, ...mw: ScheduleMiddleware[]) {
this.schedule = schedule;
this.faas = new Faas(new CronWorkerOptions(schedule['description'], cron));
this.faas.event(...mw);
Expand Down Expand Up @@ -118,7 +118,7 @@ class Schedule {
* @param mw the handler/middleware to run on a schedule
* @returns A promise that resolves when the schedule worker stops running.
*/
every = (rate: string, ...mw: EventMiddleware[]): Promise<void> => {
every = (rate: string, ...mw: ScheduleMiddleware[]): Promise<void> => {
// handle singular frequencies. e.g. schedule('something').every('day')
if (FREQUENCIES.indexOf(`${rate}s` as Frequency) !== -1) {
rate = `1 ${rate}s`; // 'day' becomes '1 days'
Expand All @@ -129,7 +129,7 @@ class Schedule {
return r['start']();
};

cron = (expression: string, ...mw: EventMiddleware[]): Promise<void> => {
cron = (expression: string, ...mw: ScheduleMiddleware[]): Promise<void> => {
const r = new Cron(this, expression, ...mw);
// Start the new cron immediately
return r['start']();
Expand Down
21 changes: 14 additions & 7 deletions src/resources/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '@nitric/api/proto/resource/v1/resource_pb';
import { ActionsList, make, SecureResource } from './common';
import { fromGrpcError } from '../api/errors';
import { NitricEvent } from '../types';

type TopicPermission = 'publishing';

Expand All @@ -37,10 +38,10 @@ export class SubscriptionWorkerOptions {
/**
* Creates a subscription worker
*/
class Subscription {
class Subscription<T extends NitricEvent = NitricEvent> {
private readonly faas: Faas;

constructor(name: string, ...mw: EventMiddleware[]) {
constructor(name: string, ...mw: EventMiddleware<T>[]) {
this.faas = new Faas(new SubscriptionWorkerOptions(name));
this.faas.event(...mw);
}
Expand All @@ -53,7 +54,9 @@ class Subscription {
/**
* Topic resource for pub/sub async messaging.
*/
export class TopicResource extends SecureResource<TopicPermission> {
export class TopicResource<
T extends NitricEvent = NitricEvent
> extends SecureResource<TopicPermission> {
/**
* Register this topic as a required resource for the calling function/container
*
Expand Down Expand Up @@ -105,8 +108,8 @@ export class TopicResource extends SecureResource<TopicPermission> {
* @param mw handler middleware which will be run for every incoming event
* @returns Promise which resolves when the handler server terminates
*/
subscribe(...mw: EventMiddleware[]): Promise<void> {
const sub = new Subscription(this.name, ...mw);
subscribe(...mw: EventMiddleware<T>[]): Promise<void> {
const sub = new Subscription<T>(this.name, ...mw);
return sub['start']();
}

Expand All @@ -133,10 +136,14 @@ export class TopicResource extends SecureResource<TopicPermission> {
* @param perms the required permission set
* @returns a usable topic reference
*/
public for<T>(...perms: TopicPermission[]): Topic {
public for(...perms: TopicPermission[]): Topic<T> {
this.registerPolicy(...perms);
return events().topic(this.name);
}
}

export const topic = make(TopicResource);
export const topic = make(TopicResource) as <
T extends Record<string, any> = Record<string, any>
>(
name: string
) => TopicResource<NitricEvent<T>>;
8 changes: 5 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export interface NitricEvent {
export interface NitricEvent<
T extends Record<string, any> = Record<string, any>
> {
/**
* Uniquely identifies the event.
*
Expand All @@ -28,10 +30,10 @@ export interface NitricEvent {
/**
* The event's payload data, with details of the event.
*/
payload: Record<string, any>;
payload: T;
}

export interface Task {
export interface Task<T extends Record<string, any> = Record<string, any>> {
/**
* Uniquely identifies the task.
*
Expand Down

0 comments on commit 04df14c

Please sign in to comment.