@algopayoracle/oracle-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.d.ts ADDED
@@ -0,0 +1,373 @@
1
+ /**
2
+ * @algopayoracle/oracle-sdk — TypeScript Definitions
3
+ * APC-1 · Ed25519 · Algorand
4
+ */
5
+
6
+ // ─── Core types ───────────────────────────────────────────────────────────────
7
+
8
+ /** Normalized payment event — the single shape consumed by AlgoPayClient */
9
+ export interface PaymentEvent {
10
+ /** Provider-issued payment reference (e.g. "pay_XXXXXXX") */
11
+ payment_id: string;
12
+ /** Integer fiat amount in base currency units (e.g. 100 for ₹100) */
13
+ amount: number;
14
+ /** ISO 4217 currency code (default: "INR") */
15
+ currency?: string;
16
+ /** Intended Web3 action (default: "unlock") */
17
+ action?: string;
18
+ /** Payment rail label — enables cross-provider replay protection (default: "unknown") */
19
+ provider?: string;
20
+ }
21
+
22
+ /** Internal signed proof — output of OracleSigner.sign() */
23
+ export interface SignedProof {
24
+ /** Original provider-issued ID (display only) */
25
+ payment_id: string;
26
+ /** Namespaced replay key: "provider:payment_id" — what was signed and stored on-chain */
27
+ canonical_id: string;
28
+ provider: string;
29
+ amount: number;
30
+ action: string;
31
+ currency: string;
32
+ /** Unix seconds when proof was signed (backdated ~30s for network latency) */
33
+ timestamp: number;
34
+ /** The Algorand Application ID this proof is locked to (0 for anchor mode) */
35
+ app_id: number;
36
+ /** Algorand address of the signing oracle */
37
+ oracle_address: string;
38
+ /** Base64 Ed25519 signature */
39
+ signature: string;
40
+ }
41
+
42
+ /** APC-1 (AlgoPay Credential v1) — standardized portable payment proof */
43
+ export interface APC1Proof {
44
+ /** Format version — always "1" */
45
+ apc: "1";
46
+ payment_id: string;
47
+ /** canonical_id is the on-chain replay key — must be present for verification */
48
+ canonical_id: string;
49
+ amount: number;
50
+ currency: string;
51
+ action: string;
52
+ timestamp: number;
53
+ oracle_address: string;
54
+ signature: string;
55
+ chain: "algorand";
56
+ network: "localnet" | "testnet" | "mainnet";
57
+ app_id: number | null;
58
+ provider: string;
59
+ }
60
+
61
+ /** Result of AlgoPayClient.verifyAndCommit() */
62
+ export interface CommitResult {
63
+ /** Confirmed Algorand transaction ID */
64
+ txId: string;
65
+ /** Internal signed proof */
66
+ proof: SignedProof;
67
+ /** APC-1 standardized credential */
68
+ apc1: APC1Proof;
69
+ /** Lora explorer link */
70
+ explorerUrl: string;
71
+ /** Relative URL for your backend's /verify-proof endpoint */
72
+ verifyUrl: string;
73
+ /** Access window in seconds (default: 300) */
74
+ access_seconds: number;
75
+ }
76
+
77
+ /** Result of proof verification */
78
+ export interface VerifyResult {
79
+ valid: boolean;
80
+ /** Human-readable reason when valid is false */
81
+ reason?: string;
82
+ proof?: SignedProof;
83
+ txId?: string;
84
+ }
85
+
86
+ /** Options for proof verification */
87
+ export interface VerifyOptions {
88
+ /** Only accept proofs signed by this oracle address */
89
+ expectedOracleAddress?: string;
90
+ /** Only accept proofs with this action */
91
+ expectedAction?: string;
92
+ /** Maximum proof age in seconds (default: 300) */
93
+ maxAgeSecs?: number;
94
+ }
95
+
96
+ // ─── PaymentAdapter interface ─────────────────────────────────────────────────
97
+ //
98
+ // The adapter contract: any object implementing PaymentAdapter can be used
99
+ // to normalize gateway-specific webhooks into AlgoPay PaymentEvents.
100
+ //
101
+ // Implementing a custom adapter (e.g. PayU, CCAvenue, PhonePe):
102
+ //
103
+ // class PayUAdapter implements PaymentAdapter {
104
+ // parseWebhook(rawBody: Buffer, signature: string): PaymentEvent | null {
105
+ // if (!this.verifyChecksum(rawBody, signature)) return null;
106
+ // const body = JSON.parse(rawBody.toString());
107
+ // return {
108
+ // payment_id: body.mihpayid,
109
+ // amount: Math.round(Number(body.amount)),
110
+ // currency: "INR",
111
+ // action: "unlock",
112
+ // provider: "payu",
113
+ // };
114
+ // }
115
+ // }
116
+
117
+ export interface PaymentAdapter {
118
+ /**
119
+ * Parse and verify a gateway webhook event.
120
+ * Returns null on invalid signature or non-payment events.
121
+ * Never throws — invalid input returns null.
122
+ */
123
+ parseWebhook(rawBody: Buffer | string, signature: string): PaymentEvent | null;
124
+ }
125
+
126
+ // ─── AlgoPayClient ────────────────────────────────────────────────────────────
127
+
128
+ export interface AlgoPayClientOptions {
129
+ /** 25-word Algorand mnemonic for the oracle signing account */
130
+ mnemonic: string;
131
+ /** Algorand network (default: "testnet") */
132
+ network?: "localnet" | "testnet" | "mainnet";
133
+ /** Deployed AlgoPayOracle App ID. Omit to run in anchor mode. */
134
+ appId?: number | null;
135
+ /** Custom algod client (overrides network) */
136
+ algod?: unknown;
137
+ /** Custom indexer client */
138
+ indexer?: unknown;
139
+ /** Custom explorer base URL */
140
+ explorerBase?: string;
141
+ }
142
+
143
+ export declare class AlgoPayClient {
144
+ readonly network: string;
145
+ readonly appId: number | null;
146
+
147
+ constructor(options: AlgoPayClientOptions);
148
+
149
+ /** Sign and commit a payment to Algorand. Primary entry point. */
150
+ verifyAndCommit(payment: PaymentEvent): Promise<CommitResult>;
151
+
152
+ /** Verify a proof by txId (indexer lookup) */
153
+ verifyProof(txId: string, opts?: VerifyOptions): Promise<VerifyResult>;
154
+
155
+ /** Verify a proof off-chain (no network) */
156
+ verifyProofOffchain(proof: SignedProof, opts?: VerifyOptions): VerifyResult;
157
+
158
+ /** Register a new oracle (creator only) */
159
+ addOracle(addressOrBase64: string): Promise<string>;
160
+
161
+ /** Deregister an oracle (creator only) */
162
+ removeOracle(addressOrBase64: string): Promise<string>;
163
+
164
+ /** Check if an oracle is registered in the contract */
165
+ isOracleRegistered(addressOrBase64: string): Promise<boolean>;
166
+
167
+ /** Total verified payments from the contract */
168
+ getTotalVerified(): Promise<number>;
169
+
170
+ /** Number of registered oracles */
171
+ getOracleCount(): Promise<number>;
172
+
173
+ getAddress(): string;
174
+ getPublicKeyBase64(): string;
175
+ getPublicKeyBytes(): Uint8Array;
176
+ getExplorerUrl(txId: string): string;
177
+ }
178
+
179
+ // ─── OracleSigner ─────────────────────────────────────────────────────────────
180
+
181
+ export interface SignOptions {
182
+ payment_id: string;
183
+ amount: number;
184
+ action?: string;
185
+ currency?: string;
186
+ provider?: string;
187
+ }
188
+
189
+ export declare class OracleSigner {
190
+ readonly address: string;
191
+
192
+ constructor(mnemonic: string);
193
+
194
+ /** Sign a payment proof */
195
+ sign(options: SignOptions, appId?: number): SignedProof;
196
+
197
+ /** Build raw message bytes (must match contract verify_payment exactly) */
198
+ static buildMessage(
199
+ canonical_id: string,
200
+ action: string,
201
+ currency: string,
202
+ amount: number,
203
+ timestamp: number,
204
+ appId?: number
205
+ ): Uint8Array;
206
+
207
+ /** Verify a proof's signature off-chain */
208
+ static verifyOffchain(proof: SignedProof): boolean;
209
+
210
+ /** Base64 public key — paste into contract create() call */
211
+ getPublicKeyBase64(): string;
212
+ getPublicKeyBytes(): Uint8Array;
213
+ getAddress(): string;
214
+ }
215
+
216
+ // ─── ProofVerifier ────────────────────────────────────────────────────────────
217
+
218
+ export interface ProofVerifierOptions {
219
+ indexer?: unknown;
220
+ network?: string;
221
+ }
222
+
223
+ export declare class ProofVerifier {
224
+ constructor(options?: ProofVerifierOptions);
225
+
226
+ verifyOffchain(proof: SignedProof | APC1Proof, opts?: VerifyOptions): VerifyResult;
227
+ verifyTxn(txId: string, opts?: VerifyOptions): Promise<VerifyResult>;
228
+ verifyBatch(txIds: string[], opts?: VerifyOptions): Promise<VerifyResult[]>;
229
+ }
230
+
231
+ // ─── Adapters ─────────────────────────────────────────────────────────────────
232
+
233
+ export interface OrderRecord {
234
+ amount: number;
235
+ currency: string;
236
+ }
237
+
238
+ export interface OrderStore {
239
+ set(id: string, data: OrderRecord): Promise<void>;
240
+ get(id: string): Promise<OrderRecord | null>;
241
+ consume(id: string): Promise<OrderRecord | null>;
242
+ delete(id: string): Promise<void>;
243
+ size?(): number;
244
+ destroy?(): void;
245
+ }
246
+
247
+ export interface RazorpayAdapterOptions {
248
+ keySecret: string;
249
+ keyId?: string;
250
+ orderStore?: OrderStore;
251
+ defaultAction?: string;
252
+ }
253
+
254
+ export interface RazorpayClientPaymentInput {
255
+ razorpay_order_id: string;
256
+ razorpay_payment_id: string;
257
+ razorpay_signature: string;
258
+ action?: string;
259
+ }
260
+
261
+ export declare class RazorpayAdapter implements PaymentAdapter {
262
+ constructor(options: RazorpayAdapterOptions);
263
+
264
+ parseWebhook(rawBody: Buffer | string, signature: string): PaymentEvent | null;
265
+ parseClientPayment(input: RazorpayClientPaymentInput): Promise<PaymentEvent>;
266
+ createOrder(opts: { amount: number; currency?: string }): Promise<{ order_id: string; amount: number; currency: string; key_id: string }>;
267
+ }
268
+
269
+ export interface StripeAdapterOptions {
270
+ webhookSecret: string;
271
+ secretKey?: string;
272
+ defaultAction?: string;
273
+ }
274
+
275
+ export declare class StripeAdapter implements PaymentAdapter {
276
+ constructor(options: StripeAdapterOptions);
277
+ parseWebhook(rawBody: Buffer, signature: string): PaymentEvent | null;
278
+ }
279
+
280
+ // ─── Utilities ────────────────────────────────────────────────────────────────
281
+
282
+ export interface NetworkConfig {
283
+ algodToken: string;
284
+ algodServer: string;
285
+ algodPort: number;
286
+ indexerToken: string;
287
+ indexerServer: string;
288
+ indexerPort: number;
289
+ explorerBase: string;
290
+ }
291
+
292
+ export declare const NETWORKS: Record<"localnet" | "testnet" | "mainnet", NetworkConfig>;
293
+
294
+ export declare function createClients(network: "localnet" | "testnet" | "mainnet"): {
295
+ algod: unknown;
296
+ indexer: unknown;
297
+ config: NetworkConfig;
298
+ };
299
+
300
+ export interface CustomClientOptions {
301
+ algodUrl: string;
302
+ algodToken?: string;
303
+ indexerUrl: string;
304
+ indexerToken?: string;
305
+ explorerBase?: string;
306
+ }
307
+
308
+ export declare function createCustomClients(opts: CustomClientOptions): {
309
+ algod: unknown;
310
+ indexer: unknown;
311
+ config: { explorerBase: string };
312
+ };
313
+
314
+ export declare const APC_VERSION: "1";
315
+ export declare function toAPC1(proof: SignedProof, meta?: { network?: string; appId?: number | null; provider?: string }): APC1Proof;
316
+ export declare function validateAPC1Structure(cred: unknown): { valid: boolean; errors: string[] };
317
+ export declare function isSupportedVersion(cred: unknown): boolean;
318
+ export declare function isExpired(cred: { timestamp: number }, maxAgeSecs?: number): boolean;
319
+
320
+ export declare function validatePaymentEvent(input: unknown): PaymentEvent;
321
+ export declare function validateProofFields(proof: unknown): void;
322
+
323
+ // ─── Logger ───────────────────────────────────────────────────────────────────
324
+
325
+ export interface Logger {
326
+ info(msg: string, fields?: Record<string, unknown>): void;
327
+ warn(msg: string, fields?: Record<string, unknown>): void;
328
+ error(msg: string, fields?: Record<string, unknown>): void;
329
+ debug(msg: string, fields?: Record<string, unknown>): void;
330
+ child(fields: Record<string, unknown>): Logger;
331
+ }
332
+
333
+ export declare function createLogger(component: string): Logger;
334
+ export declare function requestLogger(req: unknown, res: unknown, next: () => void): void;
335
+
336
+ // ─── Order store ──────────────────────────────────────────────────────────────
337
+
338
+ export declare class InMemoryOrderStore implements OrderStore {
339
+ constructor(ttlMs?: number);
340
+ set(id: string, data: OrderRecord): Promise<void>;
341
+ get(id: string): Promise<OrderRecord | null>;
342
+ consume(id: string): Promise<OrderRecord | null>;
343
+ delete(id: string): Promise<void>;
344
+ size(): number;
345
+ destroy(): void;
346
+ }
347
+
348
+ export declare function createOrderStore(options?: { ttlMs?: number }): InMemoryOrderStore;
349
+
350
+ // ─── Error classes ────────────────────────────────────────────────────────────
351
+
352
+ export declare class AlgoPayError extends Error {
353
+ code: string;
354
+ }
355
+ export declare class InsufficientAmountError extends AlgoPayError {
356
+ amount: number;
357
+ minAmount: number;
358
+ }
359
+ export declare class ProofExpiredError extends AlgoPayError {
360
+ timestamp: number;
361
+ age: number;
362
+ }
363
+ export declare class OracleNotRegisteredError extends AlgoPayError {
364
+ address: string;
365
+ }
366
+ export declare class ReplayError extends AlgoPayError {
367
+ paymentId: string;
368
+ }
369
+ export declare class InvalidSignatureError extends AlgoPayError {}
370
+ export declare class ConfigError extends AlgoPayError {}
371
+ export declare class ProviderAuthError extends AlgoPayError {
372
+ provider: string;
373
+ }
package/src/index.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @algopayoracle/oracle-sdk
3
+ *
4
+ * Everything you need is available from this single entry point.
5
+ * You should never need to import from internal paths.
6
+ *
7
+ * Common usage:
8
+ *
9
+ * const { AlgoPayClient } = require("@algopayoracle/oracle-sdk");
10
+ *
11
+ * const client = new AlgoPayClient({
12
+ * mnemonic: process.env.ORACLE_MNEMONIC,
13
+ * network: "testnet",
14
+ * appId: Number(process.env.ALGO_APP_ID),
15
+ * });
16
+ *
17
+ * const result = await client.verifyAndCommit({
18
+ * payment_id: "pay_XXXXXXX",
19
+ * amount: 100,
20
+ * action: "unlock",
21
+ * provider: "razorpay",
22
+ * });
23
+ */
24
+
25
+ "use strict";
26
+
27
+ // ── Core client (most users only need this) ───────────────────────────────────
28
+ const { AlgoPayClient } = require("./AlgoPayClient");
29
+ const { OracleSigner } = require("./OracleSigner");
30
+ const { ProofVerifier } = require("./ProofVerifier");
31
+
32
+ // ── Network helpers ────────────────────────────────────────────────────────────
33
+ const { NETWORKS, createClients, createCustomClients } = require("./networks");
34
+
35
+ // ── APC-1 standard ────────────────────────────────────────────────────────────
36
+ const {
37
+ APC_VERSION,
38
+ SUPPORTED_APC,
39
+ toAPC1,
40
+ validateAPC1Structure,
41
+ isSupportedVersion,
42
+ isExpired,
43
+ } = require("./apc1");
44
+
45
+ // ── Input validation ──────────────────────────────────────────────────────────
46
+ const { validatePaymentEvent, validateProofFields, MIN_AMOUNT } = require("./validate");
47
+
48
+ // ── Error classes ─────────────────────────────────────────────────────────────
49
+ const errors = require("./errors");
50
+
51
+ // ── Payment adapters ──────────────────────────────────────────────────────────
52
+ const { RazorpayAdapter } = require("./adapters/razorpay");
53
+ const { StripeAdapter } = require("./adapters/stripe");
54
+
55
+ // ── Infrastructure utilities ──────────────────────────────────────────────────
56
+ const { createLogger, requestLogger } = require("./utils/logger");
57
+ const { createOrderStore, InMemoryOrderStore } = require("./utils/store");
58
+
59
+ // ─── Exports ──────────────────────────────────────────────────────────────────
60
+
61
+ module.exports = {
62
+ // Primary API
63
+ AlgoPayClient,
64
+
65
+ // Lower-level building blocks
66
+ OracleSigner,
67
+ ProofVerifier,
68
+
69
+ // Network
70
+ NETWORKS,
71
+ createClients,
72
+ createCustomClients,
73
+
74
+ // APC-1 standard
75
+ APC_VERSION,
76
+ SUPPORTED_APC,
77
+ toAPC1,
78
+ validateAPC1Structure,
79
+ isSupportedVersion,
80
+ isExpired,
81
+
82
+ // Input validation
83
+ validatePaymentEvent,
84
+ validateProofFields,
85
+ MIN_AMOUNT,
86
+
87
+ // Error classes (spread so consumers can do { AlgoPayError } = require(...))
88
+ ...errors,
89
+
90
+ // Adapters
91
+ RazorpayAdapter,
92
+ StripeAdapter,
93
+
94
+ // Infrastructure
95
+ createLogger,
96
+ requestLogger,
97
+ createOrderStore,
98
+ InMemoryOrderStore,
99
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * AlgoPay Oracle SDK — Network Configurations
3
+ */
4
+
5
+ const algosdk = require("algosdk");
6
+
7
+ const NETWORKS = {
8
+ localnet: {
9
+ algodToken: "a".repeat(64),
10
+ algodServer: "http://localhost",
11
+ algodPort: 4001,
12
+ indexerToken: "a".repeat(64),
13
+ indexerServer: "http://localhost",
14
+ indexerPort: 8980,
15
+ explorerBase: "https://lora.algokit.io/localnet",
16
+ },
17
+ testnet: {
18
+ algodToken: "",
19
+ algodServer: "https://testnet-api.algonode.cloud",
20
+ algodPort: 443,
21
+ indexerToken: "",
22
+ indexerServer: "https://testnet-idx.algonode.cloud",
23
+ indexerPort: 443,
24
+ explorerBase: "https://lora.algokit.io/testnet",
25
+ },
26
+ mainnet: {
27
+ algodToken: "",
28
+ algodServer: "https://mainnet-api.algonode.cloud",
29
+ algodPort: 443,
30
+ indexerToken: "",
31
+ indexerServer: "https://mainnet-idx.algonode.cloud",
32
+ indexerPort: 443,
33
+ explorerBase: "https://lora.algokit.io/mainnet",
34
+ },
35
+ };
36
+
37
+ /**
38
+ * Create algod + indexer clients for a named network.
39
+ * @param {"localnet"|"testnet"|"mainnet"} network
40
+ * @returns {{ algod: Algodv2, indexer: Indexer, config: object }}
41
+ */
42
+ function createClients(network) {
43
+ const cfg = NETWORKS[network];
44
+ if (!cfg) throw new Error(`Unknown network: ${network}. Use localnet, testnet, or mainnet.`);
45
+ return {
46
+ algod: new algosdk.Algodv2(cfg.algodToken, cfg.algodServer, cfg.algodPort),
47
+ indexer: new algosdk.Indexer(cfg.indexerToken, cfg.indexerServer, cfg.indexerPort),
48
+ config: cfg,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Create clients from custom algod/indexer URLs (e.g. nodely, purestake).
54
+ * @param {{ algodUrl, algodToken, indexerUrl, indexerToken, explorerBase }} opts
55
+ */
56
+ function createCustomClients({ algodUrl, algodToken = "", indexerUrl, indexerToken = "", explorerBase = "" }) {
57
+ const algodUri = new URL(algodUrl);
58
+ const indexerUri = new URL(indexerUrl);
59
+ return {
60
+ algod: new algosdk.Algodv2(algodToken, algodUri.origin, algodUri.port || 443),
61
+ indexer: new algosdk.Indexer(indexerToken, indexerUri.origin, indexerUri.port || 443),
62
+ config: { explorerBase },
63
+ };
64
+ }
65
+
66
+ module.exports = { NETWORKS, createClients, createCustomClients };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * AlgoPay Oracle — Structured Logger
3
+ *
4
+ * Outputs JSON in production (machine-parseable, ingestible by Datadog/Loki/CloudWatch).
5
+ * Outputs pretty-printed lines in development.
6
+ *
7
+ * Every log entry carries a requestId so a full payment flow can be traced
8
+ * across multiple log lines: create-order → verify-payment → oracle sign → txId.
9
+ *
10
+ * Usage:
11
+ * const { createLogger, requestLogger } = require("./logger");
12
+ * const log = createLogger("payment");
13
+ * log.info("Payment received", { payment_id, amount });
14
+ * log.error("Oracle failed", { payment_id, error: e.message });
15
+ *
16
+ * Express middleware:
17
+ * app.use(requestLogger); // attaches req.log and req.requestId
18
+ * // then in routes:
19
+ * req.log.info("order created", { order_id, amount });
20
+ */
21
+
22
+ "use strict";
23
+
24
+ const crypto = require("crypto");
25
+ const IS_PROD = process.env.NODE_ENV === "production";
26
+
27
+ // ─── Core log function ────────────────────────────────────────────────────────
28
+
29
+ function write(level, component, message, fields = {}) {
30
+ const entry = {
31
+ ts: new Date().toISOString(),
32
+ level,
33
+ component,
34
+ message,
35
+ ...fields,
36
+ };
37
+
38
+ if (IS_PROD) {
39
+ // JSON — one line per entry, ready for log aggregators
40
+ process.stdout.write(JSON.stringify(entry) + "\n");
41
+ } else {
42
+ // Human-readable dev format
43
+ const color = { info: "\x1b[32m", warn: "\x1b[33m", error: "\x1b[31m", debug: "\x1b[90m" };
44
+ const reset = "\x1b[0m";
45
+ const c = color[level] || "";
46
+ const meta = Object.keys(fields).length
47
+ ? " " + Object.entries(fields).map(([k, v]) => `${k}=${v}`).join(" ")
48
+ : "";
49
+ console.log(`${c}[${entry.ts}] ${level.toUpperCase().padEnd(5)} [${component}] ${message}${meta}${reset}`);
50
+ }
51
+ }
52
+
53
+ // ─── Logger factory ───────────────────────────────────────────────────────────
54
+
55
+ function createLogger(component) {
56
+ return {
57
+ info: (msg, fields) => write("info", component, msg, fields),
58
+ warn: (msg, fields) => write("warn", component, msg, fields),
59
+ error: (msg, fields) => write("error", component, msg, fields),
60
+ debug: (msg, fields) => {
61
+ if (!IS_PROD) write("debug", component, msg, fields);
62
+ },
63
+ // Returns a child logger with fixed fields merged into every entry
64
+ child: (fixedFields) => ({
65
+ info: (msg, fields) => write("info", component, msg, { ...fixedFields, ...fields }),
66
+ warn: (msg, fields) => write("warn", component, msg, { ...fixedFields, ...fields }),
67
+ error: (msg, fields) => write("error", component, msg, { ...fixedFields, ...fields }),
68
+ debug: (msg, fields) => {
69
+ if (!IS_PROD) write("debug", component, msg, { ...fixedFields, ...fields });
70
+ },
71
+ }),
72
+ };
73
+ }
74
+
75
+ // ─── Express request logger middleware ────────────────────────────────────────
76
+
77
+ const httpLog = createLogger("http");
78
+
79
+ function requestLogger(req, res, next) {
80
+ req.requestId = crypto.randomUUID();
81
+ req.log = httpLog.child({ requestId: req.requestId });
82
+
83
+ const start = Date.now();
84
+
85
+ res.on("finish", () => {
86
+ const ms = Date.now() - start;
87
+ const level = res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warn" : "info";
88
+ write(level, "http", `${req.method} ${req.path}`, {
89
+ requestId: req.requestId,
90
+ status: res.statusCode,
91
+ ms,
92
+ });
93
+ });
94
+
95
+ next();
96
+ }
97
+
98
+ module.exports = { createLogger, requestLogger };