Compare commits

..

2 Commits

Author SHA1 Message Date
dcabc4fbdb fix: handle provider errors during source config upsert
Wrap feedSourceForUser + addSource/replaceSource in upsertSourceConfig
with try/catch. When credentials are not yet available (e.g. new CalDAV
source before updateSourceCredentials is called), the provider throws
but the config is still persisted. updateSourceCredentials will add the
source to the session later.

Co-authored-by: Ona <no-reply@ona.com>
2026-04-12 11:42:58 +00:00
b5236e0e52 feat: migrate to TypeScript 6 and add tsgo (#114)
* feat: migrate to TypeScript 6 and add tsgo

- Upgrade typescript from ^5 to ^6 across all packages
- Address TS6 breaking changes in tsconfig files:
  - Add explicit types array (new default is [])
  - Remove deprecated baseUrl (paths work without it)
  - Remove redundant esModuleInterop: true
  - Merge DOM.Iterable into DOM lib
- Install @typescript/native-preview for tsgo CLI
- Enable tsgo in VS Code settings

Co-authored-by: Ona <no-reply@ona.com>

* chore: remove redundant tsconfig comments

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
2026-04-12 12:34:02 +01:00
18 changed files with 190 additions and 137 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"js/ts.experimental.useTsgo": true
}

View File

@@ -34,7 +34,7 @@
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"typescript": "~5.9.3",
"typescript": "^6",
"vite": "^7.2.4"
}
}

View File

@@ -3,12 +3,11 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
@@ -16,14 +15,12 @@
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}

View File

@@ -2,7 +2,6 @@
"files": [],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}

View File

@@ -7,14 +7,12 @@
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

View File

@@ -10,11 +10,10 @@ CREDENTIAL_ENCRYPTION_KEY=
# Base URL of the backend
BETTER_AUTH_URL=http://localhost:3000
# Cloudflare Workers AI (LLM feed enhancement)
CF_ACCOUNT_ID=
WORKERS_AI_API_KEY=
# Optional: override the default model (default: @cf/zai-org/glm-4.7-flash)
# WORKERS_AI_MODEL=@cf/zai-org/glm-4.7-flash
# OpenRouter (LLM feed enhancement)
OPENROUTER_API_KEY=
# Optional: override the default model (default: openai/gpt-4.1-mini)
# OPENROUTER_MODEL=openai/gpt-4.1-mini
# Apple WeatherKit credentials
WEATHERKIT_PRIVATE_KEY=

View File

@@ -21,6 +21,7 @@
"@aelis/source-location": "workspace:*",
"@aelis/source-tfl": "workspace:*",
"@aelis/source-weatherkit": "workspace:*",
"@openrouter/sdk": "^0.9.11",
"arktype": "^2.1.29",
"better-auth": "^1",
"drizzle-orm": "^0.45.1",

View File

