restructured and simplified favicon

This commit is contained in:
Niki Wix Skaarup 2025-04-12 20:37:40 +02:00
parent 44d5fafc66
commit 5179d87cda
Signed by: nikiskaarup
GPG key ID: FC2F1B116F6E788C
20 changed files with 388 additions and 333 deletions

View file

@ -5,26 +5,11 @@ root = true
[*] [*]
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.md] [*.md]
indent_size = 2
trim_trailing_whitespace = false 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

View file

@ -1,3 +1,5 @@
preload = ["./image-plugin.ts"]
[install.scopes] [install.scopes]
"@jsr" = "https://npm.jsr.io" "@jsr" = "https://npm.jsr.io"

30
image-plugin.ts Normal file
View file

@ -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;

View file

@ -3,14 +3,14 @@
"version": "1.0.50", "version": "1.0.50",
"private": true, "private": true,
"type": "module", "type": "module",
"module": "src/server.ts", "module": "src/index.ts",
"scripts": { "scripts": {
"migrate": "bun --bun run ./src/db/migrate.ts", "migrate": "bun --bun run ./src/db/migrate.ts",
"generate": "bunx --bun drizzle-kit generate --config=./drizzle.config.ts", "generate": "bunx --bun drizzle-kit generate --config=./drizzle.config.ts",
"studio": "bunx --bun drizzle-kit studio --config=./drizzle.config.ts", "studio": "bunx --bun drizzle-kit studio --config=./drizzle.config.ts",
"up": "drizzle-kit up --config=./drizzle.config.ts", "up": "drizzle-kit up --config=./drizzle.config.ts",
"start": "bun migrate && bun src/server.ts", "start": "bun migrate && bun src/index.ts",
"dev": "bun run --watch src/server.ts", "dev": "bun run --watch src/index.ts",
"lint": "oxlint .", "lint": "oxlint .",
"format": "prettier --write ." "format": "prettier --write ."
}, },

0
src/client/.gitkeep Normal file
View file

25
src/images.d.ts vendored Normal file
View file

@ -0,0 +1,25 @@
declare module '*.png' {
const value: Uint8Array<ArrayBufferLike>;
// const file: Bun.BunFile;
// export { file };
export default value;
}
declare module '*.jpeg' {
const value: Uint8Array<ArrayBufferLike>;
// const file: Bun.BunFile;
// export { file };
export default value;
}
declare module '*.jpg' {
const value: Uint8Array<ArrayBufferLike>;
// const file: Bun.BunFile;
// export { file };
export default value;
}
declare module '*.webp' {
const value: Uint8Array<ArrayBufferLike>;
// const file: Bun.BunFile;
// export { file };
export default value;
}

View file

@ -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;
}
}

9
src/index.d.ts vendored
View file

@ -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<string, import('../wrapped-timer').WrappedTimer>;
}

File diff suppressed because one or more lines are too long

158
src/index.ts Normal file
View file

@ -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<typeof result[number]>;
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 });
}

23
src/routes/index.css Normal file
View file

@ -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;
}
}

21
src/routes/index.html Normal file
View file

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en" class="min-h-dvh">
<head>
<title>Progress tracker API</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="@static/favicon.png"
/>
<link rel="stylesheet" href="./index.css" />
</head>
<body
class="min-h-dvh bg-gray-950 bg-cover bg-fixed bg-center bg-no-repeat font-mono text-pretty text-gray-100 selection:bg-pink-600 selection:text-gray-200"
>
<div class="flex flex-col items-center justify-center min-h-screen">
<h1>progress tracker api</h1>
</div>
</body>
</html>

View file

@ -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 });
}

View file

