crudora 0.2.1 → 0.3.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/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Express } from 'express';
2
2
  import { z } from 'zod';
3
+ import http from 'http';
3
4
 
4
5
  /**
5
6
  * Base class for all Crudora models. Extend this to define your table schema,
@@ -297,20 +298,53 @@ interface CrudoraServerConfig {
297
298
  * - Omit / `undefined` (default): enabled with 100 requests per minute per IP.
298
299
  * - Pass a `RateLimitConfig` object to customise the window, limit, or key function.
299
300
  * - Pass `false` to disable rate limiting entirely (e.g. when using an external proxy-level limiter).
301
+ *
302
+ * **Important:** This limiter is in-memory and per-process. In multi-instance deployments
303
+ * (load balancer, Kubernetes replicas), each instance tracks its own counter independently —
304
+ * the effective limit per client is `max × instanceCount`. Use a Redis-backed limiter
305
+ * (e.g. `rate-limiter-flexible`) and pass `rateLimit: false` to disable this one.
300
306
  */
301
307
  rateLimit?: RateLimitConfig | false;
308
+ /**
309
+ * Socket-level request timeout in milliseconds.
310
+ * Requests that exceed this duration are terminated with a `503` response.
311
+ * - Omit / `0` (default): no timeout.
312
+ * - Recommended: `30_000` (30 s) for most APIs.
313
+ */
314
+ timeout?: number;
315
+ /**
316
+ * Built-in health check endpoint.
317
+ * - `true` (default): mounts `GET /health` returning `{ status: 'ok' }`.
318
+ * - `string`: custom path, e.g. `'/healthz'`.
319
+ * - `false`: disable entirely.
320
+ */
321
+ healthCheck?: boolean | string;
302
322
  }
303
323
  declare class CrudoraServer {
304
324
  private app;
305
325
  private crudora;
306
326
  private config;
327
+ private httpServer;
307
328
  constructor(config: CrudoraServerConfig);
308
329
  private setupMiddleware;
309
330
  registerModel(...modelClasses: ModelConstructor[]): this;
310
331
  registerTable<T extends Model>(modelClass: ModelConstructor<T>, table: any): this;
311
332
  generateRoutes(): this;
312
333
  use(middleware: any): this;
313
- listen(callback?: () => void): void;
334
+ /**
335
+ * Starts the HTTP server and returns the underlying `http.Server` instance.
336
+ * Use the returned server for graceful shutdown:
337
+ *
338
+ * @example
339
+ * const httpServer = server.listen();
340
+ * process.on('SIGTERM', () => httpServer.close(() => process.exit(0)));
341
+ */
342
+ listen(callback?: () => void): http.Server;
343
+ /**
344
+ * Returns the `http.Server` instance after `listen()` has been called, or `null` before.
345
+ * Useful when you need the server reference without calling listen again.
346
+ */
347
+ getHttpServer(): http.Server | null;
314
348
  getApp(): Express;
315
349
  getCrudora(): Crudora;
316
350
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Express } from 'express';
2
2
  import { z } from 'zod';
3
+ import http from 'http';
3
4
 
4
5
  /**
5
6
  * Base class for all Crudora models. Extend this to define your table schema,
@@ -297,20 +298,53 @@ interface CrudoraServerConfig {
297
298
  * - Omit / `undefined` (default): enabled with 100 requests per minute per IP.
298
299
  * - Pass a `RateLimitConfig` object to customise the window, limit, or key function.
299
300
  * - Pass `false` to disable rate limiting entirely (e.g. when using an external proxy-level limiter).
301
+ *
302
+ * **Important:** This limiter is in-memory and per-process. In multi-instance deployments
303
+ * (load balancer, Kubernetes replicas), each instance tracks its own counter independently —
304
+ * the effective limit per client is `max × instanceCount`. Use a Redis-backed limiter
305
+ * (e.g. `rate-limiter-flexible`) and pass `rateLimit: false` to disable this one.
300
306
  */
301
307
  rateLimit?: RateLimitConfig | false;
308
+ /**
309
+ * Socket-level request timeout in milliseconds.
310
+ * Requests that exceed this duration are terminated with a `503` response.
311
+ * - Omit / `0` (default): no timeout.
312
+ * - Recommended: `30_000` (30 s) for most APIs.
313
+ */
314
+ timeout?: number;
315
+ /**
316
+ * Built-in health check endpoint.
317
+ * - `true` (default): mounts `GET /health` returning `{ status: 'ok' }`.
318
+ * - `string`: custom path, e.g. `'/healthz'`.
319
+ * - `false`: disable entirely.
320
+ */
321
+ healthCheck?: boolean | string;
302
322
  }
