Skip to content

Commit

Permalink
Service worker fetch server side!
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiancook committed Sep 28, 2023
1 parent af51132 commit b06367f
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 197 deletions.
2 changes: 1 addition & 1 deletion 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.durableEventId) {
if (event.durableEventId || event.virtual) {
const schedule = (await getDurableEvent(event)) ?? (event.virtual ? event : undefined);
if (!isMatchingSchedule(schedule)) {
return;
Expand Down
5 changes: 5 additions & 0 deletions src/events/schedule/event.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {addDurableEvent, deleteDurableEvent, DurableEventData, getDurableEvent} from "../../data";
import {deleteDispatchQStash, dispatchQStash, isQStash} from "./qstash";
import {getConfig} from "../../config";
import {dispatchScheduledDurableEvents} from "./dispatch-scheduled";


export interface DispatchEventConfig {
Expand Down Expand Up @@ -29,6 +30,10 @@ export async function dispatchEvent(event: DurableEventData) {
const config = getConfig();
if (config.dispatchEvent) {
await config.dispatchEvent(durable);
} else if (event.virtual) {
await dispatchScheduledDurableEvents({
event
});
} else if (isQStash()) {
await dispatchQStash(durable);
} else if (DURABLE_EVENTS_IMMEDIATE || durable.schedule?.immediate) {
Expand Down
164 changes: 164 additions & 0 deletions src/fetch/dispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import {defer} from "@virtualstate/promise";
import {isLike, isPromise, isSignalled, ok} from "../is";
import {
DurableBody,
DurableEventData,
DurableRequest,
fromDurableRequest,
fromRequestResponse,
setDurableRequestForEvent
} from "../data";
import {dispatcher} from "../events/schedule/schedule";
import {v4} from "uuid";
import {caches} from "./cache";
import {dispatchEvent} from "../events";
import {getConfig} from "../config";
import {DurableFetchEventCache, DurableFetchEventData, FETCH} from "./events";

export function isDurableFetchEventCache(value: unknown): value is DurableFetchEventCache {
return !!(
isLike<DurableFetchEventCache>(value) &&
typeof value.name === "string"
);
}

export function createRespondWith() {
const { promise: handled, resolve, reject } = defer<Response>();

function respondWith(response: Response | Promise<Response>) {
if (isPromise(response)) {
return response.then(resolve, reject);
}
return resolve(response);
}

return {
handled,
respondWith
}
}

export function createWaitUntil() {
let promises: Promise<unknown>[] = [];

function waitUntil(promise: Promise<unknown>) {
promises.push(
promise.catch(error => void error)
);
}

async function wait(): Promise<void> {
if (!promises.length) {
return;
}
const current = promises;
promises = [];
await Promise.all(current);
return wait();
}

return {
wait,
waitUntil
}
}


function isDurableFetchEventData(event?: DurableEventData): event is DurableFetchEventData {
return !!(
isLike<DurableFetchEventData>(event) &&
event.type === FETCH &&
event.request &&
event.request.url
);
}

export const removeFetchDispatcherFunction = dispatcher(FETCH, async (event, dispatch) => {
const { signal, controller } = getSignal();
ok(isDurableFetchEventData(event));
const {
handled,
respondWith
} = createRespondWith();
const {
wait,
waitUntil
} = createWaitUntil();
const request = await fromDurableRequest(event.request);
try {
await dispatch({
...event,
signal,
request,
handled,
respondWith,
waitUntil
});
const response = await handled;
let durableEventDispatch: DurableEventData;
if (event.dispatch) {
durableEventDispatch = {
durableEventId: v4(),
...event.dispatch
};
}
const isRetain = durableEventDispatch || (event.durableEventId && event.retain !== false);
let body: DurableBody;
const givenCache = typeof event.cache === "string" ? { name: event.cache } : isDurableFetchEventCache(event.cache) ? event.cache : undefined;
const cache = givenCache ?? (isRetain ? { name: FETCH } : undefined);
if (cache) {
const { name, always } = cache;
if (response.ok || always) {
const store = await caches.open(name);
await store.put(request, response);
body = {
type: "cache",
value: name,
url: request.url
};
}
}
let durableRequest: DurableRequest;
if (isRetain) {
const durableRequestData = await fromRequestResponse(request, response, {
body
});
durableRequest = await setDurableRequestForEvent(durableRequestData, durableEventDispatch || event);
if (durableEventDispatch) {
const { response, ...request } = durableRequest;
await dispatchEvent({
...durableEventDispatch,
request,
response
});
}
}
const { response: givenFns } = getConfig();
const responseFns = Array.isArray(givenFns) ? givenFns : (givenFns ? [givenFns] : []);
if (responseFns.length) {
await Promise.all(
responseFns.map(async (fn) => fn(response.clone(), request, durableRequest))
);
}
} catch (error) {
if (!signal.aborted) {
controller?.abort(error);
}
} finally {
if (!signal.aborted) {
controller?.abort();
}
await wait();
}

function getSignal() {
if (isSignalled(event)) {
return { signal: event.signal, controller: undefined } as const;
}
const controller = new AbortController();
return {
signal: controller.signal,
controller
} as const;
}
})
162 changes: 4 additions & 158 deletions src/fetch/events.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import {DurableBody, DurableEventData, DurableRequest, DurableRequestData, setDurableRequestForEvent} from "../data";
import {dispatchEvent, on} from "../events";
import {dispatcher} from "../events/schedule/schedule";
import {defer} from "@virtualstate/promise";
import {isLike, isPromise, isSignalled, ok} from "../is";
import {fromDurableRequest, fromRequestResponse} from "../data";
import {v4} from "uuid";
import {getConfig} from "../config";
import {caches} from "./cache";

const FETCH = "fetch" as const;
import {DurableEventData, DurableRequest, DurableRequestData} from "../data";
import {on} from "../events";
import {isLike, ok} from "../is";
export const FETCH = "fetch" as const;
type ScheduleFetchEventType = typeof FETCH;

export interface FetchEventResponseFn {
Expand Down Expand Up @@ -38,13 +31,6 @@ export interface FetchEvent extends Omit<DurableFetchEventData, "request"> {
waitUntil(promise: Promise<void | unknown>): void;
}

function isDurableFetchEventCache(value: unknown): value is DurableFetchEventCache {
return !!(
isLike<DurableFetchEventCache>(value) &&
typeof value.name === "string"
);
}

function isFetchEvent(event: unknown): event is FetchEvent {
return !!(
isLike<FetchEvent>(event) &&
Expand All @@ -62,143 +48,3 @@ export const removeFetchScheduledFunction = on(FETCH, async (event) => {
)
);
});

function createRespondWith() {
const { promise: handled, resolve, reject } = defer<Response>();

function respondWith(response: Response | Promise<Response>) {
if (isPromise(response)) {
return response.then(resolve, reject);
}
return resolve(response);
}

return {
handled,
respondWith
}
}

export function createWaitUntil() {
let promises: Promise<unknown>[] = [];

function waitUntil(promise: Promise<unknown>) {
promises.push(
promise.catch(error => void error)
);
}

async function wait(): Promise<void> {
if (!promises.length) {
return;
}
const current = promises;
promises = [];
await Promise.all(current);
return wait();
}

return {
wait,
waitUntil
}
}


function isDurableFetchEventData(event?: DurableEventData): event is DurableFetchEventData {
return !!(
isLike<DurableFetchEventData>(event) &&
event.type === FETCH &&
event.request &&
event.request.url
);
}

export const removeFetchDispatcherFunction = dispatcher(FETCH, async (event, dispatch) => {
const { signal, controller } = getSignal();
ok(isDurableFetchEventData(event));
const {
handled,
respondWith
} = createRespondWith();
const {
wait,
waitUntil
} = createWaitUntil();
const request = await fromDurableRequest(event.request);
try {
await dispatch({
...event,
signal,
request,
handled,
respondWith,
waitUntil
});
const response = await handled;
let durableEventDispatch: DurableEventData;
if (event.dispatch) {
durableEventDispatch = {
durableEventId: v4(),
...event.dispatch
};
}
const isRetain = durableEventDispatch || (event.durableEventId && event.retain !== false);
let body: DurableBody;
const cache = isDurableFetchEventCache(event.cache) ? event.cache : (isRetain ? { name: FETCH } : undefined);
if (cache) {
const { name, always } = cache;
if (response.ok || always) {
const store = await caches.open(name);
await store.put(request, response);
body = {
type: "cache",
value: name,
url: request.url
};
}
}
let durableRequest: DurableRequest;
if (isRetain) {
const durableRequestData = await fromRequestResponse(request, response, {
body
});
durableRequest = await setDurableRequestForEvent(durableRequestData, durableEventDispatch || event);
if (durableEventDispatch) {
const { response, ...request } = durableRequest;
await dispatchEvent({
...durableEventDispatch,
request,
response
});
}
}
const { response: givenFns } = getConfig();
const responseFns = Array.isArray(givenFns) ? givenFns : (givenFns ? [givenFns] : []);
if (responseFns.length) {
await Promise.all(
responseFns.map(async (fn) => fn(response.clone(), request, durableRequest))
);
}
} catch (error) {
if (!signal.aborted) {
controller?.abort(error);
}
} finally {
if (!signal.aborted) {
controller?.abort();
}
await wait();
}

function getSignal() {
if (isSignalled(event)) {
return { signal: event.signal, controller: undefined } as const;
}
const controller = new AbortController();
return {
signal: controller.signal,
controller
} as const;
}
})
3 changes: 2 additions & 1 deletion src/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./events";
export * from "./cache";
export * from "./cache";
export * from "./dispatch";
Loading

0 comments on commit b06367f

Please sign in to comment.