Skip to content
Merged
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
735 changes: 517 additions & 218 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@prisma/client": "^7.8.0",
"@prisma/driver-adapter-utils": "^7.8.0",
"@stellar/stellar-sdk": "^14.6.1",
"@tanstack/react-query": "^5.100.5",
"@types/node": "^22.0.0",
"@types/pdfkit": "^0.17.6",
"@types/pg": "^8.20.0",
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions src/components/providers/QueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { QueryClientProvider } from '@tanstack/react-query'
import { queryClient } from '../../lib/queryClient'

export const QueryProvider = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
Empty file.
Empty file.
Empty file.
34 changes: 34 additions & 0 deletions src/config/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import winston from 'winston';

import { HttpLogTransport }
from '../transports/httpLogTransport';

const transports = [
new winston.transports.Console(),
];

if (
process.env
.LOG_STREAM_ENABLED ===
'true'
) {
transports.push(
new HttpLogTransport({
level: 'info',
}),
);
}

export const logger =
winston.createLogger({
level: 'info',

format:
winston.format.combine(
winston.format.timestamp(),

winston.format.json(),
),

transports,
});
Empty file added src/hooks/usePortfolio.ts
Empty file.
15 changes: 15 additions & 0 deletions src/hooks/usePrices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query'
import { api } from '../services/api'

export const usePrices = () => {
return useQuery({
queryKey: ['prices'],
queryFn: api.getPrices,

// 🔥 SWR behavior tuning
staleTime: 10 * 1000, // prices go stale fast
refetchInterval: 15 * 1000, // auto refresh every 15s

placeholderData: (previousData) => previousData, // 👈 instant UI
})
}
Empty file added src/hooks/useTransactions.ts
Empty file.
Empty file added src/layout.tsx
Empty file.
13 changes: 13 additions & 0 deletions src/lib/queryClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000, // 30s → data considered fresh
gcTime: 5 * 60 * 1000, // cache persists 5 mins
refetchOnWindowFocus: false, // 🔥 your requirement
refetchOnReconnect: true,
retry: 2,
},
},
})
13 changes: 13 additions & 0 deletions src/services/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const api = {
async getPrices() {
const res = await fetch('/api/prices')
if (!res.ok) throw new Error('Failed to fetch prices')
return res.json()
},

async getPortfolio() {
const res = await fetch('/api/portfolio')
if (!res.ok) throw new Error('Failed to fetch portfolio')
return res.json()
},
}
83 changes: 83 additions & 0 deletions src/transport/httpLogTransport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Transport from 'winston-transport';
import axios from 'axios';

interface HttpTransportOptions {
level?: string;
}

export class HttpLogTransport extends Transport {
private queue: any[] = [];

private flushing = false;

constructor(
opts: HttpTransportOptions,
) {
super(opts);

setInterval(
() => this.flush(),
3000,
);
}

log(info: any, callback: () => void) {
setImmediate(() => {
this.emit('logged', info);
});

this.queue.push({
timestamp:
new Date().toISOString(),
level: info.level,
message: info.message,
meta: info.meta ?? {},
});

callback();
}

private async flush() {
if (
this.flushing ||
this.queue.length === 0
) {
return;
}

this.flushing = true;

const batch =
this.queue.splice(0, 20);

try {
await axios.post(
process.env.LOG_STREAM_URL!,
batch,
{
timeout: Number(
process.env
.LOG_STREAM_TIMEOUT_MS ??
2000,
),

headers: {
Authorization:
`Bearer ${process.env.LOG_STREAM_TOKEN}`,
'Content-Type':
'application/json',
},
},
);
} catch (error) {
console.error(
'[log-stream] failed to stream logs',
);

// Requeue logs to avoid loss
this.queue.unshift(...batch);
} finally {
this.flushing = false;
}
}
}
Loading