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": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"typescript": "~5.9.3", "typescript": "^6",
"vite": "^7.2.4" "vite": "^7.2.4"
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,7 @@
"@aelis/source-location": "workspace:*", "@aelis/source-location": "workspace:*",
"@aelis/source-tfl": "workspace:*", "@aelis/source-tfl": "workspace:*",
"@aelis/source-weatherkit": "workspace:*", "@aelis/source-weatherkit": "workspace:*",
"@openrouter/sdk": "^0.9.11",
"arktype": "^2.1.29", "arktype": "^2.1.29",
"better-auth": "^1", "better-auth": "^1",
"drizzle-orm": "^0.45.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 type { EnhancementResult } from "./schema.ts"
import { enhancementResultJsonSchema, parseEnhancementResult } 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 const DEFAULT_TIMEOUT_MS = 30_000
export interface LlmClientConfig { export interface LlmClientConfig {
accountId: string
apiKey: string apiKey: string
model?: string model?: string
timeoutMs?: number timeoutMs?: number
@@ -23,94 +22,52 @@ export interface LlmClient {
enhance(request: LlmClientRequest): Promise<EnhancementResult | null> 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. * Creates a reusable LLM client backed by OpenRouter.
* Uses the REST API with structured JSON output. * The OpenRouter SDK instance is created once and reused across calls.
*/ */
export function createLlmClient(config: LlmClientConfig): LlmClient { 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 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 { return {
async enhance(request) { async enhance(request) {
try { const response = await client.chat.send({
const res = await fetch(baseUrl, { chatGenerationParams: {
method: "POST", model,
headers: { messages: [
Authorization: `Bearer ${config.apiKey}`, { role: "system" as const, content: request.systemPrompt },
"Content-Type": "application/json", { role: "user" as const, content: request.userMessage },
}, ],
body: JSON.stringify({ responseFormat: {
messages: [ type: "json_schema" as const,
{ role: "system", content: request.systemPrompt }, jsonSchema: {
{ role: "user", content: request.userMessage }, name: "enhancement_result",
], strict: false,
response_format: { schema: enhancementResultJsonSchema,
type: "json_schema",
json_schema: {
name: "enhancement_result",
strict: false,
schema: enhancementResultJsonSchema,
},
}, },
stream: false, },
}), reasoning: { effort: "none" },
// @ts-expect-error — bun-types AbortSignal conflicts with ESNext lib in tsc; works at runtime and in VSCode stream: false,
signal: AbortSignal.timeout(timeoutMs), },
}) })
if (!res.ok) { const message = response.choices?.[0]?.message
const body = await res.text() const content = message?.content ?? message?.reasoning
console.warn(`[enhancement] Cloudflare API error ${res.status}: ${body}`) if (typeof content !== "string") {
return null console.warn("[enhancement] LLM returned no content in response")
}
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")
return null
}
const result = parseEnhancementResult(content)
if (!result) {
console.warn("[enhancement] Failed to parse LLM response:", content)
}
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 return null
} }
const result = parseEnhancementResult(content)
if (!result) {
console.warn("[enhancement] Failed to parse LLM response:", content)
}
return result
}, },
} }
} }

View File

@@ -15,7 +15,8 @@ export type SyntheticItem = typeof SyntheticItem.infer
export type EnhancementResult = typeof EnhancementResult.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. * ⚠️ Must stay in sync with EnhancementResult above.
* If you add/remove fields, update both schemas. * 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 { db, close: closeDb } = createDatabase(process.env.DATABASE_URL!)
const auth = createAuth(db) const auth = createAuth(db)
const cfAccountId = process.env.CF_ACCOUNT_ID const openrouterApiKey = process.env.OPENROUTER_API_KEY
const workersAiApiKey = process.env.WORKERS_AI_API_KEY const feedEnhancer = openrouterApiKey
const feedEnhancer = ? createFeedEnhancer({
cfAccountId && workersAiApiKey client: createLlmClient({
? createFeedEnhancer({ apiKey: openrouterApiKey,
client: createLlmClient({ model: process.env.OPENROUTER_MODEL || undefined,
accountId: cfAccountId, }),
apiKey: workersAiApiKey, })
model: process.env.WORKERS_AI_MODEL || undefined, : null
}),
})
: null
if (!feedEnhancer) { if (!feedEnhancer) {
console.warn( console.warn("[enhancement] OPENROUTER_API_KEY not set — feed enhancement disabled")
"[enhancement] CF_ACCOUNT_ID/WORKERS_AI_API_KEY not set — feed enhancement disabled",
)
} }
const credentialEncryptionKey = process.env.CREDENTIAL_ENCRYPTION_KEY const credentialEncryptionKey = process.env.CREDENTIAL_ENCRYPTION_KEY

View File

@@ -44,6 +44,15 @@ function getEnabledSourceIds(userId: string): string[] {
*/ */
let mockFindResult: unknown | undefined 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 * Spy for `updateCredentials` calls. Tests can inspect calls via
* `mockUpdateCredentialsCalls` or override behavior. * `mockUpdateCredentialsCalls` or override behavior.
@@ -81,6 +90,9 @@ mock.module("../sources/user-sources.ts", () => ({
updatedAt: now, updatedAt: now,
} }
}, },
async upsertConfig(sourceId: string, data: { enabled: boolean; config: unknown }) {
mockUpsertConfigCalls.push({ sourceId, data })
},
async updateCredentials(sourceId: string, credentials: Buffer) { async updateCredentials(sourceId: string, credentials: Buffer) {
if (mockUpdateCredentialsError) { if (mockUpdateCredentialsError) {
throw mockUpdateCredentialsError throw mockUpdateCredentialsError
@@ -138,6 +150,7 @@ const weatherProvider: FeedSourceProvider = {
beforeEach(() => { beforeEach(() => {
enabledByUser.clear() enabledByUser.clear()
mockFindResult = undefined mockFindResult = undefined
mockUpsertConfigCalls.length = 0
mockUpdateCredentialsCalls.length = 0 mockUpdateCredentialsCalls.length = 0
mockUpdateCredentialsError = null mockUpdateCredentialsError = null
}) })
@@ -824,3 +837,65 @@ describe("UserSessionManager.updateSourceCredentials", () => {
expect(factory).not.toHaveBeenCalled() 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,11 +210,22 @@ export class UserSessionManager {
const credentials = existingRow?.credentials const credentials = existingRow?.credentials
? this.decryptCredentials(existingRow.credentials) ? this.decryptCredentials(existingRow.credentials)
: null : null
const source = await provider.feedSourceForUser(userId, config, credentials) try {
if (session.hasSource(sourceId)) { const source = await provider.feedSourceForUser(userId, config, credentials)
session.replaceSource(sourceId, source) if (session.hasSource(sourceId)) {
} else { session.replaceSource(sourceId, source)
session.addSource(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", "eas-cli": "^18.0.1",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~10.0.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": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
"typescript": "^5.9.2", "typescript": "^6",
"vite": "^7.1.7", "vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4" "vite-tsconfig-paths": "^5.1.4"
} }

View File

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

View File

@@ -7,12 +7,13 @@
"devDependencies": { "devDependencies": {
"@json-render/core": "^0.12.1", "@json-render/core": "^0.12.1",
"@nym.sh/jrx": "^0.2.0", "@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", "oxfmt": "^0.24.0",
"oxlint": "^1.39.0", "oxlint": "^1.39.0",
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5", "typescript": "^6",
}, },
}, },
"apps/admin-dashboard": { "apps/admin-dashboard": {
@@ -41,7 +42,7 @@
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"typescript": "~5.9.3", "typescript": "^6",
"vite": "^7.2.4", "vite": "^7.2.4",
}, },
}, },
@@ -55,6 +56,7 @@
"@aelis/source-location": "workspace:*", "@aelis/source-location": "workspace:*",
"@aelis/source-tfl": "workspace:*", "@aelis/source-tfl": "workspace:*",
"@aelis/source-weatherkit": "workspace:*", "@aelis/source-weatherkit": "workspace:*",
"@openrouter/sdk": "^0.9.11",
"arktype": "^2.1.29", "arktype": "^2.1.29",
"better-auth": "^1", "better-auth": "^1",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
@@ -110,7 +112,7 @@
"eas-cli": "^18.0.1", "eas-cli": "^18.0.1",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0", "eslint-config-expo": "~10.0.0",
"typescript": "~5.9.2", "typescript": "^6",
}, },
}, },
"apps/waitlist-website": { "apps/waitlist-website": {
@@ -137,7 +139,7 @@
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
"typescript": "^5.9.2", "typescript": "^6",
"vite": "^7.1.7", "vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
}, },
@@ -767,6 +769,8 @@
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], "@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-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=="], "@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/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=="], "@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-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=="], "@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=="], "@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=="], "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=="], "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=="], "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=="], "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=="], "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/@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=="], "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=="], "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/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
"c12/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "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=="], "@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=="], "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=="], "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=="], "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=="], "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=="], "chromium-edge-launcher/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],

View File

@@ -16,11 +16,12 @@
"devDependencies": { "devDependencies": {
"@json-render/core": "^0.12.1", "@json-render/core": "^0.12.1",
"@nym.sh/jrx": "^0.2.0", "@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", "oxfmt": "^0.24.0",
"oxlint": "^1.39.0" "oxlint": "^1.39.0"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5" "typescript": "^6"
} }
} }

View File

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