Files
marco-trest-test/apps/crud-api/src/stores.ts
T
marco.chan 7923ba1b8d feat: initialize ingest-api with Koa and telemetry handling
- Create ingest-api with Koa framework for handling device telemetry.
- Implement telemetry ingestion with MongoDB, Redis, and PostgreSQL.
- Add API endpoints for telemetry and health checks.
- Set up testing with Vitest for unit and end-to-end tests.
- Configure Prettier and ESLint for code formatting and linting.
- Create Docker Compose setup for PostgreSQL, MongoDB, and Redis services.
- Establish contracts package for shared Zod schemas and TypeScript types.
- Implement React frontend with device status and greeting functionalities.
- Add VS Code setup documentation for onboarding developers.
2026-09-03 10:13:02 +08:00

87 lines
2.3 KiB
TypeScript

import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/node-postgres';
import Redis from 'ioredis';
import pg from 'pg';
import type { DeviceStatusPair } from 'contracts';
import { env } from './env.js';
import { deviceCurrentState } from './db/schema.js';
import { emptyStatus, redisKey, toDeviceStatus } from './status.js';
export type DeviceStores = {
readStatus(deviceId: string): Promise<DeviceStatusPair>;
close(): Promise<void>;
};
type RedisSnapshot = {
lastSeen: string;
payload: Record<string, unknown>;
};
export function createDeviceStores(): DeviceStores {
const pool = new pg.Pool({ connectionString: env.databaseUrl });
const db = drizzle(pool);
const redis = new Redis(env.redisUrl, { maxRetriesPerRequest: 1, lazyConnect: true });
return {
async readStatus(deviceId: string): Promise<DeviceStatusPair> {
const pair = emptyStatus(deviceId);
try {
const rows = await db
.select()
.from(deviceCurrentState)
.where(eq(deviceCurrentState.deviceId, deviceId))
.limit(1);
const row = rows[0];
if (row) {
pair.postgres = toDeviceStatus({
deviceId,
lastSeen: row.lastSeen,
payload: row.payload,
source: 'postgres',
});
}
} catch (error) {
console.error('postgres status read failed', error);
}
try {
if (redis.status === 'wait') {
await redis.connect();
}
const raw = await redis.get(redisKey(deviceId));
if (raw) {
const snapshot = JSON.parse(raw) as RedisSnapshot;
pair.redis = toDeviceStatus({
deviceId,
lastSeen: new Date(snapshot.lastSeen),
payload: snapshot.payload ?? {},
source: 'redis',
});
}
} catch (error) {
console.error('redis status read failed', error);
}
return pair;
},
async close() {
await Promise.allSettled([pool.end(), redis.quit()]);
},
};
}
export function createMemoryStores(
seed: Partial<Record<string, DeviceStatusPair>> = {},
): DeviceStores {
return {
async readStatus(deviceId: string) {
return seed[deviceId] ?? emptyStatus(deviceId);
},
async close() {
return;
},
};
}