@@ -1,14 +1,13 @@
import { type } from "arktype"
import { OpenRouter } from "@openrouter/sdk"
import type { EnhancementResult } from "./schema.ts"
import { enhancementResultJsonSchema, parseEnhancementResult } from "./schema.ts"
const DEFAULT_MODEL = "@cf/zai-org/glm-4.7-flash"
const DEFAULT_MODEL = "z-ai/glm-4.7-flash"
const DEFAULT_TIMEOUT_MS = 30_000
export interface LlmClientConfig {
accountId: string
apiKey: string
model?: string
timeoutMs?: number
@@ -23,77 +22,43 @@ export interface LlmClient {
enhance(request: LlmClientRequest): Promise<EnhancementResult | null>
}
const CloudflareApiResponse = type({
result: {
choices: type({
message: {
content: "string",
"role?": "string",
},
}).array(),
},
success: "boolean",
"errors?": type({ message: "string" }).array(),
})
/**
* Creates a reusable LLM client backed by Cloudflare Workers AI.
* Uses the REST API with structured JSON output.
* Creates a reusable LLM client backed by OpenRouter.
* The OpenRouter SDK instance is created once and reused across calls.
*/
export function createLlmClient(config: LlmClientConfig): LlmClient {
const client = new OpenRouter({
apiKey: config.apiKey,
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
})
const model = config.model ?? DEFAULT_MODEL
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS
const baseUrl = `https://api.cloudflare.com/client/v4/accounts/${config.accountId}/ai/run/${model}`
return {
async enhance(request) {
try {
const res = await fetch(baseUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
const response = await client.chat.send({
chatGenerationParams: {
model,
messages: [
{ role: "system", content: request.systemPrompt },
{ role: "user", content: request.userMessage },
{ role: "system" as const, content: request.systemPrompt },
{ role: "user" as const, content: request.userMessage },
],
response_format: {
type: "json_schema",
json_schema: {
responseFormat: {
type: "json_schema" as const,
jsonSchema: {
name: "enhancement_result",
strict: false,
schema: enhancementResultJsonSchema,
},
},
reasoning: { effort: "none" },
stream: false,
}),
// @ts-expect-error — bun-types AbortSignal conflicts with ESNext lib in tsc; works at runtime and in VSCode
signal: AbortSignal.timeout(timeoutMs),
},
})
if (!res.ok) {
const body = await res.text()
console.warn(`[enhancement] Cloudflare API error ${res.status}: ${body}`)
return null
}
const json: unknown = await res.json()
const parsed = CloudflareApiResponse(json)
if (parsed instanceof type.errors) {
console.warn("[enhancement] Unexpected API response shape:", parsed.summary)
return null
}
if (!parsed.success) {
console.warn("[enhancement] Cloudflare API errors:", parsed.errors)
return null
}
const content = parsed.result.choices[0]?.message.content
if (content === undefined) {
console.warn("[enhancement] LLM returned no choices in response")
const message = response.choices?.[0]?.message
const content = message?.content ?? message?.reasoning
if (typeof content !== "string") {
console.warn("[enhancement] LLM returned no content in response")
return null
}
@@ -103,14 +68,6 @@ export function createLlmClient(config: LlmClientConfig): LlmClient {
}
return result
} catch (error) {
if (error instanceof DOMException && error.name === "TimeoutError") {
console.warn("[enhancement] LLM request timed out")
} else {
console.warn("[enhancement] LLM request failed:", error)
}
return null
}
},
}
}

View File

@@ -15,7 +15,8 @@ export type SyntheticItem = typeof SyntheticItem.infer
export type EnhancementResult = typeof EnhancementResult.infer
/**
* JSON Schema passed to Cloudflare Workers AI for structured output.
* JSON Schema passed to OpenRouter's structured output.
* OpenRouter doesn't support arktype, so this is maintained separately.
*
* ⚠️ Must stay in sync with EnhancementResult above.
* If you add/remove fields, update both schemas.

View File

@@ -23,22 +23,17 @@ function main() {
const { db, close: closeDb } = createDatabase(process.env.DATABASE_URL!)
const auth = createAuth(db)
const cfAccountId = process.env.CF_ACCOUNT_ID
const workersAiApiKey = process.env.WORKERS_AI_API_KEY
const feedEnhancer =
cfAccountId && workersAiApiKey
const openrouterApiKey = process.env.OPENROUTER_API_KEY
const feedEnhancer = openrouterApiKey
? createFeedEnhancer({
client: createLlmClient({
accountId: cfAccountId,
apiKey: workersAiApiKey,
model: process.env.WORKERS_AI_MODEL || undefined,
apiKey: openrouterApiKey,
model: process.env.OPENROUTER_MODEL || undefined,
}),
})
: null
if (!feedEnhancer) {
console.warn(
"[enhancement] CF_ACCOUNT_ID/WORKERS_AI_API_KEY not set — feed enhancement disabled",
)
console.warn("[enhancement] OPENROUTER_API_KEY not set — feed enhancement disabled")
}
const credentialEncryptionKey = process.env.CREDENTIAL_ENCRYPTION_KEY

View File

@@ -44,6 +44,15 @@ function getEnabledSourceIds(userId: string): string[] {
*/
let mockFindResult: unknown | undefined
/**
* Spy for `upsertConfig` calls. Tests can inspect calls via
* `mockUpsertConfigCalls`.
*/
const mockUpsertConfigCalls: Array<{
sourceId: string
data: { enabled: boolean; config: unknown }
}> = []
/**
* Spy for `updateCredentials` calls. Tests can inspect calls via
* `mockUpdateCredentialsCalls` or override behavior.
@@ -81,6 +90,9 @@ mock.module("../sources/user-sources.ts", () => ({
updatedAt: now,
}
},
async upsertConfig(sourceId: string, data: { enabled: boolean; config: unknown }) {
mockUpsertConfigCalls.push({ sourceId, data })
},
async updateCredentials(sourceId: string, credentials: Buffer) {
if (mockUpdateCredentialsError) {
throw mockUpdateCredentialsError
@@ -138,6 +150,7 @@ const weatherProvider: FeedSourceProvider = {
beforeEach(() => {
enabledByUser.clear()
mockFindResult = undefined
mockUpsertConfigCalls.length = 0
mockUpdateCredentialsCalls.length = 0
mockUpdateCredentialsError = null
})
@@ -824,3 +837,65 @@ describe("UserSessionManager.updateSourceCredentials", () => {
expect(factory).not.toHaveBeenCalled()
})
})
describe("UserSessionManager.upsertSourceConfig", () => {
test("persists config to DB even when feedSourceForUser throws", async () => {
setEnabledSources(["test"])
let callCount = 0
const factory = mock(async (_userId: string, _config: unknown, _credentials: unknown) => {
callCount++
// Succeed on first call (session creation), throw on second (upsert refresh)
if (callCount > 1) {
throw new InvalidSourceCredentialsError("test", "credentials required")
}
return createStubSource("test")
})
const provider: FeedSourceProvider = { sourceId: "test", feedSourceForUser: factory }
const manager = new UserSessionManager({ db: fakeDb, providers: [provider] })
// Create a session so the session-refresh path is exercised
await manager.getOrCreate("user-1")
const spy = spyOn(console, "warn").mockImplementation(() => {})
// upsertSourceConfig with no existing credentials — provider will throw
await manager.upsertSourceConfig("user-1", "test", {
enabled: true,
config: { url: "https://example.com" },
})
// Config should still have been persisted to DB
expect(mockUpsertConfigCalls).toHaveLength(1)
expect(mockUpsertConfigCalls[0]!.sourceId).toBe("test")
expect(mockUpsertConfigCalls[0]!.data.enabled).toBe(true)
// The error should have been logged, not thrown
expect(spy).toHaveBeenCalled()
spy.mockRestore()
})
test("adds source to session when feedSourceForUser succeeds", async () => {
setEnabledSources(["test"])
const factory = mock(async () => createStubSource("test"))
const provider: FeedSourceProvider = { sourceId: "test", feedSourceForUser: factory }
const manager = new UserSessionManager({ db: fakeDb, providers: [provider] })
const session = await manager.getOrCreate("user-1")
await manager.upsertSourceConfig("user-1", "test", { enabled: true })
// Config persisted
expect(mockUpsertConfigCalls).toHaveLength(1)
// Source should be in the session (feedSourceForUser succeeded)
expect(session.getSource("test")).toBeDefined()
})
test("throws SourceNotFoundError for unknown provider", async () => {
setEnabledSources([])
const manager = new UserSessionManager({ db: fakeDb, providers: [] })
await expect(
manager.upsertSourceConfig("user-1", "unknown", { enabled: true }),
).rejects.toBeInstanceOf(SourceNotFoundError)
})
})

View File

@@ -210,12 +210,23 @@ export class UserSessionManager {
const credentials = existingRow?.credentials
? this.decryptCredentials(existingRow.credentials)
: null
try {
const source = await provider.feedSourceForUser(userId, config, credentials)
if (session.hasSource(sourceId)) {
session.replaceSource(sourceId, source)
} else {
session.addSource(source)
}
} catch (err) {
// Provider may fail when credentials are not yet available (e.g. new
// source added before updateSourceCredentials is called). The config
// is already persisted above; updateSourceCredentials will add the
// source to the session later.
console.warn(
`[UserSessionManager] feedSourceForUser("${sourceId}") failed during upsert for user ${userId}, skipping session update:`,
err,
)
}
}
}
}

View File

@@ -55,6 +55,6 @@
"eas-cli": "^18.0.1",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
"typescript": "~5.9.2"
"typescript": "^6"
}
}

View File

@@ -30,7 +30,7 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.13",
"typescript": "^5.9.2",
"typescript": "^6",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}

View File

@@ -1,18 +1,16 @@
{
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"lib": ["DOM", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,

View File

@@ -7,12 +7,13 @@
"devDependencies": {
"@json-render/core": "^0.12.1",
"@nym.sh/jrx": "^0.2.0",
"@types/bun": "^1.3.12",
"@types/bun": "latest",
"@typescript/native-preview": "^7.0.0-dev.20260412.1",
"oxfmt": "^0.24.0",
"oxlint": "^1.39.0",
},
"peerDependencies": {
"typescript": "^5",
"typescript": "^6",
},
},
"apps/admin-dashboard": {
@@ -41,7 +42,7 @@
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"typescript": "~5.9.3",
"typescript": "^6",
"vite": "^7.2.4",
},
},
@@ -55,6 +56,7 @@
"@aelis/source-location": "workspace:*",
"@aelis/source-tfl": "workspace:*",
"@aelis/source-weatherkit": "workspace:*",
"@openrouter/sdk": "^0.9.11",
"arktype": "^2.1.29",
"better-auth": "^1",
"drizzle-orm": "^0.45.1",
@@ -110,7 +112,7 @@
"eas-cli": "^18.0.1",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
"typescript": "~5.9.2",
"typescript": "^6",
},
},
"apps/waitlist-website": {
@@ -137,7 +139,7 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.13",
"typescript": "^5.9.2",
"typescript": "^6",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4",
},
@@ -767,6 +769,8 @@
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
"@openrouter/sdk": ["@openrouter/sdk@0.9.11", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-BgFu6NcIJO4a9aVjr04y3kZ8pyM71j15I+bzfVAGEvxnj+KQNIkBYQGgwrG3D+aT1QpDKLki8btcQmpaxUas6A=="],
"@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.24.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aYXuGf/yq8nsyEcHindGhiz9I+GEqLkVq8CfPbd+6VE259CpPEH+CaGHEO1j6vIOmNr8KHRq+IAjeRO2uJpb8A=="],
"@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.24.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vs3b8Bs53hbiNvcNeBilzE/+IhDTWKjSBB3v/ztr664nZk65j0xr+5IHMBNz3CFppmX7o/aBta2PxY+t+4KoPg=="],
@@ -1369,7 +1373,7 @@
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
"@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="],
@@ -1449,6 +1453,22 @@
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg=="],
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260412.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260412.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260412.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260412.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260412.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260412.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260412.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260412.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-tDw3XZt2BkjAlt/MJmnFGmbe9lgKmc5wezmrMoBIEvJcqz+/KVpVBVvjbkZoaiABnJmuG3G3b6IUFrEveTw6UQ=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260412.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sSkFG+hjtRWffg6FddF3dEkk7N3TRMEqfiUpixwcWhXgyocMdPw8wutTvQRBxQdgxeL9y01M2SO8A8YPPiEgVg=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260412.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2BTeaLkrHEEDg0D9snigddy01qTY+wgx+W+GpXAfx36PPvW4xWuGXNVWfSaB8bqAC9C8NeLnT/C9/G/rJ5v2w=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260412.1", "", { "os": "linux", "cpu": "arm" }, "sha512-wDLekbfsfmKMWORg7CTnEnpKj8oXpU/6AEBrtVN9CEUCiQAe6yH878nZHhJNzWQXHtrtFf3lY49Yplqmdxja3w=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260412.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-JAdsG6MlVV1hoAUKPy8zxAL7xLeNxz8JgCbLCJVqW8EyH29R9FD4cFTqr7CSIRTNUEDzDTrgnXUyoRtDe1gr+w=="],
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260412.1", "", { "os": "linux", "cpu": "x64" }, "sha512-gYgppiQIqid3jZ7D8THh4k3Q+4bwidrQH6SL9Xgbk1qfP6/jwv8twuPqDOfZ+cK2OD55lQHp15fOh2lMNAC40Q=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260412.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-TOh7rH5H3jisHJqRXJSjmUGMzcbNBocS/hufhXPQIv+g3pdG5IKZoSnv3SV62I5d12FFDSS5KQon5MQPnOKAHg=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260412.1", "", { "os": "win32", "cpu": "x64" }, "sha512-u+70wL89wspN1wKoX6FVNUATRGCG3BpleByP3H/UqOZvlwuMm8N7Gy8hEbM0U8bDyAuyP/daUfTBVkqXjjv9mA=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
@@ -1661,7 +1681,7 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
"bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
@@ -3425,7 +3445,7 @@
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
"ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
@@ -3901,8 +3921,6 @@
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"aelis-client/@tanstack/react-query": ["@tanstack/react-query@5.90.21", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="],
"aelis-client/@types/react": ["@types/react@19.1.17", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA=="],
"aelis-client/react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
@@ -3929,6 +3947,8 @@
"body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
"bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
"c12/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"c12/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -4539,8 +4559,6 @@
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
"aelis-client/@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="],
"aelis-client/react-dom/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"better-opn/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
@@ -4549,6 +4567,8 @@
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"chrome-launcher/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"chromium-edge-launcher/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],

View File

@@ -16,11 +16,12 @@
"devDependencies": {
"@json-render/core": "^0.12.1",
"@nym.sh/jrx": "^0.2.0",
"@types/bun": "^1.3.12",
"@types/bun": "latest",
"@typescript/native-preview": "^7.0.0-dev.20260412.1",
"oxfmt": "^0.24.0",
"oxlint": "^1.39.0"
},
"peerDependencies": {
"typescript": "^5"
"typescript": "^6"
}
}

View File

@@ -1,6 +1,5 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
@@ -8,20 +7,19 @@
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"types": ["bun"],
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false