Skip to content
Draft
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
6 changes: 4 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"recommendations": [
"svelte.svelte-vscode"
"svelte.svelte-vscode",
"biomejs.biome",
"vitest.explorer"
]
}
}
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "biomejs.biome",
"editor.tabSize": 4
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "biomejs.biome",
"editor.tabSize": 4
},
"[svelte]": {
"editor.defaultFormatter": "biomejs.biome"
},
"typescript.preferences.preferTypeOnlyAutoImports": true,
"js/ts.implicitProjectConfig.target": "ESNext",
"typescript.preferences.importModuleSpecifier": "non-relative",
Expand Down
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ node_modules/
# env
.env.production
.dev.vars
secret.*

# logs
logs/
Expand Down
1 change: 1 addition & 0 deletions backend/.vscode
3 changes: 0 additions & 3 deletions backend/.vscode/extensions.json

This file was deleted.

12 changes: 0 additions & 12 deletions backend/.vscode/settings.json

This file was deleted.

17 changes: 11 additions & 6 deletions backend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ This file provides guidance for AI coding agents working on the backend of this

## Build and Test Commands

- **Run the application**: `pnpm run dev` (from the `backend` directory) or `pnpm run dev:be` (from the root directory)
- **Run the application**:`pnpm run -w dev:be`
- **Run tests**: Currently no tests.
- **Build the application**: `pnpm run build` (from the `backend` directory) or `pnpm run build:be` (from the root directory)
- **Format code**: `pnpm run format` (from the `backend` directory) or `pnpm run format:be` (from the root directory)
- **Build the application**: `pnpm run -w build:be`
- **Format code**: `pnpm run -w format:be`
- **Lint code**: `pnpm run -w lint:be`

## Backend Development Guidelines

Expand All @@ -16,16 +17,20 @@ This file provides guidance for AI coding agents working on the backend of this
- Follow the style of the existing codebase.
- Maintain consistency in indentation, variable names, and function names.
- Use JSDoc for documentation.
- Firing/receiving events MUST be documented with JSDoc!
- Firing/receiving events MUST be documented with JSDoc!
- Don't use `any` type if possible; prefer specific types or generics.
- Use `async/await` for asynchronous code instead of `.then()`. We have ESNext support!
- This is NOT a Node.js project; it uses Web Workers. Use Web APIs instead of Node.js APIs.
- Use libraries instead of reinventing the wheel.
- One exception: If Web APIs are available with the same functionality, prefer them over libraries.
(example: `fetch` instead of `axios` library, `crypto.randomUUID` instead of `uuid` library).
- One exception: If Web APIs are available with the same functionality, prefer them over libraries.
(example: `fetch` instead of `axios` library, `crypto.randomUUID` instead of `uuid` library).
- Use `satisfies` operator to ensure object types instead of type assertions (`as Type`) if possible.

### Developer's Guide

- Make code stateless to reduce trouble with containers, auto-scaling, etc.
- Use `pnpm add` instead of editing `package.json` directly.
- Use `import.meta.env`(injected at build time) or `c.env`(injected at runtime, per HTTP call) for environment variables.

### Documents
- Hono: https://hono.dev/llms.txt
39 changes: 39 additions & 0 deletions backend/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noConstEnum": "off" // We don't need to iterate it
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 4,
"lineWidth": 80,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always"
}
}
}
14 changes: 11 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
"deploy": "wrangler deploy",
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
"postinstall": "pnpm run cf-typegen",
"format": "node --experimental-strip-types ../node_modules/prettier/bin/prettier.cjs --write .",
"auth": "pnpx http-server -p 5000 -c-1 tools/"
"format": "biome format --write .",
"lint": "biome lint .",
"check": "biome check --write .",
"auth": "pnpx http-server -p 5000 -c-1 tools/",
"test": "vitest"
},
"engines": {
"node": ">=20"
Expand All @@ -31,15 +34,20 @@
"@hono/zod-openapi": "^1.1.3",
"@scalar/hono-api-reference": "^0.9.20",
"aws4fetch": "^1.0.20",
"firebase-rest-firestore": "^1.5.0",
"hono": "^4.9.9",
"hono-openapi": "^1.1.0",
"zod": "^4.1.11"
},
"devDependencies": {
"@biomejs/biome": "^2.3.8",
"@cloudflare/vite-plugin": "^1.2.3",
"@cloudflare/vitest-pool-workers": "^0.10.11",
"@vitest/ui": "^3.2.4",
"consola": "^3.4.2",
"vite": "^6.3.5",
"vite": "^7.2.1",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4",
"wrangler": "^4.17.0"
}
}
8 changes: 0 additions & 8 deletions backend/prettier.config.ts

This file was deleted.

71 changes: 60 additions & 11 deletions backend/src/adapters/StorageClientBase.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,53 @@
import { DataType } from "@/schema";
import type { DataType } from "@/schema";

