Skip to content
Open
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
13 changes: 0 additions & 13 deletions .eslintrc.js

This file was deleted.

5 changes: 0 additions & 5 deletions .prettierrc.json

This file was deleted.

1 change: 0 additions & 1 deletion .tool-versions

This file was deleted.

66 changes: 33 additions & 33 deletions apps/cdn/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
import { Hono } from "hono";
import { ZoneCache } from "./lib/cache";
import { ABBY_WINDOW_KEY, AbbyDataResponse } from "@tryabby/core";
import { Hono } from 'hono'
import { ZoneCache } from './lib/cache'
import { ABBY_WINDOW_KEY, AbbyDataResponse } from '@tryabby/core'

import { cors } from "hono/cors";
import { timing } from "hono/timing";
import { logger } from "hono/logger";
import { ConfigService } from "./lib/config";
import { cors } from 'hono/cors'
import { timing } from 'hono/timing'
import { logger } from 'hono/logger'
import { ConfigService } from './lib/config'

const cache = new ZoneCache<{
config: AbbyDataResponse;
config: AbbyDataResponse
}>({
cloudflareApiKey: "",
domain: "cache.tryabby.com",
cloudflareApiKey: '',
domain: 'cache.tryabby.com',
fresh: 60 * 1000,
stale: 60 * 1000,
zoneId: "",
});
zoneId: '',
})

const configCache = new ConfigService(cache);
const configCache = new ConfigService(cache)

const app = new Hono()
.use(
"*",
'*',
cors({
origin: "*",
origin: '*',
maxAge: 60 * 60 * 24 * 30,
})
)
.use("*", timing())
.use("*", logger())
.get("/:projectId/:environment", async (c) => {
const environment = c.req.param("environment");
const projectId = c.req.param("projectId");
.use('*', timing())
.use('*', logger())
.get('/:projectId/:environment', async (c) => {
const environment = c.req.param('environment')
const projectId = c.req.param('projectId')

const [data, , reason] = await configCache.retrieveConfig({
c,
environment,
projectId,
});
})

c.header("x-abby-cache", reason);
return c.json(data);
c.header('x-abby-cache', reason)
return c.json(data)
})
.get("/:projectId/:environment/script.js", async (c) => {
const environment = c.req.param("environment");
const projectId = c.req.param("projectId");
.get('/:projectId/:environment/script.js', async (c) => {
const environment = c.req.param('environment')
const projectId = c.req.param('projectId')

const [data, , reason] = await configCache.retrieveConfig({
c,
environment,
projectId,
});
})

c.header("x-abby-cache", reason);
c.header('x-abby-cache', reason)

const script = `window.${ABBY_WINDOW_KEY} = ${JSON.stringify(data)};`;
const script = `window.${ABBY_WINDOW_KEY} = ${JSON.stringify(data)};`

return c.text(script, {
headers: {
"Content-Type": "application/javascript",
'Content-Type': 'application/javascript',
},
});
});
})
})

export default app;
export default app
56 changes: 27 additions & 29 deletions apps/cdn/src/lib/cache.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,63 @@
import type { Context } from "hono";
import type { Context } from 'hono'

export type CacheConfig = {
/**
* How long an entry should be fresh in milliseconds
*/
fresh: number;
fresh: number

/**
* How long an entry should be stale in milliseconds
*
* Stale entries are still valid but should be refreshed in the background
*/
stale: number;
};
stale: number
}

export type Entry<TValue> = {
value: TValue;
};
value: TValue
}

export type ZoneCacheConfig = CacheConfig & {
domain: string;
zoneId: string;
domain: string
zoneId: string
/**
* This token must have at least
*/
cloudflareApiKey: string;
};
cloudflareApiKey: string
}