303
323
  declare class CrudoraServer {
304
324
  private app;
305
325
  private crudora;
306
326
  private config;
327
+ private httpServer;
307
328
  constructor(config: CrudoraServerConfig);
308
329
  private setupMiddleware;
309
330
  registerModel(...modelClasses: ModelConstructor[]): this;
310
331
  registerTable<T extends Model>(modelClass: ModelConstructor<T>, table: any): this;
311
332
  generateRoutes(): this;
312
333
  use(middleware: any): this;
313
- listen(callback?: () => void): void;
334
+ /**
335
+ * Starts the HTTP server and returns the underlying `http.Server` instance.
336
+ * Use the returned server for graceful shutdown:
337
+ *
338
+ * @example
339
+ * const httpServer = server.listen();
340
+ * process.on('SIGTERM', () => httpServer.close(() => process.exit(0)));
341
+ */
342
+ listen(callback?: () => void): http.Server;
343
+ /**
344
+ * Returns the `http.Server` instance after `listen()` has been called, or `null` before.
345
+ * Useful when you need the server reference without calling listen again.
346
+ */
347
+ getHttpServer(): http.Server | null;
314
348
  getApp(): Express;
315
349
  getCrudora(): Crudora;
316
350
  /**
package/dist/index.js CHANGED
@@ -1446,6 +1446,7 @@ var Crudora = class {
1446
1446
  };
1447
1447
 
1448
1448
  // src/core/crudoraServer.ts
1449
+ import http from "http";
1449
1450
  import express from "express";
1450
1451
  import { randomUUID as randomUUID2 } from "crypto";
1451
1452
  function createRateLimiter(config) {
@@ -1492,6 +1493,7 @@ function createDefaultLogger() {
1492
1493
  }
1493
1494
  var CrudoraServer = class {
1494
1495
  constructor(config) {
1496
+ this.httpServer = null;
1495
1497
  const resolvedLogger = config.logger === void 0 ? createDefaultLogger() : config.logger;
1496
1498
  const resolvedRateLimit = config.rateLimit === false ? false : {
1497
1499
  windowMs: config.rateLimit?.windowMs ?? 6e4,
@@ -1505,6 +1507,8 @@ var CrudoraServer = class {
1505
1507
  bodyParser: true,
1506
1508
  bodyParserLimit: "100kb",
1507
1509
  basePath: "/api",
1510
+ timeout: 0,
1511
+ healthCheck: true,
1508
1512
  ...config,
1509
1513
  logger: resolvedLogger,
1510
1514
  rateLimit: resolvedRateLimit
@@ -1528,6 +1532,19 @@ var CrudoraServer = class {
1528
1532
  req.correlationId = randomUUID2();
1529
1533
  next();
1530
1534
  });
1535
+ if (this.config.timeout > 0) {
1536
+ const timeoutMs = this.config.timeout;
1537
+ this.app.use((_req, res, next) => {
1538
+ const timer = setTimeout(() => {
1539
+ if (!res.headersSent) {
1540
+ res.status(503).json({ success: false, error: { code: "TIMEOUT", message: "Request timed out" } });
1541
+ }
1542
+ }, timeoutMs);
1543
+ res.on("finish", () => clearTimeout(timer));
1544
+ res.on("close", () => clearTimeout(timer));
1545
+ next();
1546
+ });
1547
+ }
1531
1548
  if (this.config.rateLimit !== false) {
1532
1549
  this.app.use(createRateLimiter(this.config.rateLimit));
1533
1550
  }
@@ -1569,6 +1586,12 @@ var CrudoraServer = class {
1569
1586
  return this;
1570
1587
  }
1571
1588
  generateRoutes() {
1589
+ if (this.config.healthCheck !== false) {
1590
+ const healthPath = typeof this.config.healthCheck === "string" ? this.config.healthCheck : "/health";
1591
+ this.app.get(healthPath, (_req, res) => {
1592
+ res.json({ success: true, data: { status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() } });
1593
+ });
1594
+ }
1572
1595
  this.crudora.generateRoutes(this.app, this.config.basePath);
1573
1596
  return this;
1574
1597
  }
@@ -1576,12 +1599,29 @@ var CrudoraServer = class {
1576
1599
  this.app.use(middleware);
1577
1600
  return this;
1578
1601
  }
1602
+ /**
1603
+ * Starts the HTTP server and returns the underlying `http.Server` instance.
1604
+ * Use the returned server for graceful shutdown:
1605
+ *
1606
+ * @example
1607
+ * const httpServer = server.listen();
1608
+ * process.on('SIGTERM', () => httpServer.close(() => process.exit(0)));
1609
+ */
1579
1610
  listen(callback) {
1580
- this.app.listen(this.config.port, () => {
1611
+ this.httpServer = http.createServer(this.app);
1612
+ this.httpServer.listen(this.config.port, () => {
1581
1613
  console.log(`\u{1F680} Crudora server running on port ${this.config.port}`);
1582
1614
  console.log(`\u{1F4DA} API available at http://localhost:${this.config.port}${this.config.basePath}`);
1583
1615
  if (callback) callback();
1584
1616
  });
1617
+ return this.httpServer;
1618
+ }
1619
+ /**
1620
+ * Returns the `http.Server` instance after `listen()` has been called, or `null` before.
1621
+ * Useful when you need the server reference without calling listen again.
1622
+ */
1623
+ getHttpServer() {
1624
+ return this.httpServer;
1585
1625
  }
1586
1626
  getApp() {
1587
1627
  return this.app;