@ -2,33 +2,33 @@ import { Database } from 'bun:sqlite';
import { env } from 'bun'; import { env } from 'bun';
import { drizzle } from 'drizzle-orm/bun-sqlite'; import { drizzle } from 'drizzle-orm/bun-sqlite';
// import { migrate } from 'drizzle-orm/bun-sqlite/migrator'; // import { migrate } from 'drizzle-orm/bun-sqlite/migrator';
import { createWrappedTimer } from '../wrapped-timer'; import { createWrappedTimer } from '@server/wrapped-timer';
function initDb() { function initDb() {
// global.db.exec('PRAGMA journal_mode = delete'); // global.db.exec('PRAGMA journal_mode = delete');
global.db.exec('PRAGMA journal_mode = WAL'); global.db.exec('PRAGMA journal_mode = WAL');
global.db.exec('PRAGMA synchronous = NORMAL'); global.db.exec('PRAGMA synchronous = NORMAL');
global.db.exec('PRAGMA auto_vacuum = INCREMENTAL'); global.db.exec('PRAGMA auto_vacuum = INCREMENTAL');
global.db.exec('PRAGMA wal_autocheckpoint = 1000'); global.db.exec('PRAGMA wal_autocheckpoint = 1000');
} }
function incrementalVacuumDb() { function incrementalVacuumDb() {
global.db.exec('PRAGMA incremental_vacuum'); global.db.exec('PRAGMA incremental_vacuum');
} }
function optimizeDb() { function optimizeDb() {
global.db.exec('PRAGMA optimize'); global.db.exec('PRAGMA optimize');
} }
function vacuumDb() { function vacuumDb() {
global.db.exec('vacuum'); global.db.exec('vacuum');
} }
if (global.db === undefined || global.drizzleDB === undefined) { if (global.db === undefined || global.drizzleDB === undefined) {
global.db = new Database(`${env.SQLITE_DB_PATH}/${env.SQLITE_DB_NAME}`, { create: true, strict: true }); global.db = new Database(`${env.SQLITE_DB_PATH}/${env.SQLITE_DB_NAME}`, { create: true, strict: true });
initDb(); initDb();
global.drizzleDB = drizzle(global.db); global.drizzleDB = drizzle(global.db);
// migrate(global.drizzleDB, { migrationsFolder: './drizzle' }); // migrate(global.drizzleDB, { migrationsFolder: './drizzle' });
} }
const incrementalVacuumInterval = 1000 * 30; // 30 seconds const incrementalVacuumInterval = 1000 * 30; // 30 seconds

9
src/server/index.d.ts vendored Normal file
View file

@ -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<string, import('@server/wrapped-timer').WrappedTimer>;
}

View file

@ -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<typeof getCallbackHandler>; 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<TArgs>) => (Promise<void> | void)} callback function to run
* @param {Array<TArgs>} args arguments to pass to the callback
* @returns {() => Promise<void>}
*/
function getCallbackHandler<TArgs>(key: string, callback: (...args: Array<TArgs>) => (Promise<void> | void), ...args: Array<TArgs>): () => Promise<void> {
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<TArgs>) => (Promise<void> | void)} callback function to run
* @param {Array<TArgs>} args arguments to pass to the callback
* @returns {WrappedTimerResult}
*/
export function createWrappedTimer<TArgs>(key: string, callback: (...args: Array<TArgs>) => (Promise<void> | void), interval: number, ...args: Array<TArgs>): 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,
};
}

View file

@ -1,78 +0,0 @@
if (global.wrappedTimers === undefined) {
global.wrappedTimers = new Map();
}
type WrappedTimer = { timer: Timer | undefined; running: boolean; };
type WrappedTimerResult = { callback: ReturnType<typeof getCallbackHandler>; 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<TArgs>) => (Promise<void> | void)} callback function to run
* @param {Array<TArgs>} args arguments to pass to the callback
* @returns {() => Promise<void>}
*/
function getCallbackHandler<TArgs>(key: string, callback: (...args: Array<TArgs>) => (Promise<void> | void), ...args: Array<TArgs>): () => Promise<void> {
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<TArgs>) => (Promise<void> | void)} callback function to run
* @param {Array<TArgs>} args arguments to pass to the callback
* @returns {WrappedTimerResult}
*/
export function createWrappedTimer<TArgs>(key: string, callback: (...args: Array<TArgs>) => (Promise<void> | void), interval: number, ...args: Array<TArgs>): 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,
};
}

View file

@ -1,17 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
// Enable latest features // Environment setup & latest features
"lib": ["ESNext"], "lib": [
"ESNext",
"DOM",
],
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleDetection": "force", "moduleDetection": "force",
"jsx": "react-jsx", "jsx": "react-jsx",
"allowJs": true, "allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"sourceMap": true,
// Bundler mode // Bundler mode
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
@ -21,13 +19,27 @@
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default) // Some stricter flags (disabled by default)
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false "noPropertyAccessFromIndexSignature": false,
} "paths": {
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias "@client/*": [
// "./src/client/*"
// 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 "@server/*": [
"./src/server/*"
],
"@routes/*": [
"./src/routes/*"
],
"@static/*": [
"./src/static/*"
],
}
},
"exclude": [
"node_modules"
]
} }