app2/CONVENTIONS.md
Use Effect's type-safe alternatives where possible:
// Instead of:
let value: string | null | undefined
// Use:
let value: Option<string>
Never use try {} catch {} blocks. always use Effect. When dealing with unsafe functions from libraries, use Effect.tryPromise(() => somePromiseFn())
Do NOT do Option.isSome(Option.fromNullable(T)) if T is already an Option type
Error handling is what distinguishes production-grade apps from toys.
Be extremely mindful and considerate of how you implement errors.
Always use Effect's Error system. If you have a function that can fail, it must be an Effect.
Create new Tagged Errors and yield them whenever an effect errors.
Never do things like this:
catch: error => (error instanceof Error ? error : new Error("Unknown error"))
Never use the keywords try, catch, and throw.
When you deal with a library function outside of our control, you can use Effect.try to wrap that function into an Effect. If you do this, put the wrapped function as a standalone utility in ts-sdk/. Do not put a big codeblock inside of an Effect.try, it is not meant as a substitute for
try {
// lots of logic
} catch (e) {
// did not see this coming i guess ill just panic bc i have no idea what went wrong here
}
It is very bad to do something like this
const myError; // coming from somewhere
return "My operation went wrong! ${myError.message}"
If you do this, all previously provided details of that error will be gone and you will only see a message. The error object may have many more details. Even worse is this:
const myError; // coming from somewhere
return "my operation went wrong!"
Instead, create a new TaggedError if myError is not already a yieldable tagged effect error. Extract all details of the extracted error, and yield* your new error type. This will make sure that your new error is tracked at the type system. For example:
export class ReadContractError extends Data.TaggedError("ReadContractError")<{
myErrorSpecificDetail: number,
cause: unknown // the unknown error we received
}> {}
then, when you get an error, yield* your new tagged error.
const myError; // coming from somewhere
yield* new ReadContractError({ myErrorSpecificDetails: 4, cause: extractErrorDetails(myError) })
"But I cannot yield* an error in <context in which you encounter an error>!!!"
In that case, your function definition (context) is wrong. If your operation can error, it is an Effect.
As an extension of the previous section, do make sure that the full error object with all of its details is always exposed to the user in the UI. It is not acceptable to leave out any of the details here. We cannot expect a user to open the browser console. You can use <ErrorComponent/> if you don't know how to display an error. Do not only show error.message, show the full error.
extractErrorDetailsWhen wrapping incoming unknown Errors into Data.TaggedErrors, use extractErrorDetails to extract all details from that external error.
/* Instead of: */
bg-gray-100 dark:bg-gray-800
/* Use: */
bg-zinc-100 dark:bg-zinc-800
mx-auto to style. use flexbox instead.flex gap-*, rather than space-x-*components/ui/Card.sveltecomponents/ui/Label.sveltecomponents/ui/Skeleton.sveltecomponents/model/ErrorComponent.sveltecomponents/ui/DateTimeComponent.svelteAll UI components must accept a class prop for styling customization
Use the cn() utility for combining class names
Example pattern:
type Props = HTMLAttributes<HTMLDivElement> & {
children: Snippet
class?: string
}
const { children, class: className = "", ...rest }: Props = $props()
const classes = cn(
"base classes",
className
)
Do not create self-closing div tags <div />, instead do <div></div>
$: statements or $store syntax$state(), $derived(), and $effect() insteadon:input / on:click, use oninput and onclick instead.<div/>). instead do <div></div>Use Svelte 5's prop syntax:
interface Props {
title: string
count: number
}
let { title, count }: Props = $props()
Use Svelte 5's render children statement with Snippet type:
type Props = {
children: Snippet
}
const { children }: Props = $props()
{@render children()}
// Instead of:
$: total = count * 2
let $store
// Use:
let total = $derived(count * 2)
let store = $state(initialValue)
use Array<T> rather than T[]