Skip to content

Commit

Permalink
Standard durable requests, #73, #72
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiancook committed Sep 2, 2023
1 parent 95d8b7d commit 4bdfdce
Show file tree
Hide file tree
Showing 26 changed files with 274 additions and 123 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,41 @@ export interface DurableEventTypeData extends UnknownEvent {

export interface DurableEventData extends Record<string, unknown>, DurableEventTypeData, Expiring {
timeStamp?: number;
eventId?: string;
durableEventId?: string;
schedule?: DurableEventSchedule;
retain?: boolean;
virtual?: boolean;
}

export interface DurableEvent extends DurableEventData {
eventId: string;
durableEventId: string
}

export interface DurableRequestData extends Pick<Request, "url" | "method">, Expiring {
headers?: Record<string, string>
body?: string;
}

export interface DurableResponseData extends Pick<Response, "url" | "status" | "statusText"> {
headers?: Record<string, string>
body?: string;
}

export interface DurableRequest extends DurableRequestData {
durableRequestId: string;
response?: DurableResponseData;
createdAt: string;
updatedAt: string;
}

export interface RequestQueryInfo extends DurableRequestData {

}

export type RequestQuery = RequestQueryInfo | RequestInfo | URL

export type PartialDurableRequest = DurableRequestData & Partial<DurableRequest>;

export interface Expiring {
expiresAt?: string;
}
Expand Down
7 changes: 5 additions & 2 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface BackgroundInput extends Record<string, unknown> {
export interface BackgroundQuery extends Record<string, unknown> {
event?: string;
eventId?: string;
durableEventId?: string;
eventTimeStamp?: string | `${number}` | number;
cron?: string;
seed?: string;
Expand Down Expand Up @@ -71,12 +72,14 @@ async function backgroundScheduleWithOptions(input: BackgroundInput) {
} else if (event) {
const {
eventId,
durableEventId: givenDurableEventId,
eventTimeStamp: timeStamp
} = input.query;
if (eventId) {
const durableEventId = givenDurableEventId || eventId;
if (durableEventId) {
options.event = {
type: event,
eventId
durableEventId
}
if (isNumberString(timeStamp)) {
options.event.timeStamp = +timeStamp;
Expand Down
1 change: 1 addition & 0 deletions src/client/interface.readonly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from "../data/authorisation/types"
export * from "../data/authorisation-notification/types"
export * from "../data/change/types"
export * from "../data/durable-event/types"
export * from "../data/durable-request/types"
export * from "../data/expiring/types"
export * from "../data/file/types"
export * from "../data/form-meta/types"
Expand Down
3 changes: 2 additions & 1 deletion src/data/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export * from "./appointment";
export * from "./change";
export * from "./membership";
export * from "./service";
export * from "./durable-event";
export * from "./durable-event";
export * from "./durable-request";
4 changes: 2 additions & 2 deletions src/data/durable-event/add-durable-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {ok} from "../../is";

export async function addDurableEvent(event: DurableEventData) {
const createdAt = new Date().toISOString();
const eventId = event.eventId || v4();
const eventId = event.durableEventId || v4();
const durable: DurableEvent = {
timeStamp: Date.now(),
createdAt,
updatedAt: createdAt,
eventId,
durableEventId: eventId,
...event
};
ok(!durable.virtual, "Cannot store virtual event");
Expand Down
4 changes: 2 additions & 2 deletions src/data/durable-event/delete-durable-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import {DurableEvent, DurableEventData} from "./types";

export function deleteDurableEvent(event: DurableEventData) {
const store = getDurableEventStore(event);
if (!event.eventId) return undefined;
return store.delete(event.eventId);
if (!event.durableEventId) return undefined;
return store.delete(event.durableEventId);
}
4 changes: 2 additions & 2 deletions src/data/durable-event/get-durable-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {DurableEventData} from "./types";
import {getDurableEventStore} from "./store";

export function getDurableEvent(event: DurableEventData) {
if (!event.eventId) return undefined;
if (!event.durableEventId) return undefined;
const store = getDurableEventStore(event);
return store.get(event.eventId);
return store.get(event.durableEventId);
}
4 changes: 2 additions & 2 deletions src/data/durable-event/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ export interface DurableEventTypeData extends UnknownEvent {

export interface DurableEventData extends Record<string, unknown>, DurableEventTypeData, Expiring {
timeStamp?: number;
eventId?: string;
durableEventId?: string;
schedule?: DurableEventSchedule;
retain?: boolean;
virtual?: boolean;
}

export interface DurableEvent extends DurableEventData {
eventId: string;
durableEventId: string
}
6 changes: 6 additions & 0 deletions src/data/durable-request/delete-durable-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {getDurableRequestStore} from "./store";

export function deleteDurableRequest(durableRequestId: string) {
const store = getDurableRequestStore();
return store.delete(durableRequestId);
}
75 changes: 75 additions & 0 deletions src/data/durable-request/from.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {DurableRequest, DurableRequestData, DurableResponseData} from "./types";

export function fromDurableRequest(durableRequest: DurableRequestData, getOrigin?: () => string) {
const { url, method, headers, body } = durableRequest;
return new Request(
new URL(url, getOrigin?.()),
{
method,
headers,
body
}
);
}

export function fromDurableResponse(durableResponse: DurableResponseData) {
const { body, statusText, status, headers } = durableResponse;
return new Response(
body,
{
status,
statusText,
headers
}
);
}

export async function fromRequestResponse(request: Request, response: Response) {
const clonedResponse = response.clone();

const durableResponse: DurableResponseData = {
headers: getResponseHeadersObject(),
status: response.status,
statusText: response.statusText,
// response.url is empty if it was constructed manually
// Should be same value anyway...
url: response.url || request.url,
// TODO investigate non string responses and storage
// we could just use something like
// await save(`fetch/cache/${durableRequestId}`, Buffer.from(await clonedResponse.arrayBuffer()))
body: await clonedResponse.text()
};

const createdAt = new Date().toISOString();
const { method, url } = request;

return {
durableRequestId: `${method}:${url}`,
method,
url,
response: durableResponse,
createdAt,
updatedAt: createdAt
};

function getResponseHeadersObject() {
const headers = new Headers(response.headers);
// Not sure if we ever get this header in node fetch
// https://developer.mozilla.org/en-US/docs/Web/API/Cache#cookies_and_cache_objects
// Maybe these headers were constructed by a user though
headers.delete("Set-Cookie");
return getHeadersObject(headers);
}

}

function getHeadersObject(headers?: Headers) {
const output: Record<string, string> = {};
if (!headers) {
return output;
}
headers.forEach((value, key) => {
output[key] = value;
})
return output;
}
17 changes: 17 additions & 0 deletions src/data/durable-request/get-durable-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {getDurableRequestStore} from "./store";
import {DurableEvent} from "../durable-event";

export function getDurableRequest(durableRequestId: string) {
const store = getDurableRequestStore();
return store.get(durableRequestId);
}

export function getDurableRequestIdForEvent(event: DurableEvent) {
return `${event.type}:request:${event.durableEventId}`;
}

export function getDurableRequestForEvent(event: DurableEvent) {
return getDurableRequest(
getDurableRequestIdForEvent(event)
);
}
7 changes: 7 additions & 0 deletions src/data/durable-request/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./types";
export * from "./store";
export * from "./set-durable-request";
export * from "./delete-durable-request";
export * from "./get-durable-request";
export * from "./list-durable-requests";
export * from "./from";
6 changes: 6 additions & 0 deletions src/data/durable-request/list-durable-requests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {getDurableRequestStore} from "./store";

export function listDurableRequests() {
const store = getDurableRequestStore();
return store.values();
}
27 changes: 27 additions & 0 deletions src/data/durable-request/set-durable-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {v4} from "uuid";
import {DurableRequest, PartialDurableRequest} from "./types";
import {getDurableRequestStore} from "./store";
import {DurableEvent} from "../durable-event";
import {getDurableRequestIdForEvent} from "./get-durable-request";


export async function setDurableRequest(data: PartialDurableRequest) {
const createdAt = new Date().toISOString();
const durableRequestId = data.durableRequestId || v4();
const durableRequest: DurableRequest = {
...data,
createdAt,
updatedAt: createdAt,
durableRequestId,
};
const store = getDurableRequestStore();
await store.set(durableRequestId, durableRequest);
return durableRequest;
}

export function setDurableRequestForEvent(data: PartialDurableRequest, event: DurableEvent) {
return setDurableRequest({
...data,
durableRequestId: getDurableRequestIdForEvent(event)
});
}
16 changes: 16 additions & 0 deletions src/data/durable-request/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {getExpiringStore} from "../expiring-kv";
import {DurableRequest} from "./types";

const STORE_NAME = "request" as const;

export interface DurableRequestStoreOptions {
name?: string;
prefix?: string;
}

export function getDurableRequestStore({ name, prefix }: DurableRequestStoreOptions = {}) {
return getExpiringStore<DurableRequest>(name || STORE_NAME, {
counter: false,
prefix
});
}
15 changes: 13 additions & 2 deletions src/fetch/types.ts → src/data/durable-request/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface DurableRequestData extends Pick<Request, "url" | "method"> {
import {Expiring} from "../expiring";

export interface DurableRequestData extends Pick<Request, "url" | "method">, Expiring {
headers?: Record<string, string>
body?: string;
}
Expand All @@ -12,4 +14,13 @@ export interface DurableRequest extends DurableRequestData {
durableRequestId: string;
response?: DurableResponseData;
createdAt: string;
}
updatedAt: string;
}

export interface RequestQueryInfo extends DurableRequestData {

}

export type RequestQuery = RequestQueryInfo | RequestInfo | URL

export type PartialDurableRequest = DurableRequestData & Partial<DurableRequest>;
4 changes: 2 additions & 2 deletions src/events/dispatch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export async function onDispatchEvent(event: UnknownEvent) {
virtual: true,
};
// If the instance has no id, give it one
if (!dispatching.eventId) {
dispatching.eventId = v4();
if (!dispatching.durableEventId) {
dispatching.durableEventId = v4();
}
await dispatchScheduledDurableEvents({
event: dispatching
Expand Down
4 changes: 2 additions & 2 deletions src/events/schedule/dispatch-scheduled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function dispatchScheduledDurableEvents(options: BackgroundSchedule
}

async function dispatchScheduledEvent(event: DurableEventData) {
if (event.eventId) {
if (event.durableEventId) {
const schedule = (await getDurableEvent(event)) ?? (event.virtual ? event : undefined);
if (!isMatchingSchedule(schedule)) {
return;
Expand All @@ -66,7 +66,7 @@ export async function dispatchScheduledDurableEvents(options: BackgroundSchedule
}

async function dispatchEvent(event: DurableEventData) {
const done = await lock(`dispatchEvent:${event.type}:${event.eventId || "no-event-id"}`);
const done = await lock(`dispatchEvent:${event.type}:${event.durableEventId || "no-event-id"}`);
// TODO detect if this event tries to dispatch again
try {
await dispatchEventToHandlers(event);
Expand Down
4 changes: 2 additions & 2 deletions src/events/schedule/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function dispatchEvent(event: DurableEventData) {
let durable = event;

if (!event.virtual) {
if (!event.eventId || !(await hasDurableEvent(event))) {
if (!event.durableEventId || !(await hasDurableEvent(event))) {
durable = await addDurableEvent(event);
}
}
Expand All @@ -30,7 +30,7 @@ export async function dispatchEvent(event: DurableEventData) {
await background({
query: {
event: durable.type,
eventId: durable.eventId
eventId: durable.durableEventId
},
quiet: true
});
Expand Down
6 changes: 3 additions & 3 deletions src/events/schedule/qstash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ interface ScheduleMeta {


function getMetaStore(event: DurableEventData) {
ok(event.eventId, "Expected eventId");
return getDurableEventStore(event).meta<ScheduleMeta>(event.eventId);
ok(event.durableEventId, "Expected eventId");
return getDurableEventStore(event).meta<ScheduleMeta>(event.durableEventId);
}

export async function dispatchQStash(event: DurableEventData) {
Expand Down Expand Up @@ -84,7 +84,7 @@ export async function dispatchQStash(event: DurableEventData) {
}
const background: BackgroundQuery = {
event: event.type,
eventId: event.eventId,
eventId: event.durableEventId,
eventTimeStamp: event.timeStamp
};
const response = await fetch(
Expand Down
Loading

0 comments on commit 4bdfdce

Please sign in to comment.