From 5179d87cda3667fa28913e7081372f342b7a2d02 Mon Sep 17 00:00:00 2001 From: Niki Wix Skaarup Date: Sat, 12 Apr 2025 20:37:40 +0200 Subject: [PATCH] restructured and simplified favicon --- .editorconfig | 17 +--- bunfig.toml | 2 + image-plugin.ts | 30 +++++++ package.json | 6 +- src/client/.gitkeep | 0 src/images.d.ts | 25 ++++++ src/index.css | 23 ----- src/index.d.ts | 9 -- src/index.html | 21 ----- src/index.ts | 158 +++++++++++++++++++++++++++++++++ src/routes/index.css | 23 +++++ src/routes/index.html | 21 +++++ src/server.ts | 157 -------------------------------- src/{ => server}/db/index.ts | 26 +++--- src/{ => server}/db/migrate.ts | 0 src/{ => server}/db/schema.ts | 0 src/server/index.d.ts | 9 ++ src/server/wrapped-timer.ts | 78 ++++++++++++++++ src/wrapped-timer.ts | 78 ---------------- tsconfig.json | 38 +++++--- 20 files changed, 388 insertions(+), 333 deletions(-) create mode 100644 image-plugin.ts create mode 100644 src/client/.gitkeep create mode 100644 src/images.d.ts delete mode 100644 src/index.css delete mode 100644 src/index.d.ts delete mode 100644 src/index.html create mode 100644 src/index.ts create mode 100644 src/routes/index.css create mode 100644 src/routes/index.html delete mode 100644 src/server.ts rename src/{ => server}/db/index.ts (61%) rename src/{ => server}/db/migrate.ts (100%) rename src/{ => server}/db/schema.ts (100%) create mode 100644 src/server/index.d.ts create mode 100644 src/server/wrapped-timer.ts delete mode 100644 src/wrapped-timer.ts diff --git a/.editorconfig b/.editorconfig index fcc0e13..afc63ba 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,26 +5,11 @@ root = true [*] indent_style = space -indent_size = 4 +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] -indent_size = 2 trim_trailing_whitespace = false - -[*.js] -indent_style = tab - -[*.ts] -indent_style = tab - -[*.yml] -indent_style = space -indent_size = 2 - -[*.json] -indent_style = space -indent_size = 2 diff --git a/bunfig.toml b/bunfig.toml index 331faab..0a06033 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,3 +1,5 @@ +preload = ["./image-plugin.ts"] + [install.scopes] "@jsr" = "https://npm.jsr.io" diff --git a/image-plugin.ts b/image-plugin.ts new file mode 100644 index 0000000..9f08d92 --- /dev/null +++ b/image-plugin.ts @@ -0,0 +1,30 @@ +import { plugin, type BunPlugin } from "bun"; +import { readFileSync } from 'fs'; + +const imageLoaderPlugin: BunPlugin = { + name: "image-loader", + setup(builder) { + builder.onLoad({ filter: /\.(png|jpg|jpeg|webp)/ }, async ({ path }) => { + try { + const file = Bun.file(path); + const bytes = await file.bytes(); + return { + exports: { default: bytes }, + loader: "object", + } + } catch (err) { + console.error(err); + if (err instanceof Error) { + throw new Error(`Failed to load file: ${err.message}`); + } else if (typeof err === 'string') { + throw new Error(`Failed to load file: ${err}`); + } + throw new Error("Failed to load file"); + } + }); + } +}; + +plugin(imageLoaderPlugin); + +export default imageLoaderPlugin; diff --git a/package.json b/package.json index 574c2ea..e96da28 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,14 @@ "version": "1.0.50", "private": true, "type": "module", - "module": "src/server.ts", + "module": "src/index.ts", "scripts": { "migrate": "bun --bun run ./src/db/migrate.ts", "generate": "bunx --bun drizzle-kit generate --config=./drizzle.config.ts", "studio": "bunx --bun drizzle-kit studio --config=./drizzle.config.ts", "up": "drizzle-kit up --config=./drizzle.config.ts", - "start": "bun migrate && bun src/server.ts", - "dev": "bun run --watch src/server.ts", + "start": "bun migrate && bun src/index.ts", + "dev": "bun run --watch src/index.ts", "lint": "oxlint .", "format": "prettier --write ." }, diff --git a/src/client/.gitkeep b/src/client/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/images.d.ts b/src/images.d.ts new file mode 100644 index 0000000..a103574 --- /dev/null +++ b/src/images.d.ts @@ -0,0 +1,25 @@ + +declare module '*.png' { + const value: Uint8Array; + // const file: Bun.BunFile; + // export { file }; + export default value; +} +declare module '*.jpeg' { + const value: Uint8Array; + // const file: Bun.BunFile; + // export { file }; + export default value; +} +declare module '*.jpg' { + const value: Uint8Array; + // const file: Bun.BunFile; + // export { file }; + export default value; +} +declare module '*.webp' { + const value: Uint8Array; + // const file: Bun.BunFile; + // export { file }; + export default value; +} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index cbc6c3e..0000000 --- a/src/index.css +++ /dev/null @@ -1,23 +0,0 @@ -@import 'tailwindcss' source('.'); - -@theme { - --breakout-size: calc((var(--breakpoint-xl) - var(--breakpoint-lg)) / 2); - --ultrawide-val: minmax(calc(var(--spacing) * 4), 1fr); - --breakout-val: minmax(0, var(--breakout-size)); - --content-val: min(100% - calc(var(--spacing) * 8), var(--breakpoint-lg)); -} - -@layer base { - * { - min-width: 0; - } - - .content { - grid-template-columns: - [ultrawide-start] var(--ultrawide-val) [breakout-start] var(--breakout-val) [content-start] var(--content-val) [content-end] var(--breakout-val) [breakout-end] var(--ultrawide-val) [ultrawide-end]; - } - - .content>* { - grid-column: content; - } -} diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 8cff349..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Database } from 'bun:sqlite'; -import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'; - -declare global { - var db: Database; - var drizzleDB: BunSQLiteDatabase; - var performanceObserver: PerformanceObserver; - var wrappedTimers: Map; -} diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 19e405d..0000000 --- a/src/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - Progress tracker API - - - - - - - -
-

progress tracker api

-
- - diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4605394 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,158 @@ +import { env } from "bun"; +import homepage from '@routes/index.html'; +import robotsTxt from '@static/robots.txt'; +import sitemapTxt from '@static/sitemap.txt'; +import favicon from '@static/favicon.png'; +import { entry } from "@server/db/schema"; +import { desc, eq } from "drizzle-orm"; +import { drizzleDB } from "@server/db"; + +const development = env.NODE_ENV !== 'production'; + +const faviconInit = { headers: new Headers({ 'Content-Type': 'image/png' }) }; + +console.log('woo'); +Bun.serve({ + routes: { + '/': homepage, + '/robots.txt': new Response(robotsTxt, { + headers: { 'Content-Type': 'text/plain' }, + }), + '/sitemap.txt': new Response(sitemapTxt, { + headers: { 'Content-Type': 'text/plain' }, + }), + '/favicon.ico': new Response(favicon, faviconInit), + '/health': new Response('OK'), + '/api/entries': { + async GET(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const entries = await drizzleDB.select({ id: entry.id, name: entry.name, href: entry.href, finished: entry.finished, updated_at: entry.updatedAt }).from(entry).orderBy(desc(entry.updatedAt)); + + return new Response(JSON.stringify(entries), { + headers: { 'Content-Type': 'application/json' } + }); + }, + async PUT(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const body = await getEntryFromReq(req); + if (!body) return new Response('Invalid data', { status: 400 }); + + const result = await drizzleDB.select({ id: entry.id }).from(entry).where(eq(entry.name, body.name)); + + let created = true; + + if (result.length === 0) { + await drizzleDB.insert(entry).values(body).execute(); + } else if (result.length === 1) { + const row = result[0] as NonNullable; + created = false; + await drizzleDB.update(entry).set(body).where(eq(entry.id, row.id)).execute(); + } else { + return new Response('Invalid data, multiple matches?', { status: 400 }); + } + + return new Response(JSON.stringify({ created }), { + headers: { 'Content-Type': 'application/json' } + }); + }, + }, + '/api/entries/:id': { + async GET(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + + const result = await drizzleDB.select().from(entry).where(eq(entry.id, id)); + + return new Response(JSON.stringify(result), { + headers: { 'Content-Type': 'application/json' } + }); + }, + async PUT(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + + const body = await getEntryFromReq(req); + if (!body) return new Response('Invalid data', { status: 400 }); + + await drizzleDB.update(entry).set(body).where(eq(entry.id, id)).execute(); + + let created = false; + return new Response(JSON.stringify({ created: false }), { + headers: { 'Content-Type': 'application/json' } + }); + }, + async DELETE(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + + await drizzleDB.delete(entry).where(eq(entry.id, id)).execute(); + + return new Response('OK'); + }, + }, + '/api/entries/:id/check/:finished': { + async PUT(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + const finished = req.params.finished === 'true'; + + await drizzleDB + .update(entry) + .set({ finished: finished }) + .where(eq(entry.id, id)) + .execute(); + + return new Response('OK'); + }, + }, + }, + development, + reusePort: true, + port: env.PORT || 3000, + // async fetch(req, server) { + // console.log(req); + // return new Response("Not found", { status: 404 }); + // }, +}); + +console.log('Server started on port:', env.PORT ? Number.parseInt(env.PORT, 10) : 3000); + +async function getEntryFromReq(req: Request) { + const json = await req.json(); + const body = json as { name: string, href: string } + if (!body.name || !body.href || typeof body.name !== 'string' || typeof body.href !== 'string') { + return null; + } + + // Trim and lowercase the name to ensure consistency and uniqueness + body.name = body.name.trim().toLocaleLowerCase(); + + return body; +} + +function isAuthenticated(req: Request) { + const auth = req.headers.get('authorization'); + if (!auth) return false; + const [type, bearer] = auth.split(' '); + if (type !== 'Bearer') return false; + // Check if the token is valid + return bearer === env.BEARER_TOKEN; +} + +const unauthorizedHeaders = new Headers({ + 'WWW-Authenticate': `Bearer realm='sign', error="invalid_request"` +}); + +function unauthorizedResp() { + return new Response('Unauthorized', { status: 401, headers: unauthorizedHeaders }); +} diff --git a/src/routes/index.css b/src/routes/index.css new file mode 100644 index 0000000..2782dbf --- /dev/null +++ b/src/routes/index.css @@ -0,0 +1,23 @@ +@import 'tailwindcss' source('../'); + +@theme { + --breakout-size: calc((var(--breakpoint-xl) - var(--breakpoint-lg)) / 2); + --ultrawide-val: minmax(calc(var(--spacing) * 4), 1fr); + --breakout-val: minmax(0, var(--breakout-size)); + --content-val: min(100% - calc(var(--spacing) * 8), var(--breakpoint-lg)); +} + +@layer base { + * { + min-width: 0; + } + + .content { + grid-template-columns: + [ultrawide-start] var(--ultrawide-val) [breakout-start] var(--breakout-val) [content-start] var(--content-val) [content-end] var(--breakout-val) [breakout-end] var(--ultrawide-val) [ultrawide-end]; + } + + .content>* { + grid-column: content; + } +} diff --git a/src/routes/index.html b/src/routes/index.html new file mode 100644 index 0000000..f08af11 --- /dev/null +++ b/src/routes/index.html @@ -0,0 +1,21 @@ + + + + Progress tracker API + + + + + + + +
+

progress tracker api

+
+ + diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 31004cd..0000000 --- a/src/server.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { env } from "bun"; -import homepage from './index.html'; -import robotsTxt from './static/robots.txt'; -import sitemapTxt from './static/sitemap.txt'; -// @ts-ignore ts2307 -import icon from './static/favicon.png' with { type: 'file' }; -import { entry } from "./db/schema"; -import { desc, eq } from "drizzle-orm"; -import { drizzleDB } from "./db"; -const favicon = await Bun.file(icon).bytes(); - -const development = env.NODE_ENV !== 'production'; - -Bun.serve({ - routes: { - '/': homepage, - '/robots.txt': new Response(robotsTxt, { - headers: { 'Content-Type': 'text/plain' }, - }), - '/sitemap.txt': new Response(sitemapTxt, { - headers: { 'Content-Type': 'text/plain' }, - }), - '/favicon.ico': new Response(favicon, { - headers: { 'Content-Type': 'image/png' }, - }), - '/health': new Response('OK'), - '/api/entries': { - async GET(req) { - if (!isAuthenticated(req)) return unauthorizedResp(); - - const entries = await drizzleDB.select({ id: entry.id, name: entry.name, href: entry.href, finished: entry.finished, updated_at: entry.updatedAt }).from(entry).orderBy(desc(entry.updatedAt)); - - return new Response(JSON.stringify(entries), { - headers: { 'Content-Type': 'application/json' } - }); - }, - async PUT(req) { - if (!isAuthenticated(req)) return unauthorizedResp(); - - const body = await getEntryFromReq(req); - if (!body) return new Response('Invalid data', { status: 400 }); - - const result = await drizzleDB.select({ id: entry.id }).from(entry).where(eq(entry.name, body.name)); - - let created = true; - - if (result.length === 0) { - await drizzleDB.insert(entry).values(body).execute(); - } else if (result.length === 1) { - const row = result[0]; - created = false; - await drizzleDB.update(entry).set(body).where(eq(entry.id, row.id)).execute(); - } else { - return new Response('Invalid data, multiple matches?', { status: 400 }); - } - - return new Response(JSON.stringify({ created }), { - headers: { 'Content-Type': 'application/json' } - }); - }, - }, - '/api/entries/:id': { - async GET(req) { - if (!isAuthenticated(req)) return unauthorizedResp(); - - const id = Number.parseInt(req.params.id, 10); - if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); - - const result = await drizzleDB.select().from(entry).where(eq(entry.id, id)); - - return new Response(JSON.stringify(result), { - headers: { 'Content-Type': 'application/json' } - }); - }, - async PUT(req) { - if (!isAuthenticated(req)) return unauthorizedResp(); - - const id = Number.parseInt(req.params.id, 10); - if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); - - const body = await getEntryFromReq(req); - if (!body) return new Response('Invalid data', { status: 400 }); - - await drizzleDB.update(entry).set(body).where(eq(entry.id, id)).execute(); - - let created = false; - return new Response(JSON.stringify({ created: false }), { - headers: { 'Content-Type': 'application/json' } - }); - }, - async DELETE(req) { - if (!isAuthenticated(req)) return unauthorizedResp(); - - const id = Number.parseInt(req.params.id, 10); - if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); - - await drizzleDB.delete(entry).where(eq(entry.id, id)).execute(); - - return new Response('OK'); - }, - }, - '/api/entries/:id/check/:finished': { - async PUT(req) { - if (!isAuthenticated(req)) return unauthorizedResp(); - - const id = Number.parseInt(req.params.id, 10); - if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); - const finished = req.params.finished === 'true'; - - await drizzleDB - .update(entry) - .set({ finished: finished }) - .where(eq(entry.id, id)) - .execute(); - - return new Response('OK'); - }, - }, - }, - development, - reusePort: true, - port: env.PORT || 3000, - // async fetch(req, server) { - // console.log(req); - // return new Response("Not found", { status: 404 }); - // }, -}); - -async function getEntryFromReq(req: Request) { - const json = await req.json(); - const body = json as { name: string, href: string } - if (!body.name || !body.href || typeof body.name !== 'string' || typeof body.href !== 'string') { - return null; - } - - // Trim and lowercase the name to ensure consistency and uniqueness - body.name = body.name.trim().toLocaleLowerCase(); - - return body; -} - -function isAuthenticated(req: Request) { - const auth = req.headers.get('authorization'); - if (!auth) return false; - const [type, bearer] = auth.split(' '); - if (type !== 'Bearer') return false; - // Check if the token is valid - return bearer === env.BEARER_TOKEN; -} - -const unauthorizedHeaders = new Headers({ - 'WWW-Authenticate': `Bearer realm='sign', error="invalid_request"` -}); - -function unauthorizedResp() { - return new Response('Unauthorized', { status: 401, headers: unauthorizedHeaders }); -} diff --git a/src/db/index.ts b/src/server/db/index.ts similarity index 61% rename from src/db/index.ts rename to src/server/db/index.ts index 4446d65..16b45b5 100644 --- a/src/db/index.ts +++ b/src/server/db/index.ts @@ -2,33 +2,33 @@ import { Database } from 'bun:sqlite'; import { env } from 'bun'; import { drizzle } from 'drizzle-orm/bun-sqlite'; // import { migrate } from 'drizzle-orm/bun-sqlite/migrator'; -import { createWrappedTimer } from '../wrapped-timer'; +import { createWrappedTimer } from '@server/wrapped-timer'; function initDb() { - // global.db.exec('PRAGMA journal_mode = delete'); - global.db.exec('PRAGMA journal_mode = WAL'); - global.db.exec('PRAGMA synchronous = NORMAL'); - global.db.exec('PRAGMA auto_vacuum = INCREMENTAL'); - global.db.exec('PRAGMA wal_autocheckpoint = 1000'); + // global.db.exec('PRAGMA journal_mode = delete'); + global.db.exec('PRAGMA journal_mode = WAL'); + global.db.exec('PRAGMA synchronous = NORMAL'); + global.db.exec('PRAGMA auto_vacuum = INCREMENTAL'); + global.db.exec('PRAGMA wal_autocheckpoint = 1000'); } function incrementalVacuumDb() { - global.db.exec('PRAGMA incremental_vacuum'); + global.db.exec('PRAGMA incremental_vacuum'); } function optimizeDb() { - global.db.exec('PRAGMA optimize'); + global.db.exec('PRAGMA optimize'); } function vacuumDb() { - global.db.exec('vacuum'); + global.db.exec('vacuum'); } if (global.db === undefined || global.drizzleDB === undefined) { - global.db = new Database(`${env.SQLITE_DB_PATH}/${env.SQLITE_DB_NAME}`, { create: true, strict: true }); - initDb(); - global.drizzleDB = drizzle(global.db); - // migrate(global.drizzleDB, { migrationsFolder: './drizzle' }); + global.db = new Database(`${env.SQLITE_DB_PATH}/${env.SQLITE_DB_NAME}`, { create: true, strict: true }); + initDb(); + global.drizzleDB = drizzle(global.db); + // migrate(global.drizzleDB, { migrationsFolder: './drizzle' }); } const incrementalVacuumInterval = 1000 * 30; // 30 seconds diff --git a/src/db/migrate.ts b/src/server/db/migrate.ts similarity index 100% rename from src/db/migrate.ts rename to src/server/db/migrate.ts diff --git a/src/db/schema.ts b/src/server/db/schema.ts similarity index 100% rename from src/db/schema.ts rename to src/server/db/schema.ts diff --git a/src/server/index.d.ts b/src/server/index.d.ts new file mode 100644 index 0000000..000b7bc --- /dev/null +++ b/src/server/index.d.ts @@ -0,0 +1,9 @@ +import type { Database } from 'bun:sqlite'; +import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'; + +declare global { + var db: Database; + var drizzleDB: BunSQLiteDatabase; + var performanceObserver: PerformanceObserver; + var wrappedTimers: Map; +} diff --git a/src/server/wrapped-timer.ts b/src/server/wrapped-timer.ts new file mode 100644 index 0000000..56bfb37 --- /dev/null +++ b/src/server/wrapped-timer.ts @@ -0,0 +1,78 @@ +if (global.wrappedTimers === undefined) { + global.wrappedTimers = new Map(); +} + +export type WrappedTimer = { timer: Timer | undefined; running: boolean; }; +export type WrappedTimerResult = { callback: ReturnType; wrappedTimer: WrappedTimer; }; + +/** + * Create a callback handler for a wrapped timer that prevents the callback from running if it is already running + * and prevents the callback from running if it is already running. + * @template TArgs + * @param {string} key unique identifier for the timer + * @param {(...args: Array) => (Promise | void)} callback function to run + * @param {Array} args arguments to pass to the callback + * @returns {() => Promise} + */ +function getCallbackHandler(key: string, callback: (...args: Array) => (Promise | void), ...args: Array): () => Promise { + return async () => { + const thisTimer = global.wrappedTimers.get(key); + if (thisTimer === undefined) { + console.debug(`Wrapped timer ${key} does not exist`); + return; + } + if (thisTimer.running) { + console.debug(`Wrapped timer ${key} is already running`); + return; + } + + try { + thisTimer.running = true; + await callback(...args); + } catch (e) { + console.error(e); + } finally { + thisTimer.running = false; + } + }; +} + +/** + * Create a wrapped timer aka interval + * @template TArgs + * @param {string} key unique identifier for the timer + * @param {number} interval in milliseconds + * @param {(...args: Array) => (Promise | void)} callback function to run + * @param {Array} args arguments to pass to the callback + * @returns {WrappedTimerResult} + */ +export function createWrappedTimer(key: string, callback: (...args: Array) => (Promise | void), interval: number, ...args: Array): WrappedTimerResult { + const thisTimer = global.wrappedTimers.get(key); + const handler = getCallbackHandler(key, callback, ...args); + + if (thisTimer !== undefined) { + console.debug(`Wrapped timer ${key} already exists, clearing timer`); + clearInterval(thisTimer.timer); + console.debug(`Wrapped timer ${key} set with interval ${interval}ms`); + thisTimer.timer = setInterval(handler, interval); + + return { + callback: handler, + wrappedTimer: thisTimer, + }; + } + + console.debug(`Wrapped timer ${key} created with interval ${interval}ms`); + + const wrappedTimer: WrappedTimer = { + timer: setInterval(handler, interval), + running: false, + }; + + global.wrappedTimers.set(key, wrappedTimer); + + return { + callback: handler, + wrappedTimer, + }; +} diff --git a/src/wrapped-timer.ts b/src/wrapped-timer.ts deleted file mode 100644 index c9b6a03..0000000 --- a/src/wrapped-timer.ts +++ /dev/null @@ -1,78 +0,0 @@ -if (global.wrappedTimers === undefined) { - global.wrappedTimers = new Map(); -} - -type WrappedTimer = { timer: Timer | undefined; running: boolean; }; -type WrappedTimerResult = { callback: ReturnType; wrappedTimer: WrappedTimer; }; - -/** - * Create a callback handler for a wrapped timer that prevents the callback from running if it is already running - * and prevents the callback from running if it is already running. - * @template TArgs - * @param {string} key unique identifier for the timer - * @param {(...args: Array) => (Promise | void)} callback function to run - * @param {Array} args arguments to pass to the callback - * @returns {() => Promise} - */ -function getCallbackHandler(key: string, callback: (...args: Array) => (Promise | void), ...args: Array): () => Promise { - return async () => { - const thisTimer = global.wrappedTimers.get(key); - if (thisTimer === undefined) { - console.debug(`Wrapped timer ${key} does not exist`); - return; - } - if (thisTimer.running) { - console.debug(`Wrapped timer ${key} is already running`); - return; - } - - try { - thisTimer.running = true; - await callback(...args); - } catch (e) { - console.error(e); - } finally { - thisTimer.running = false; - } - }; -} - -/** - * Create a wrapped timer aka interval - * @template TArgs - * @param {string} key unique identifier for the timer - * @param {number} interval in milliseconds - * @param {(...args: Array) => (Promise | void)} callback function to run - * @param {Array} args arguments to pass to the callback - * @returns {WrappedTimerResult} - */ -export function createWrappedTimer(key: string, callback: (...args: Array) => (Promise | void), interval: number, ...args: Array): WrappedTimerResult { - const thisTimer = global.wrappedTimers.get(key); - const handler = getCallbackHandler(key, callback, ...args); - - if (thisTimer !== undefined) { - console.debug(`Wrapped timer ${key} already exists, clearing timer`); - clearInterval(thisTimer.timer); - console.debug(`Wrapped timer ${key} set with interval ${interval}ms`); - thisTimer.timer = setInterval(handler, interval); - - return { - callback: handler, - wrappedTimer: thisTimer, - }; - } - - console.debug(`Wrapped timer ${key} created with interval ${interval}ms`); - - const wrappedTimer: WrappedTimer = { - timer: setInterval(handler, interval), - running: false, - }; - - global.wrappedTimers.set(key, wrappedTimer); - - return { - callback: handler, - wrappedTimer, - }; -} diff --git a/tsconfig.json b/tsconfig.json index d47c860..7e9f16c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,15 @@ { "compilerOptions": { - // Enable latest features - "lib": ["ESNext"], + // Environment setup & latest features + "lib": [ + "ESNext", + "DOM", + ], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", "jsx": "react-jsx", "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "sourceMap": true, // Bundler mode "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -21,13 +19,27 @@ "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in + "noPropertyAccessFromIndexSignature": false, + "paths": { + "@client/*": [ + "./src/client/*" + ], + "@server/*": [ + "./src/server/*" + ], + "@routes/*": [ + "./src/routes/*" + ], + "@static/*": [ + "./src/static/*" + ], + } + }, + "exclude": [ + "node_modules" + ] }