/**
* Pagination options for querying data items.
*/
export type PaginationOptions = {
/**
* Number of items to return per page.
*/
limit: number;

/**
* Token for retrieving the next page of results.
* This is typically returned from a previous query.
*/
pageToken?: string;
};
export const paginationOptionsDefault: PaginationOptions = {
limit: 10,
};
/**
* Result of a paginated query.
*/
export type PaginationResult<T> = {
/**
* Array of items for the current page.
*/
items: T[];

/**
* Token for retrieving the next page of results.
* If null or undefined, there are no more pages.
* It is typically(not nececcary) alphanumeric value.
*/
nextPageToken?: string;

/**
* Total number of items (if available).
* This might not be supported by all implementations.
*/
totalCount?: number;
};

/**
* Client for performing blob storage operations.
*
* Implementations should return a publicly accessible string (e.g. URL or key)
* from upload, and must support reading and deletion of blobs by that string.
*/
export interface BaseBlobStorageClient {
export type BaseBlobStorageClient = {
/**
* Upload a binary buffer to storage.
* @param buffer - The data to upload.
Expand All @@ -15,7 +56,7 @@ export interface BaseBlobStorageClient {
*/
upload(
buffer: ArrayBuffer | Uint8Array,
contentType?: string
contentType?: string,
): Promise<string>;

/**
Expand All @@ -31,13 +72,13 @@ export interface BaseBlobStorageClient {
* @returns A promise that resolves when deletion completes.
*/
delete(url: string): Promise<void>;
}
};
/**
* Client interface for data (domain objects) database operations.
*
* Provides methods to get, query, list, create (put), and delete data items.
*/
export interface BaseDataDBClient {
export type BaseDataDBClient = {
/**
* Get a data item by id.
* @param id - Data id.
Expand All @@ -48,16 +89,24 @@ export interface BaseDataDBClient {
/**
* Query data items by name.
* @param name - Name to query by.
* @returns Array of matching data items.
* @param options [options=options] - Pagination options.
* @returns Paginated result of matching data items.
*/
queryByName(name: string): Promise<DataType[]>;
queryByName(
name: string,
options: PaginationOptions,
): Promise<PaginationResult<DataType>>;

/**
* List data items with optional ordering.
* @param order - Optional ordering hint.
* @returns Array of data items.
* @param options - Pagination options.
* @returns Paginated result of data items.
*/
list(order?: DataListOrder): Promise<DataType[]>;
list(
order?: DataListOrder,
options?: PaginationOptions,
): Promise<PaginationResult<DataType>>;

/**
* Create a new data item.
Expand Down Expand Up @@ -86,14 +135,14 @@ export interface BaseDataDBClient {
* @returns The updated data item.
*/
update(item: Partial<DataType> & { id: DataType["id"] }): Promise<DataType>;
}
};

/**
* Ordering options for listing data items.
*
* @enum {string}
*/
export enum DataListOrder {
export const enum DataListOrder {
/**
* Newest first
*/
Expand Down
29 changes: 17 additions & 12 deletions backend/src/adapters/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
* loading unnecessary code in environments that don't need it.
*/

import { RuntimeSecret, RuntimeVariable } from "@/types";
import {
import type {
BaseBlobStorageClient,
BaseDataDBClient,
} from "@/adapters/StorageClientBase";
import type { RuntimeSecret, RuntimeVariable } from "@/environmentTypes";

/**
* Environment variables required by the database and blob storage clients.
Expand All @@ -23,18 +23,23 @@ let cachedBlobClient: BaseBlobStorageClient | null = null;

/**
* Dynamically imports and returns the DatabaseClient.
* Temporarily using InMemoryBlob as a placeholder.
* Try AzureCosmosDB, then FirebaseFirestore.
* Temporarily using InMemoryBlob as fallback.
* It is created once and cached for subsequent calls.
*/
export async function DataDBClient(env: DBEnv): Promise<BaseDataDBClient> {
if (cachedDBClient) return cachedDBClient;
let module: Promise<{
default: new (env: DBEnv) => BaseDataDBClient;
}>;
if (env.SECRET_AZURE_COSMOSDB_CONNECTION_STRING) {
cachedDBClient = new (await import("./vendor/AzureCosmosDB")).default(
env
);
module = import("./vendor/AzureCosmosDB");
} else if (env.SECRET_FIREBASE_PROJECT_ID) {
module = import("./vendor/FirebaseFirestore");
} else {
cachedDBClient = new (await import("./vendor/InMemoryDB")).default(env);
module = import("./vendor/InMemoryDB");
}
cachedDBClient = new (await module).default(env);
return cachedDBClient;
}

Expand All @@ -45,11 +50,11 @@ export async function DataDBClient(env: DBEnv): Promise<BaseDataDBClient> {
export async function BlobClient(env: DBEnv): Promise<BaseBlobStorageClient> {
if (cachedBlobClient) return cachedBlobClient;
if (env.SECRET_S3_BUCKET_NAME) {
return (cachedBlobClient = new (
cachedBlobClient = new (
await import("./vendor/S3CompatibleBlob")
).default(env));
).default(env);
return cachedBlobClient;
}
return (cachedBlobClient = new (
await import("./vendor/InMemoryBlob")
).default(env));
cachedBlobClient = new (await import("./vendor/InMemoryBlob")).default(env);
return cachedBlobClient;
}
Loading
Loading