Skip to content

Commit

Permalink
migrate to deno kv
Browse files Browse the repository at this point in the history
  • Loading branch information
0f-0b committed Sep 8, 2023
1 parent 649530e commit c9c434e
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"editor.defaultFormatter": "denoland.vscode-deno"
},
"deno.enable": true,
"deno.importMap": "./static/import_map.json"
"deno.importMap": "./static/import_map.json",
"deno.unstable": true
}
39 changes: 16 additions & 23 deletions code.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DAY } from "./deps/std/datetime/constants.ts";
import { z } from "./deps/zod.ts";

import { DBError, gql } from "./db.ts";
import { DBError, gql } from "./fauna.ts";
import { randomString } from "./random.ts";

export const Code = z.strictObject({
Expand All @@ -9,6 +10,7 @@ export const Code = z.strictObject({
});
export type Code = z.infer<typeof Code>;

/** @deprecated */
const doGetCode = gql<{ id: string }>`
query($id: String!) {
code(id: $id) {
Expand All @@ -23,10 +25,14 @@ const doGetCode = gql<{ id: string }>`
};
}>;

export async function getCode(id: string): Promise<Code | null> {
export async function getCode(kv: Deno.Kv, id: string): Promise<Code | null> {
if (!/^[a-z0-9]{8}$/.test(id)) {
return null;
}
const entry = await kv.get(["code", id]) as Deno.KvEntryMaybe<Code>;
if (entry.versionstamp !== null) {
return entry.value;
}
try {
const { code } = await doGetCode({ id });
return code;
Expand All @@ -38,29 +44,16 @@ export async function getCode(id: string): Promise<Code | null> {
}
}

const doPutCode = gql<{ id: string; format: string; input: string }>`
mutation($id: String!, $format: String!, $input: String!) {
createCode(data: { id: $id, format: $format, input: $input }) {
id
}
}
`<{
createCode: {
id: string;
};
}>;

export async function putCode(code: Code): Promise<string> {
export async function putCode(kv: Deno.Kv, code: Code): Promise<string> {
for (;;) {
try {
const id = randomString(8, "abcdefghijklmnopqrstuvwxyz0123456789");
await doPutCode({ id, ...code });
const id = randomString(8, "abcdefghijklmnopqrstuvwxyz0123456789");
const result = await kv.atomic()
.check({ key: ["code", id], versionstamp: null })
.set(["code", id], code /* , { expireIn: 90 * DAY } */)
.set(["expire", Date.now() + 90 * DAY, "code", id], null)
.commit();
if (result.ok) {
return id;
} catch (e) {
if (e instanceof DBError && e.code === "instance not unique") {
continue;
}
throw e;
}
}
}
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dist"
],
"tasks": {
"lint": "deno fmt --check && deno lint && deno check --all main.ts && deno check --all scripts/build.tsx && deno check --all scripts/generate_import_map.ts",
"lint": "deno fmt --check && deno lint && deno check --unstable --all dump_kv.ts && deno check --unstable --all main.ts && deno check --all scripts/build.tsx && deno check --all scripts/generate_import_map.ts",
"static:lint": "cd static && deno check main.tsx && deno check --all sw.ts"
}
}
1 change: 1 addition & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions deps/std/datetime/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "https://deno.land/std@0.201.0/datetime/constants.ts";
11 changes: 11 additions & 0 deletions dump_kv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env -S deno run --unstable

const kv = await Deno.openKv();
try {
const iter = kv.list({ prefix: [] });
for await (const entry of iter) {
console.dir(entry, { depth: Infinity, iterableLimit: Infinity });
}
} finally {
kv.close();
}
1 change: 1 addition & 0 deletions db.ts → fauna.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { dedent } from "./deps/string_dedent.ts";

import { requireEnv } from "./env.ts";

/** @deprecated */
export function gql<T>(
template: { readonly raw: ArrayLike<string> },
): <R>(variables: T) => Promise<R> {
Expand Down
9 changes: 9 additions & 0 deletions interrupt_signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const controller = new AbortController();
Deno.addSignalListener("SIGINT", function abort() {
queueMicrotask(() => {
Deno.addSignalListener("SIGINT", () => Deno.exit(0x82));
Deno.removeSignalListener("SIGINT", abort);
});
controller.abort(new DOMException("Interrupted.", "AbortError"));
});
export const signal = controller.signal;
32 changes: 25 additions & 7 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
#!/usr/bin/env -S deno run --allow-read=dist,index.html,robots.txt --allow-net=0.0.0.0,graphql.fauna.com --allow-env=FAUNA_SECRET
#!/usr/bin/env -S deno run --unstable --allow-read=dist,index.html,robots.txt --allow-net=0.0.0.0,graphql.fauna.com --allow-env=FAUNA_SECRET

import { onError, toStdHandler } from "./handler.ts";
import { handler } from "./server.ts";
import { signal } from "./interrupt_signal.ts";
import { getHandler } from "./server.ts";

const server = Deno.serve({
handler: toStdHandler(handler),
onError,
});
await server.finished;
const kv = await Deno.openKv();
try {
const server = Deno.serve({
signal,
handler: toStdHandler(getHandler(kv)),
onError,
});
await Promise.all([
server.finished,
(async () => {
const iter = kv.list({ prefix: ["expire"], end: ["expire", Date.now()] });
for await (const { key } of iter) {
kv.atomic()
.delete(key)
.delete(key.slice(2))
.commit();
}
})(),
]);
} finally {
kv.close();
}
72 changes: 37 additions & 35 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,43 @@ import {
} from "./handler.ts";
import { decodeURLPathComponents, staticFile } from "./static.ts";

export const handler: RootHandler = logTime(reportHttpErrors(route({
"/": () => staticFile("index.html"),
"/c/:id": () => staticFile("index.html"),
"/robots.txt": () => staticFile("robots.txt"),
"/api/*": route({
"/code": methods({
POST: parseBodyAsJson(Code, async (_, { body: code }) => {
const id = await putCode(code);
return Response.json({ id }, { status: Status.Created });
export function getHandler(kv: Deno.Kv): RootHandler {
return logTime(reportHttpErrors(route({
"/": () => staticFile("index.html"),
"/c/:id": () => staticFile("index.html"),
"/robots.txt": () => staticFile("robots.txt"),
"/api/*": route({
"/code": methods({
POST: parseBodyAsJson(Code, async (_, { body: code }) => {
const id = await putCode(kv, code);
return Response.json({ id }, { status: Status.Created });
}),
}),
}),
"/code/:id": methods({
GET: async (_, { params: { id } }) => {
const code = await getCode(id!) ??
fail(new errors.NotFound("Code not found"));
return Response.json({ code });
},
}),
}, () => fail(new errors.NotFound("Not found"))),
}, async (req) => {
const path = decodeURLPathComponents(new URL(req.url).pathname);
if (path) {
try {
return await staticFile(join("dist", ...path), {
cacheControl: "max-age=2592000, immutable",
});
} catch (e) {
if (
!(e instanceof Deno.errors.NotFound ||
e instanceof Deno.errors.NotADirectory ||
e instanceof Deno.errors.IsADirectory)
) {
throw e;
"/code/:id": methods({
GET: async (_, { params: { id } }) => {
const code = await getCode(kv, id!) ??
fail(new errors.NotFound("Code not found"));
return Response.json({ code });
},
}),
}, () => fail(new errors.NotFound("Not found"))),
}, async (req) => {
const path = decodeURLPathComponents(new URL(req.url).pathname);
if (path) {
try {
return await staticFile(join("dist", ...path), {
cacheControl: "max-age=2592000, immutable",
});
} catch (e) {
if (
!(e instanceof Deno.errors.NotFound ||
e instanceof Deno.errors.NotADirectory ||
e instanceof Deno.errors.IsADirectory)
) {
throw e;
}
}
}
}
return await staticFile("index.html", { status: Status.NotFound });
})));
return await staticFile("index.html", { status: Status.NotFound });
})));
}

0 comments on commit c9c434e

Please sign in to comment.