lacis 0.2.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/LICENSE +21 -0
- package/README.md +395 -0
- package/dist/adapters/index.d.ts +14 -0
- package/dist/adapters/index.js +1 -0
- package/dist/chunk-NVNSYLVY.js +2075 -0
- package/dist/cli/index.js +158 -0
- package/dist/index-rE4kFMlu.d.ts +192 -0
- package/dist/index.d.ts +343 -0
- package/dist/index.js +457 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { router, loadRoutes, primaryLog, getAdapter } from './chunk-NVNSYLVY.js';
|
|
2
|
+
export { addMiddleware, addPathMiddleware, collectMiddleware, createCorsMiddleware, createSSEClient, findRoute, getPathMiddlewares, getRouterStats, getRoutesDir, hasMiddlewares, initSSE, isRouteError, loadMiddlewares, loadRoutes, registerCorsConfig, registerMiddlewareConfig, registerRoutes, resetMiddlewares, resetRouter, router, runMiddlewares, send, sendEvent, sendJson, setVerboseLogging, sseClose, sseComment, sseEventError, sseId, sseRetry } from './chunk-NVNSYLVY.js';
|
|
3
|
+
import cluster from 'cluster';
|
|
4
|
+
|
|
5
|
+
// src/config/serverConfig.ts
|
|
6
|
+
var defaultConfig = {
|
|
7
|
+
port: 3e3,
|
|
8
|
+
isDev: process.env.NODE_ENV === "development",
|
|
9
|
+
timeout: 3e4,
|
|
10
|
+
cluster: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
workers: void 0
|
|
13
|
+
},
|
|
14
|
+
platform: "node"
|
|
15
|
+
};
|
|
16
|
+
function getConfig(customConfig = {}) {
|
|
17
|
+
return {
|
|
18
|
+
...defaultConfig,
|
|
19
|
+
...customConfig
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/core/defineHandler.ts
|
|
24
|
+
async function runValidation(schema, value) {
|
|
25
|
+
const result = await schema["~standard"].validate(value);
|
|
26
|
+
if (result.issues) return { success: false, issues: result.issues };
|
|
27
|
+
return { success: true, data: result.value };
|
|
28
|
+
}
|
|
29
|
+
function formatIssues(issues) {
|
|
30
|
+
return Array.from(issues).map((issue) => ({
|
|
31
|
+
message: issue.message,
|
|
32
|
+
path: issue.path ? Array.from(issue.path).map((p) => typeof p === "object" && "key" in p ? p.key : p) : void 0
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function defineHandler(config) {
|
|
36
|
+
const wrapped = async (req, res) => {
|
|
37
|
+
if (config.params) {
|
|
38
|
+
const result = await runValidation(config.params, req.params ?? {});
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
res.status(400).json({ error: "Validation failed", issues: formatIssues(result.issues) });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
req.params = result.data;
|
|
44
|
+
}
|
|
45
|
+
if (config.query) {
|
|
46
|
+
const result = await runValidation(config.query, req.query ?? {});
|
|
47
|
+
if (!result.success) {
|
|
48
|
+
res.status(400).json({ error: "Validation failed", issues: formatIssues(result.issues) });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
req.query = result.data;
|
|
52
|
+
}
|
|
53
|
+
if (config.body) {
|
|
54
|
+
let rawBody;
|
|
55
|
+
try {
|
|
56
|
+
rawBody = await req.json();
|
|
57
|
+
} catch {
|
|
58
|
+
res.status(400).json({ error: "Invalid JSON body" });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const result = await runValidation(config.body, rawBody);
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
res.status(400).json({ error: "Validation failed", issues: formatIssues(result.issues) });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
req.body = result.data;
|
|
67
|
+
}
|
|
68
|
+
await config.handler(req, res);
|
|
69
|
+
};
|
|
70
|
+
wrapped._defineHandler = config;
|
|
71
|
+
return wrapped;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/core/openapi.ts
|
|
75
|
+
async function schemaToJsonSchema(schema) {
|
|
76
|
+
const vendor = schema?.["~standard"]?.vendor;
|
|
77
|
+
if (!vendor) return null;
|
|
78
|
+
try {
|
|
79
|
+
if (vendor === "zod") {
|
|
80
|
+
const zod = await import('zod');
|
|
81
|
+
if (typeof zod.toJSONSchema === "function") return zod.toJSONSchema(schema);
|
|
82
|
+
const mod = await import('zod-to-json-schema');
|
|
83
|
+
return (mod.zodToJsonSchema ?? mod.default)(schema, { target: "openApi3" });
|
|
84
|
+
}
|
|
85
|
+
if (vendor === "valibot") {
|
|
86
|
+
const mod = await import('@valibot/to-json-schema');
|
|
87
|
+
return (mod.toJsonSchema ?? mod.default)(schema);
|
|
88
|
+
}
|
|
89
|
+
if (vendor === "arktype") {
|
|
90
|
+
return schema.toJsonSchema();
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
primaryLog(`[openapi] no converter found for "${vendor}" \u2014 install the matching json-schema package`);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function toOpenApiPath(path) {
|
|
98
|
+
return path.replace(/:(\w+)\??/g, "{$1}");
|
|
99
|
+
}
|
|
100
|
+
async function buildOperation(method, config) {
|
|
101
|
+
const op = {
|
|
102
|
+
responses: { "200": { description: "Success" } }
|
|
103
|
+
};
|
|
104
|
+
if (config.meta?.summary) op.summary = config.meta.summary;
|
|
105
|
+
if (config.meta?.description) op.description = config.meta.description;
|
|
106
|
+
if (config.meta?.tags) op.tags = config.meta.tags;
|
|
107
|
+
if (config.meta?.deprecated) op.deprecated = config.meta.deprecated;
|
|
108
|
+
const parameters = [];
|
|
109
|
+
if (config.params) {
|
|
110
|
+
const jsonSchema = await schemaToJsonSchema(config.params);
|
|
111
|
+
if (jsonSchema?.properties) {
|
|
112
|
+
for (const [name, propSchema] of Object.entries(jsonSchema.properties)) {
|
|
113
|
+
parameters.push({ name, in: "path", required: true, schema: propSchema });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (config.query) {
|
|
118
|
+
const jsonSchema = await schemaToJsonSchema(config.query);
|
|
119
|
+
if (jsonSchema?.properties) {
|
|
120
|
+
const required = jsonSchema.required ?? [];
|
|
121
|
+
for (const [name, propSchema] of Object.entries(jsonSchema.properties)) {
|
|
122
|
+
parameters.push({ name, in: "query", required: required.includes(name), schema: propSchema });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (parameters.length > 0) op.parameters = parameters;
|
|
127
|
+
if (config.body) {
|
|
128
|
+
const jsonSchema = await schemaToJsonSchema(config.body);
|
|
129
|
+
if (jsonSchema) {
|
|
130
|
+
op.requestBody = {
|
|
131
|
+
required: true,
|
|
132
|
+
content: { "application/json": { schema: jsonSchema } }
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return op;
|
|
137
|
+
}
|
|
138
|
+
async function buildOpenApiDoc(config) {
|
|
139
|
+
const routes = router.getRoutes();
|
|
140
|
+
const paths = {};
|
|
141
|
+
for (const { method, path, handler } of routes) {
|
|
142
|
+
const defineConfig = handler._defineHandler;
|
|
143
|
+
const openApiPath = toOpenApiPath(path);
|
|
144
|
+
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
145
|
+
paths[openApiPath][method.toLowerCase()] = defineConfig ? await buildOperation(method, defineConfig) : { responses: { "200": { description: "Success" } } };
|
|
146
|
+
}
|
|
147
|
+
return { openapi: "3.1.0", info: config.info, paths };
|
|
148
|
+
}
|
|
149
|
+
var serverInstance = null;
|
|
150
|
+
var isShuttingDown = false;
|
|
151
|
+
var shutdownListenersRegistered = false;
|
|
152
|
+
async function createServer(routesDir, config = defaultConfig) {
|
|
153
|
+
const { platform = "node" } = config;
|
|
154
|
+
const verbose = config.isDev && cluster.isPrimary && !process.env.ZENO_BUN_WORKER;
|
|
155
|
+
try {
|
|
156
|
+
await loadRoutes(routesDir);
|
|
157
|
+
if (config.openapi) {
|
|
158
|
+
const doc = await buildOpenApiDoc(config.openapi);
|
|
159
|
+
const openapiPath = config.openapi.path ?? "/openapi.json";
|
|
160
|
+
router.addRoute("GET", openapiPath, (_req, res) => res.json(doc));
|
|
161
|
+
if (verbose) primaryLog(`OpenAPI doc available at ${openapiPath}`);
|
|
162
|
+
}
|
|
163
|
+
if (verbose) {
|
|
164
|
+
primaryLog("\u{1F680} Serveur d\xE9marr\xE9");
|
|
165
|
+
primaryLog(`\u{1F4C2} Routes charg\xE9es depuis: ${routesDir}`);
|
|
166
|
+
}
|
|
167
|
+
if (config.isDev) {
|
|
168
|
+
if (verbose) {
|
|
169
|
+
primaryLog("\u{1F525} Mode de d\xE9veloppement activ\xE9");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const adapter = getAdapter(platform);
|
|
173
|
+
const handler = adapter.createHandler(routesDir);
|
|
174
|
+
let server;
|
|
175
|
+
switch (platform) {
|
|
176
|
+
case "node":
|
|
177
|
+
server = await handler(config);
|
|
178
|
+
serverInstance = server;
|
|
179
|
+
break;
|
|
180
|
+
case "bun":
|
|
181
|
+
server = handler(config);
|
|
182
|
+
break;
|
|
183
|
+
case "vercel":
|
|
184
|
+
case "netlify":
|
|
185
|
+
server = handler;
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
throw new Error(`Plateforme "${platform}" non support\xE9e`);
|
|
189
|
+
}
|
|
190
|
+
setupGracefulShutdown();
|
|
191
|
+
return server;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (verbose) {
|
|
194
|
+
primaryLog("\u274C Erreur lors de la cr\xE9ation du serveur:", error);
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function setupGracefulShutdown() {
|
|
200
|
+
if (!cluster.isPrimary) return;
|
|
201
|
+
if (shutdownListenersRegistered) return;
|
|
202
|
+
shutdownListenersRegistered = true;
|
|
203
|
+
const shutdown = async (signal) => {
|
|
204
|
+
if (isShuttingDown) return;
|
|
205
|
+
isShuttingDown = true;
|
|
206
|
+
primaryLog(`
|
|
207
|
+
Signal ${signal} re\xE7u, arr\xEAt en cours...`);
|
|
208
|
+
if (serverInstance && typeof serverInstance.close === "function") {
|
|
209
|
+
await new Promise((resolve) => {
|
|
210
|
+
serverInstance.close(() => resolve());
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
primaryLog("Fermeture forc\xE9e apr\xE8s d\xE9lai");
|
|
213
|
+
resolve();
|
|
214
|
+
}, 3e3);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
process.exit(0);
|
|
218
|
+
};
|
|
219
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
220
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
221
|
+
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/core/errors.ts
|
|
225
|
+
var HTTP_STATUS = {
|
|
226
|
+
OK: 200,
|
|
227
|
+
CREATED: 201,
|
|
228
|
+
NO_CONTENT: 204,
|
|
229
|
+
BAD_REQUEST: 400,
|
|
230
|
+
UNAUTHORIZED: 401,
|
|
231
|
+
FORBIDDEN: 403,
|
|
232
|
+
NOT_FOUND: 404,
|
|
233
|
+
METHOD_NOT_ALLOWED: 405,
|
|
234
|
+
CONFLICT: 409,
|
|
235
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
236
|
+
TOO_MANY_REQUESTS: 429,
|
|
237
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
238
|
+
SERVICE_UNAVAILABLE: 503,
|
|
239
|
+
GATEWAY_TIMEOUT: 504
|
|
240
|
+
};
|
|
241
|
+
var DEFAULT_ERROR_MESSAGES = {
|
|
242
|
+
400: "Bad Request",
|
|
243
|
+
401: "Unauthorized",
|
|
244
|
+
403: "Forbidden",
|
|
245
|
+
404: "Not Found",
|
|
246
|
+
405: "Method Not Allowed",
|
|
247
|
+
409: "Conflict",
|
|
248
|
+
422: "Unprocessable Entity",
|
|
249
|
+
429: "Too Many Requests",
|
|
250
|
+
500: "Internal Server Error",
|
|
251
|
+
503: "Service Unavailable",
|
|
252
|
+
504: "Gateway Timeout"
|
|
253
|
+
};
|
|
254
|
+
function createHttpError(options) {
|
|
255
|
+
const code = options.code || HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
256
|
+
const message = options.message || DEFAULT_ERROR_MESSAGES[code] || "Unknown Error";
|
|
257
|
+
const name = options.name || `HttpError${code}`;
|
|
258
|
+
const stackCapture = new Error(message);
|
|
259
|
+
Error.captureStackTrace?.(stackCapture, createHttpError);
|
|
260
|
+
return {
|
|
261
|
+
name,
|
|
262
|
+
code,
|
|
263
|
+
message,
|
|
264
|
+
details: options.details,
|
|
265
|
+
expose: options.expose !== void 0 ? options.expose : code < 500,
|
|
266
|
+
log: options.log !== void 0 ? options.log : code >= 500,
|
|
267
|
+
stack: stackCapture.stack
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function errorToJSON(error) {
|
|
271
|
+
const result = {
|
|
272
|
+
error: error.message,
|
|
273
|
+
code: error.code
|
|
274
|
+
};
|
|
275
|
+
if (error.expose && error.details) {
|
|
276
|
+
return { ...result, details: error.details };
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
function sendError(error, res) {
|
|
281
|
+
if (!res.headersSent) {
|
|
282
|
+
res.statusCode = error.code;
|
|
283
|
+
res.setHeader("Content-Type", "application/json");
|
|
284
|
+
res.end(JSON.stringify(errorToJSON(error)));
|
|
285
|
+
}
|
|
286
|
+
if (error.log) {
|
|
287
|
+
logError(error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function logError(error) {
|
|
291
|
+
const logData = {
|
|
292
|
+
name: error.name,
|
|
293
|
+
message: error.message,
|
|
294
|
+
code: error.code,
|
|
295
|
+
details: error.details,
|
|
296
|
+
stack: error.stack
|
|
297
|
+
};
|
|
298
|
+
if (error.code >= 500) {
|
|
299
|
+
primaryLog("\u274C ERROR:", JSON.stringify(logData, null, 2));
|
|
300
|
+
} else {
|
|
301
|
+
primaryLog("\u26A0\uFE0F WARNING:", JSON.stringify(logData, null, 2));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function createBadRequestError(message, details) {
|
|
305
|
+
return createHttpError({
|
|
306
|
+
name: "BadRequestError",
|
|
307
|
+
code: HTTP_STATUS.BAD_REQUEST,
|
|
308
|
+
message,
|
|
309
|
+
details
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function createUnauthorizedError(message, details) {
|
|
313
|
+
return createHttpError({
|
|
314
|
+
name: "UnauthorizedError",
|
|
315
|
+
code: HTTP_STATUS.UNAUTHORIZED,
|
|
316
|
+
message,
|
|
317
|
+
details
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function createForbiddenError(message, details) {
|
|
321
|
+
return createHttpError({
|
|
322
|
+
name: "ForbiddenError",
|
|
323
|
+
code: HTTP_STATUS.FORBIDDEN,
|
|
324
|
+
message,
|
|
325
|
+
details
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function createNotFoundError(message, details) {
|
|
329
|
+
return createHttpError({
|
|
330
|
+
name: "NotFoundError",
|
|
331
|
+
code: HTTP_STATUS.NOT_FOUND,
|
|
332
|
+
message,
|
|
333
|
+
details
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function createMethodNotAllowedError(message, details) {
|
|
337
|
+
return createHttpError({
|
|
338
|
+
name: "MethodNotAllowedError",
|
|
339
|
+
code: HTTP_STATUS.METHOD_NOT_ALLOWED,
|
|
340
|
+
message,
|
|
341
|
+
details
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function createConflictError(message, details) {
|
|
345
|
+
return createHttpError({
|
|
346
|
+
name: "ConflictError",
|
|
347
|
+
code: HTTP_STATUS.CONFLICT,
|
|
348
|
+
message,
|
|
349
|
+
details
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
function createValidationError(message, details) {
|
|
353
|
+
return createHttpError({
|
|
354
|
+
name: "ValidationError",
|
|
355
|
+
code: HTTP_STATUS.UNPROCESSABLE_ENTITY,
|
|
356
|
+
message,
|
|
357
|
+
details,
|
|
358
|
+
expose: true
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function createRateLimitError(message, details) {
|
|
362
|
+
return createHttpError({
|
|
363
|
+
name: "RateLimitError",
|
|
364
|
+
code: HTTP_STATUS.TOO_MANY_REQUESTS,
|
|
365
|
+
message,
|
|
366
|
+
details
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
function createInternalServerError(message, details) {
|
|
370
|
+
return createHttpError({
|
|
371
|
+
name: "InternalServerError",
|
|
372
|
+
code: HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
373
|
+
message,
|
|
374
|
+
details
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function createServiceUnavailableError(message, details) {
|
|
378
|
+
return createHttpError({
|
|
379
|
+
name: "ServiceUnavailableError",
|
|
380
|
+
code: HTTP_STATUS.SERVICE_UNAVAILABLE,
|
|
381
|
+
message,
|
|
382
|
+
details
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function createGatewayTimeoutError(message, details) {
|
|
386
|
+
return createHttpError({
|
|
387
|
+
name: "GatewayTimeoutError",
|
|
388
|
+
code: HTTP_STATUS.GATEWAY_TIMEOUT,
|
|
389
|
+
message,
|
|
390
|
+
details
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function normalizeError(error) {
|
|
394
|
+
if (error && typeof error === "object" && typeof error.code === "number" && "message" in error) {
|
|
395
|
+
return error;
|
|
396
|
+
}
|
|
397
|
+
const message = error?.message || "Unknown error occurred";
|
|
398
|
+
const details = error?.stack ? { stack: error.stack } : void 0;
|
|
399
|
+
const errorCode = error?.code || error?.statusCode;
|
|
400
|
+
if (errorCode === "ECONNREFUSED" || errorCode === "ENOTFOUND") {
|
|
401
|
+
return createServiceUnavailableError(
|
|
402
|
+
`Service connection failed: ${message}`,
|
|
403
|
+
details
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (errorCode === "ETIMEDOUT") {
|
|
407
|
+
return createGatewayTimeoutError(`Request timed out: ${message}`, details);
|
|
408
|
+
}
|
|
409
|
+
if (typeof errorCode === "number" && errorCode >= 400 && errorCode < 600) {
|
|
410
|
+
return createHttpError({
|
|
411
|
+
code: errorCode,
|
|
412
|
+
message,
|
|
413
|
+
details
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return createInternalServerError(message, details);
|
|
417
|
+
}
|
|
418
|
+
function isHttpError(error) {
|
|
419
|
+
return !!(error && typeof error === "object" && typeof error.code === "number" && "message" in error && "name" in error);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/core/rateLimit.ts
|
|
423
|
+
function createRateLimit(options = {}) {
|
|
424
|
+
const windowMs = options.windowMs ?? 6e4;
|
|
425
|
+
const max = options.max ?? 100;
|
|
426
|
+
const message = options.message ?? "Too Many Requests";
|
|
427
|
+
const keyGenerator = options.keyGenerator ?? ((req) => {
|
|
428
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
429
|
+
return (typeof forwarded === "string" ? forwarded.split(",")[0].trim() : req.socket?.remoteAddress) ?? "unknown";
|
|
430
|
+
});
|
|
431
|
+
const store = /* @__PURE__ */ new Map();
|
|
432
|
+
return (req, res) => {
|
|
433
|
+
const key = keyGenerator(req);
|
|
434
|
+
const now = Date.now();
|
|
435
|
+
let entry = store.get(key);
|
|
436
|
+
if (!entry || now >= entry.resetAt) {
|
|
437
|
+
entry = { count: 0, resetAt: now + windowMs };
|
|
438
|
+
store.set(key, entry);
|
|
439
|
+
}
|
|
440
|
+
entry.count++;
|
|
441
|
+
const remaining = Math.max(0, max - entry.count);
|
|
442
|
+
const resetSecs = Math.ceil(entry.resetAt / 1e3);
|
|
443
|
+
res.setHeader("X-RateLimit-Limit", String(max));
|
|
444
|
+
res.setHeader("X-RateLimit-Remaining", String(remaining));
|
|
445
|
+
res.setHeader("X-RateLimit-Reset", String(resetSecs));
|
|
446
|
+
if (entry.count > max) {
|
|
447
|
+
res.setHeader(
|
|
448
|
+
"Retry-After",
|
|
449
|
+
String(Math.ceil((entry.resetAt - now) / 1e3))
|
|
450
|
+
);
|
|
451
|
+
sendError(createRateLimitError(message), res);
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export { HTTP_STATUS, buildOpenApiDoc, createBadRequestError, createConflictError, createForbiddenError, createGatewayTimeoutError, createHttpError, createInternalServerError, createMethodNotAllowedError, createNotFoundError, createRateLimit, createRateLimitError, createServer, createServiceUnavailableError, createUnauthorizedError, createValidationError, defaultConfig, defineHandler, getConfig, isHttpError, logError, normalizeError, sendError };
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lacis",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Zero-dependency TypeScript web framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "index.ts",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./adapters": {
|
|
15
|
+
"import": "./dist/adapters/index.js",
|
|
16
|
+
"types": "./dist/adapters/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/core": "^7.29.0",
|
|
24
|
+
"@babel/preset-env": "^7.29.5",
|
|
25
|
+
"@types/bun": "latest",
|
|
26
|
+
"@types/jest": "^29.5.14",
|
|
27
|
+
"@types/node": "^22.13.5",
|
|
28
|
+
"@types/supertest": "^7.2.0",
|
|
29
|
+
"@valibot/to-json-schema": "^1.7.0",
|
|
30
|
+
"arktype": "^2.2.0",
|
|
31
|
+
"babel-jest": "^29.7.0",
|
|
32
|
+
"jest": "^29.7.0",
|
|
33
|
+
"supertest": "^7.2.2",
|
|
34
|
+
"ts-jest": "^29.2.6",
|
|
35
|
+
"tsup": "^8.5.1",
|
|
36
|
+
"valibot": "^1.4.0",
|
|
37
|
+
"zod": "^4.4.3",
|
|
38
|
+
"zod-to-json-schema": "^3.25.2"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@types/node": ">=18",
|
|
42
|
+
"typescript": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"typescript": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@types/node": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"bin": {
|
|
53
|
+
"lacis": "dist/cli/index.js"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"build:watch": "tsup --watch",
|
|
58
|
+
"test": "jest",
|
|
59
|
+
"test:unit": "jest --selectProjects unit",
|
|
60
|
+
"test:integration": "jest --selectProjects integration"
|
|
61
|
+
}
|
|
62
|
+
}
|