@mattrika/server-utils 0.0.0-stage → 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/index.esm.js ADDED
@@ -0,0 +1,396 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import pino from 'pino';
4
+ import { HTTPException } from 'hono/http-exception';
5
+ import { z } from '@hono/zod-openapi';
6
+ import crypto from 'crypto';
7
+ import argon2 from '@node-rs/argon2';
8
+ import { INTERNAL_SERVER_ERROR, NOT_FOUND, UNPROCESSABLE_ENTITY } from 'stoker/http-status-codes';
9
+ import { clamp } from 'es-toolkit';
10
+ import { sign, verify } from 'hono/jwt';
11
+
12
+ const DEFAULT_PAGE_SIZE = 20;
13
+ const MAX_PAGE_SIZE = 100;
14
+
15
+ const MAX_SIZE = 1024 * 1024; // 1MB
16
+ async function safeResponseBody(res) {
17
+ try {
18
+ const contentType = res.headers.get('content-type') || '';
19
+ const disposition = res.headers.get('content-disposition') || '';
20
+ // ❌ Skip downloads
21
+ if (disposition.includes('attachment')) {
22
+ return '[FILE DOWNLOAD SKIPPED]';
23
+ }
24
+ // ❌ Skip non-JSON responses
25
+ if (!contentType.includes('application/json')) {
26
+ return '[NON-JSON RESPONSE]';
27
+ }
28
+ const text = await res.clone().text();
29
+ if (!text) return undefined;
30
+ const size = Buffer.byteLength(text, 'utf8');
31
+ if (size > MAX_SIZE) return `[RESPONSE TOO LARGE: ${Math.round(size / 1024)} KB]`;
32
+ return JSON.parse(text);
33
+ } catch {
34
+ return '[UNREADABLE RESPONSE]';
35
+ }
36
+ }
37
+ async function safeRequestBody(req) {
38
+ try {
39
+ const contentType = req.header('content-type') || '';
40
+ // ❌ Skip file uploads
41
+ if (contentType.includes('multipart/form-data')) {
42
+ return '[FILE UPLOAD SKIPPED]';
43
+ }
44
+ if (!contentType.includes('application/json')) {
45
+ return undefined;
46
+ }
47
+ const text = await req.raw.clone().text();
48
+ if (!text) return undefined;
49
+ const size = Buffer.byteLength(text, 'utf8');
50
+ if (size > MAX_SIZE) return `[BODY TOO LARGE: ${Math.round(size / 1024)} KB]`;
51
+ return JSON.parse(text);
52
+ } catch {
53
+ return '[UNREADABLE REQUEST BODY]';
54
+ }
55
+ }
56
+ function ensureDirExists(filePath) {
57
+ const dir = path.dirname(filePath);
58
+ if (!fs.existsSync(dir)) {
59
+ fs.mkdirSync(dir, {
60
+ recursive: true
61
+ });
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Checks whether APP_ENV environment variable is set to 'production' or NODE_ENV environment variable is set to 'production', in that order.
67
+ * @returns boolean
68
+ */
69
+ function isProductionAppEnvironment() {
70
+ const appEnv = process?.env?.['APP_ENV'];
71
+ if (appEnv === 'production') return true;
72
+ const nodeEnv = process?.env?.['NODE_ENV'];
73
+ if (nodeEnv === 'production') return true;
74
+ return false;
75
+ }
76
+
77
+ const logFilePath = './logs/app.log';
78
+ let transport;
79
+ function pinoLogger() {
80
+ const isProd = isProductionAppEnvironment();
81
+ try {
82
+ // ensure logs folder exists
83
+ ensureDirExists(logFilePath);
84
+ transport = isProd ? pino.transport({
85
+ target: 'pino/file',
86
+ options: {
87
+ destination: logFilePath
88
+ }
89
+ }) : pino.transport({
90
+ targets: [{
91
+ target: 'pino-pretty',
92
+ options: {
93
+ colorize: true,
94
+ levelFirst: true,
95
+ translateTime: 'SYS:standard'
96
+ }
97
+ }]
98
+ });
99
+ } catch (err) {
100
+ console.error('⚠️ Logger file transport failed, falling back to stdout', err);
101
+ transport = undefined;
102
+ }
103
+ return pino({
104
+ level: process.env?.['PINO_LOG_LEVEL'] || 'info',
105
+ timestamp: pino.stdTimeFunctions.isoTime,
106
+ formatters: {
107
+ bindings: bindings => ({
108
+ pid: bindings?.['pid'],
109
+ host: bindings?.['hostname'],
110
+ node_version: process.version
111
+ })
112
+ },
113
+ redact: {
114
+ paths: ['req.headers.authorization', 'req.headers.cookie', 'req.body.password', 'req.body.confirmPassword', 'req.body.oldPassword', 'req.body.newPassword', 'req.body.token', 'req.body.refreshToken', 'req.body.accessToken', 'req.body.*.password', 'req.body.*.token', 'req.body.*.refreshToken', 'req.body.*.accessToken', 'res.body.token', 'res.body.refreshToken', 'res.body.accessToken', 'res.body.data.token', 'res.body.data.refreshToken', 'res.body.data.accessToken', 'res.body.*.token', 'res.body.*.refreshToken', 'res.body.*.accessToken'],
115
+ censor: '[REDACTED]'
116
+ }
117
+ }, transport);
118
+ }
119
+
120
+ const pinoLoggerMiddleware = () => {
121
+ return async (c, next) => {
122
+ const start = Date.now();
123
+ const logger = pinoLogger();
124
+ const childLogger = logger.child({
125
+ requestId: c.var['requestId'],
126
+ req: {
127
+ method: c.req.method,
128
+ path: c.req.path,
129
+ query: c.req.query(),
130
+ headers: c.req.header(),
131
+ body: c.req.method !== 'GET' ? await safeRequestBody(c.req) : undefined
132
+ }
133
+ });
134
+ c.set('logger', childLogger);
135
+ childLogger.info('Incoming request');
136
+ try {
137
+ await next();
138
+ const responseBody = await safeResponseBody(c.res);
139
+ childLogger.info({
140
+ res: {
141
+ status: c.res.status,
142
+ durationMs: Date.now() - start,
143
+ body: responseBody
144
+ }
145
+ }, 'Request completed');
146
+ } catch (err) {
147
+ if (err instanceof HTTPException) {
148
+ childLogger.warn({
149
+ res: {
150
+ status: err.status
151
+ },
152
+ error: err.message
153
+ }, 'HTTP error');
154
+ throw err;
155
+ }
156
+ childLogger.error({
157
+ err
158
+ }, 'Unhandled server error');
159
+ throw err;
160
+ }
161
+ };
162
+ };
163
+
164
+ const zPagination = z.object({
165
+ page: z.coerce.number().optional(),
166
+ size: z.coerce.number().optional()
167
+ });
168
+ const zCommonQuery = z.object({
169
+ search: z.string().optional(),
170
+ orderBy: z.string().optional(),
171
+ sortOrder: z.enum(['asc', 'desc']).optional()
172
+ }).extend(zPagination.shape);
173
+ const zId = z.object({
174
+ id: z.string()
175
+ });
176
+ const zIds = z.object({
177
+ ids: z.array(z.string()).min(1)
178
+ });
179
+ const zPassword = z.string().min(8).max(128).regex(/[A-Z]/, 'Must contain at least one uppercase letter').regex(/[0-9]/, 'Must contain at least one number').regex(/[^A-Za-z0-9\s]/, 'Must contain at least one special character');
180
+ const zEmpty = z.object({});
181
+ const zFile = z.object({
182
+ file: z.instanceof(File).openapi({
183
+ type: 'string',
184
+ format: 'binary'
185
+ }).optional(),
186
+ files: z.array(z.instanceof(File).openapi({
187
+ type: 'string',
188
+ format: 'binary'
189
+ })).optional()
190
+ });
191
+
192
+ // `argon2.Algorithm` is an ambient `const enum`, so its members cannot be
193
+ // accessed when `isolatedModules` is enabled. Argon2id === 2; typing the
194
+ // literal against the library's own option type keeps it type-safe.
195
+ const ARGON2ID = 2;
196
+ class CryptoService {
197
+ static async hash(plain) {
198
+ return argon2.hash(plain, {
199
+ algorithm: ARGON2ID,
200
+ memoryCost: 1 << 16,
201
+ // 64 MB
202
+ timeCost: 3,
203
+ parallelism: 1
204
+ });
205
+ }
206
+ static async verify(plain, hash) {
207
+ return argon2.verify(hash, plain);
208
+ }
209
+ static random64chars() {
210
+ // 32 bytes hex -> 64 hex chars
211
+ return crypto.randomBytes(32).toString('hex');
212
+ }
213
+ }
214
+
215
+ const zResponsePagination = z.object({
216
+ page: z.number(),
217
+ size: z.number(),
218
+ totalItems: z.number(),
219
+ totalPages: z.number()
220
+ });
221
+ function ApiResponse(dataSchema, description) {
222
+ return {
223
+ content: {
224
+ 'application/json': {
225
+ schema: z.object({
226
+ data: dataSchema,
227
+ message: z.string(),
228
+ success: z.boolean(),
229
+ error: z.any().optional(),
230
+ meta: z.any().optional()
231
+ })
232
+ }
233
+ },
234
+ description
235
+ };
236
+ }
237
+ function ApiListResponse(dataSchema, description) {
238
+ return {
239
+ content: {
240
+ 'application/json': {
241
+ schema: z.object({
242
+ data: dataSchema,
243
+ message: z.string(),
244
+ success: z.boolean(),
245
+ error: z.any().optional(),
246
+ meta: z.any().optional(),
247
+ pagination: zResponsePagination
248
+ })
249
+ }
250
+ },
251
+ description
252
+ };
253
+ }
254
+ // 1. Base Stream Schema
255
+ const zStreamResponse = z.object({
256
+ body: z.custom(val => val instanceof ReadableStream, {
257
+ message: 'Body must be a ReadableStream'
258
+ }),
259
+ headers: z.object({
260
+ 'Content-Type': z.string().default('application/octet-stream'),
261
+ 'Content-Disposition': z.string().refine(val => val.startsWith('inline; filename="'), {
262
+ message: 'Must follow format: inline; filename="..."'
263
+ })
264
+ }).optional()
265
+ });
266
+ // 2. Generic API Response for ReadableStream
267
+ function StreamResponse(description) {
268
+ return {
269
+ content: {
270
+ 'application/octet-stream': {
271
+ schema: zStreamResponse
272
+ }
273
+ },
274
+ description
275
+ };
276
+ }
277
+ function ApiPdfResponse(description) {
278
+ return {
279
+ content: {
280
+ 'application/pdf': {
281
+ schema: z.object({
282
+ format: z.literal('binary'),
283
+ type: z.literal('string')
284
+ })
285
+ }
286
+ },
287
+ description
288
+ };
289
+ }
290
+
291
+ function throwHttpError(error, defaultMessage = 'Something went wrong') {
292
+ const code = error instanceof Error ? Number.isNaN(error.cause) ? INTERNAL_SERVER_ERROR : error.cause : INTERNAL_SERVER_ERROR;
293
+ throw new HTTPException(code, {
294
+ message: error.message ?? defaultMessage
295
+ });
296
+ }
297
+ function isUniqueViolationPg(error) {
298
+ const e = error;
299
+ return e?.code === '23505'; // unique_violation
300
+ }
301
+
302
+ function toErrorCode(value) {
303
+ if (value === undefined || value === null || Number.isNaN(value)) {
304
+ return 500;
305
+ }
306
+ return typeof value === 'number' ? Math.max(value, 0) : 500;
307
+ }
308
+ function onErrorFn(debugLog = true) {
309
+ return async (err, c) => {
310
+ const statusCode = 'status' in err ? toErrorCode(err.status) : 'cause' in err ? toErrorCode(err.cause) : INTERNAL_SERVER_ERROR;
311
+ const logger = c.get('logger');
312
+ logger.error({
313
+ err,
314
+ path: c.req.path,
315
+ method: c.req.method
316
+ }, 'Request failed');
317
+ if (debugLog) {
318
+ console.error(JSON.stringify(err, undefined, 2));
319
+ }
320
+ return c.json({
321
+ data: null,
322
+ error: !debugLog ? undefined : err,
323
+ message: err.cause ?? err.message,
324
+ success: false
325
+ }, statusCode);
326
+ };
327
+ }
328
+
329
+ const notFoundFn = c => {
330
+ return c.json({
331
+ success: false,
332
+ error: 'Not Found',
333
+ data: {},
334
+ message: `Not Found - ${c.req.path}`
335
+ }, NOT_FOUND);
336
+ };
337
+
338
+ function buildPaginationResponse(page, size, total) {
339
+ const numPage = clamp(Number(page), 1) || 1;
340
+ const positivePage = numPage < 1 ? 1 : numPage;
341
+ const numSize = clamp(Number(size), DEFAULT_PAGE_SIZE);
342
+ const numTotal = clamp(Number(total), 0);
343
+ const totalPages = Math.ceil(numTotal / numSize);
344
+ return {
345
+ page: positivePage,
346
+ size: numSize,
347
+ totalItems: numTotal,
348
+ totalPages
349
+ };
350
+ }
351
+
352
+ class TokenUtil {
353
+ alg;
354
+ secret;
355
+ constructor(secret, alg = 'HS256') {
356
+ this.alg = alg;
357
+ this.secret = secret;
358
+ }
359
+ async create(payload, lifeSeconds = 15 * 60) {
360
+ if (payload.exp === undefined) {
361
+ // JWT NumericDate claims (`exp`) are seconds since the epoch.
362
+ payload.exp = Math.floor(Date.now() / 1000) + lifeSeconds;
363
+ }
364
+ return await sign(payload, this.secret, this.alg);
365
+ }
366
+ async decode(token) {
367
+ return await verify(token, this.secret, this.alg);
368
+ }
369
+ async decodeUnexpired(token) {
370
+ // Disable Hono's built-in `exp` check so an expired token resolves to
371
+ // `null` instead of throwing, then validate it here.
372
+ const {
373
+ exp,
374
+ ...rest
375
+ } = await verify(token, this.secret, {
376
+ alg: this.alg,
377
+ exp: false
378
+ });
379
+ const nowSeconds = Math.floor(Date.now() / 1000);
380
+ if (exp !== undefined && exp <= nowSeconds) return null;
381
+ return {
382
+ exp: exp,
383
+ ...rest
384
+ };
385
+ }
386
+ }
387
+
388
+ function validationHook(result, _c) {
389
+ if (!result.success) {
390
+ throwHttpError(new Error('Validation error', {
391
+ cause: UNPROCESSABLE_ENTITY
392
+ }));
393
+ }
394
+ }
395
+
396
+ export { ApiListResponse, ApiPdfResponse, ApiResponse, CryptoService, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, StreamResponse, TokenUtil, buildPaginationResponse, ensureDirExists, isProductionAppEnvironment, isUniqueViolationPg, notFoundFn, onErrorFn, pinoLogger, pinoLoggerMiddleware, safeRequestBody, safeResponseBody, throwHttpError, validationHook, zCommonQuery, zEmpty, zFile, zId, zIds, zPagination, zPassword, zResponsePagination, zStreamResponse };
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@mattrika/server-utils",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mattrika/libs"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "dependencies": {
13
+ "tslib": "^2.3.0"
14
+ },
15
+ "peerDependencies": {
16
+ "@hono/zod-openapi": "^1.6.3",
17
+ "@node-rs/argon2": "^2.2.2",
18
+ "es-toolkit": "^1.52.0",
19
+ "hono": "^4.13.13",
20
+ "pino": "^10.4.0",
21
+ "stoker": "^2.0.1",
22
+ "zod": "^4.6.7"
23
+ },
24
+ "exports": {
25
+ "./package.json": "./package.json",
26
+ ".": {
27
+ "module": "./index.esm.js",
28
+ "types": "./index.d.ts",
29
+ "import": "./index.cjs.mjs",
30
+ "default": "./index.cjs.js"
31
+ }
32
+ },
33
+ "module": "./index.esm.js",
34
+ "main": "./index.cjs.js",
35
+ "types": "./index.d.ts"
6
36
  }
package/src/index.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export * from './lib/constants/common.values';
2
+ export * from './lib/pino-logger/logger.helpers';
3
+ export * from './lib/pino-logger/pino-logger';
4
+ export * from './lib/pino-logger/pino-logger.middleware';
5
+ export * from './lib/predicates/is-prod';
6
+ export * from './lib/schemas/common.schema';
7
+ export * from './lib/services/crypto.service';
8
+ export * from './lib/utils/api-response.util';
9
+ export * from './lib/utils/error.util';
10
+ export * from './lib/utils/on-error';
11
+ export * from './lib/utils/on-not-found';
12
+ export * from './lib/utils/pagination.util';
13
+ export * from './lib/utils/token.util';
14
+ export * from './lib/utils/validation-hook';
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_PAGE_SIZE = 20;
2
+ export declare const MAX_PAGE_SIZE = 100;
@@ -0,0 +1,4 @@
1
+ import type { HonoRequest } from 'hono';
2
+ export declare function safeResponseBody(res: Response): Promise<any>;
3
+ export declare function safeRequestBody(req: HonoRequest): Promise<any>;
4
+ export declare function ensureDirExists(filePath: string): void;
@@ -0,0 +1,2 @@
1
+ import pino from 'pino';
2
+ export declare function pinoLogger(): pino.Logger<never, boolean>;
@@ -0,0 +1,2 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ export declare const pinoLoggerMiddleware: () => MiddlewareHandler;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Checks whether APP_ENV environment variable is set to 'production' or NODE_ENV environment variable is set to 'production', in that order.
3
+ * @returns boolean
4
+ */
5
+ export declare function isProductionAppEnvironment(): boolean;
@@ -0,0 +1,29 @@
1
+ import { z } from '@hono/zod-openapi';
2
+ export declare const zPagination: z.ZodObject<{
3
+ page: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
4
+ size: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
5
+ }, z.core.$strip>;
6
+ export declare const zCommonQuery: z.ZodObject<{
7
+ search: z.ZodOptional<z.ZodString>;
8
+ orderBy: z.ZodOptional<z.ZodString>;
9
+ sortOrder: z.ZodOptional<z.ZodEnum<{
10
+ asc: "asc";
11
+ desc: "desc";
12
+ }>>;
13
+ page: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
14
+ size: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
15
+ }, z.core.$strip>;
16
+ export declare const zId: z.ZodObject<{
17
+ id: z.ZodString;
18
+ }, z.core.$strip>;
19
+ export declare const zIds: z.ZodObject<{
20
+ ids: z.ZodArray<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ export declare const zPassword: z.ZodString;
23
+ export declare const zEmpty: z.ZodObject<{}, z.core.$strip>;
24
+ export declare const zFile: z.ZodObject<{
25
+ file: z.ZodOptional<z.ZodInstanceOf<File>>;
26
+ files: z.ZodOptional<z.ZodArray<z.ZodInstanceOf<File>>>;
27
+ }, z.core.$strip>;
28
+ export type CommonQueryParams = z.infer<typeof zCommonQuery>;
29
+ export type PaginationParams = z.infer<typeof zPagination>;
@@ -0,0 +1,5 @@
1
+ export declare class CryptoService {
2
+ static hash(plain: string): Promise<string>;
3
+ static verify(plain: string, hash: string): Promise<boolean>;
4
+ static random64chars(): string;
5
+ }
@@ -0,0 +1,76 @@
1
+ import { z } from '@hono/zod-openapi';
2
+ import { ZodAny, ZodArray, ZodBoolean, ZodNumber, ZodObject, ZodOptional, ZodString } from 'zod';
3
+ export type ZodSchema = ZodObject<any> | ZodArray<ZodObject<any>>;
4
+ export declare const zResponsePagination: z.ZodObject<{
5
+ page: z.ZodNumber;
6
+ size: z.ZodNumber;
7
+ totalItems: z.ZodNumber;
8
+ totalPages: z.ZodNumber;
9
+ }, z.core.$strip>;
10
+ export type ResponsePagination = z.infer<typeof zResponsePagination>;
11
+ export declare function ApiResponse<T extends ZodSchema>(dataSchema: T, description: string): {
12
+ content: {
13
+ 'application/json': {
14
+ schema: ZodObject<{
15
+ data: T;
16
+ message: ZodString;
17
+ success: ZodBoolean;
18
+ error: ZodOptional<ZodAny>;
19
+ meta: ZodOptional<ZodAny>;
20
+ }>;
21
+ };
22
+ };
23
+ description: string;
24
+ };
25
+ export declare function ApiListResponse<T extends ZodSchema>(dataSchema: T, description: string): {
26
+ content: {
27
+ 'application/json': {
28
+ schema: ZodObject<{
29
+ data: T;
30
+ message: ZodString;
31
+ success: ZodBoolean;
32
+ error: ZodOptional<ZodAny>;
33
+ meta: ZodOptional<ZodAny>;
34
+ pagination: ZodObject<{
35
+ page: ZodNumber;
36
+ size: ZodNumber;
37
+ totalItems: ZodNumber;
38
+ totalPages: ZodNumber;
39
+ }>;
40
+ }>;
41
+ };
42
+ };
43
+ description: string;
44
+ };
45
+ export declare const zStreamResponse: z.ZodObject<{
46
+ body: z.ZodCustom<ReadableStream<any>, ReadableStream<any>>;
47
+ headers: z.ZodOptional<z.ZodObject<{
48
+ 'Content-Type': z.ZodDefault<z.ZodString>;
49
+ 'Content-Disposition': z.ZodString;
50
+ }, z.core.$strip>>;
51
+ }, z.core.$strip>;
52
+ export declare function StreamResponse(description: string): {
53
+ content: {
54
+ 'application/octet-stream': {
55
+ schema: z.ZodObject<{
56
+ body: z.ZodCustom<ReadableStream<any>, ReadableStream<any>>;
57
+ headers: z.ZodOptional<z.ZodObject<{
58
+ 'Content-Type': z.ZodDefault<z.ZodString>;
59
+ 'Content-Disposition': z.ZodString;
60
+ }, z.core.$strip>>;
61
+ }, z.core.$strip>;
62
+ };
63
+ };
64
+ description: string;
65
+ };
66
+ export declare function ApiPdfResponse(description: string): {
67
+ content: {
68
+ 'application/pdf': {
69
+ schema: z.ZodObject<{
70
+ format: z.ZodLiteral<"binary">;
71
+ type: z.ZodLiteral<"string">;
72
+ }, z.core.$strip>;
73
+ };
74
+ };
75
+ description: string;
76
+ };
@@ -0,0 +1,2 @@
1
+ export declare function throwHttpError(error: unknown, defaultMessage?: string): never;
2
+ export declare function isUniqueViolationPg(error: unknown): boolean;
@@ -0,0 +1,2 @@
1
+ import { ErrorHandler } from 'hono';
2
+ export declare function onErrorFn(debugLog?: boolean): ErrorHandler;
@@ -0,0 +1,2 @@
1
+ import { NotFoundHandler } from 'hono';
2
+ export declare const notFoundFn: NotFoundHandler;
@@ -0,0 +1,2 @@
1
+ import { ResponsePagination } from './api-response.util';
2
+ export declare function buildPaginationResponse(page: number | undefined, size: number | undefined, total: number): ResponsePagination;
@@ -0,0 +1,10 @@
1
+ import type { SignatureAlgorithm } from 'hono/utils/jwt/jwa';
2
+ import type { JWTPayload } from 'hono/utils/jwt/types';
3
+ export declare class TokenUtil {
4
+ private alg;
5
+ private secret;
6
+ constructor(secret: string, alg?: SignatureAlgorithm);
7
+ create<T extends JWTPayload>(payload: T, lifeSeconds?: number): Promise<string>;
8
+ decode<T extends JWTPayload>(token: string): Promise<T | null>;
9
+ decodeUnexpired<T extends JWTPayload>(token: string): Promise<T | null>;
10
+ }
@@ -0,0 +1,2 @@
1
+ import { Context } from 'hono';
2
+ export declare function validationHook(result: any, _c: Context): void;