export class ZoneCache<TNamespaces extends Record<string, unknown>> {
private readonly config: ZoneCacheConfig;
private readonly config: ZoneCacheConfig

constructor(config: ZoneCacheConfig) {
this.config = config;
this.config = config
}

private createCacheKey<TName extends keyof TNamespaces>(
namespace: TName,
key: string,
cacheBuster = "v1"
cacheBuster = 'v1'
): URL {
return new URL(
`https://${this.config.domain}/cache/${cacheBuster}/${String(namespace)}/${key}`
);
return new URL(`https://${this.config.domain}/cache/${cacheBuster}/${String(namespace)}/${key}`)
}

public async get<TName extends keyof TNamespaces>(
c: Context,
namespace: TName,
key: string
): Promise<[TNamespaces[TName] | undefined, "stale" | "hit" | "miss" | "error"]> {
): Promise<[TNamespaces[TName] | undefined, 'stale' | 'hit' | 'miss' | 'error']> {
try {
const res = await caches.default.match(new Request(this.createCacheKey(namespace, key)));
const res = await caches.default.match(new Request(this.createCacheKey(namespace, key)))
if (!res) {
return [undefined, "miss"];
return [undefined, 'miss']
}
const entry = (await res.json()) as Entry<TNamespaces[TName]>;
const entry = (await res.json()) as Entry<TNamespaces[TName]>

return [entry.value, "hit"];
return [entry.value, 'hit']
} catch (e) {
console.error("zone cache error:", e);
return [undefined, "error"];
console.error('zone cache error:', e)
return [undefined, 'error']
}
}

Expand All @@ -71,15 +69,15 @@ export class ZoneCache<TNamespaces extends Record<string, unknown>> {
): Promise<void> {
const entry: Entry<TNamespaces[TName] | null> = {
value: value,
};
const req = new Request(this.createCacheKey(namespace, key));
}
const req = new Request(this.createCacheKey(namespace, key))
const res = new Response(JSON.stringify(entry), {
headers: {
"Content-Type": "application/json",
"Cache-Control": `public, s-maxage=60`,
'Content-Type': 'application/json',
'Cache-Control': `public, s-maxage=60`,
},
});
})

await caches.default.put(req, res);
await caches.default.put(req, res)
}
}
38 changes: 19 additions & 19 deletions apps/cdn/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { AbbyDataResponse, HttpService } from "@tryabby/core";
import { AbbyDataResponse, HttpService } from '@tryabby/core'

import type { ZoneCache } from "./cache";
import { Context } from "hono";
import { endTime, startTime } from "hono/timing";
import type { ZoneCache } from './cache'
import { Context } from 'hono'
import { endTime, startTime } from 'hono/timing'

export class ConfigService {
constructor(
private readonly cache: ZoneCache<{
config: AbbyDataResponse;
config: AbbyDataResponse
}>
) {}

Expand All @@ -16,35 +16,35 @@ export class ConfigService {
projectId,
c,
}: {
projectId: string;
environment: string;
c: Context;
projectId: string
environment: string
c: Context
}) {
const cacheKey = [projectId, environment].join(",");
const cacheKey = [projectId, environment].join(',')

startTime(c, "cacheRead");
const [cachedData, reason] = await this.cache.get(c, "config", cacheKey);
startTime(c, 'cacheRead')
const [cachedData, reason] = await this.cache.get(c, 'config', cacheKey)

endTime(c, "cacheRead");
endTime(c, 'cacheRead')

if (cachedData) {
return [cachedData, true, reason] as const;
return [cachedData, true, reason] as const
}

startTime(c, "remoteRead");
startTime(c, 'remoteRead')

const data = await HttpService.getProjectData({
projectId,
environment,
});
})

if (!data) {
throw new Error("Failed to fetch data");
throw new Error('Failed to fetch data')
}

endTime(c, "remoteRead");
c.executionCtx.waitUntil(this.cache.set(c, "config", cacheKey, data));
endTime(c, 'remoteRead')
c.executionCtx.waitUntil(this.cache.set(c, 'config', cacheKey, data))

return [data, false, reason] as const;
return [data, false, reason] as const
}
}
12 changes: 4 additions & 8 deletions apps/cdn/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"lib": [
"esnext"
],
"types": [
"@cloudflare/workers-types"
],
"lib": ["esnext"],
"types": ["@cloudflare/workers-types"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
},
}
}
}
18 changes: 9 additions & 9 deletions apps/docs/next.config.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
const withNextra = require("nextra")({
theme: "nextra-theme-docs",
themeConfig: "./theme.config.jsx",
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.jsx',
defaultShowCopyCode: true,
});
})

const { withPlausibleProxy } = require("next-plausible");
const { withPlausibleProxy } = require('next-plausible')

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// use this to add <html lang="en"> to all pages
i18n: {
locales: ["en"],
defaultLocale: "en",
locales: ['en'],
defaultLocale: 'en',
},
};
}

module.exports = withPlausibleProxy()(withNextra(nextConfig));
module.exports = withPlausibleProxy()(withNextra(nextConfig))
1 change: 0 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.5",
"eslint": "8.29.0",
"eslint-config-next": "13.0.6",
"next": "14.0.4",
"next-plausible": "^3.11.3",
"nextra": "2.13.2",
Expand Down
14 changes: 7 additions & 7 deletions apps/docs/pages/reference/_meta.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"nextjs": "Next.js",
"react": "React",
"svelte": "Svelte",
"angular": "Angular",
"http": "HTTP API",
"cli": "CLI"
}
"nextjs": "Next.js",
"react": "React",
"svelte": "Svelte",
"angular": "Angular",
"http": "HTTP API",
"cli": "CLI"
}
Loading