sleepy-serv 0.7.0 → 0.8.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/README.md CHANGED
@@ -26,9 +26,25 @@ The parameter for `import.meta.dirname` can be any directory you prefer, but it'
26
26
 
27
27
  ### Return Value
28
28
 
29
- `sleepy-serv` was originally built for NodeJS, but it was ported to `bun` recently (before the initial release). The `createApp()` function merely calls `Bun.serve()` under-the-hood, and returns the `app` object that contains two properties:
29
+ `sleepy-serv` was originally built for NodeJS, but it was ported to `bun` recently (before the initial release). The `createApp()` function merely calls `Bun.serve()` under-the-hood, and returns the `app` object that contains these properties:
30
30
  - `routes`: Contains a list of all of the routes defined by the file structure. This is useful for debugging.
31
- - `server`: this is the object that's returned from `Bun.serve()`. The `server` object has an `async` `.stop()` method on it, which can also be used for graceful shutdowns.
31
+ - `server`: this is the object that's returned from `Bun.serve()`. The `server` object has an `async` `.stop()` method on it, but prefer `app.close()` (see [Shutting Down](#shutting-down)), which stops the server and releases everything else the app holds.
32
+ - `commands`: WebSocket helpers for pushing messages out to connected clients: `send(clientId, event, body)` and `broadcast(event, body)`.
33
+ - `close`: an `async` function that shuts the app down. See [Shutting Down](#shutting-down).
34
+
35
+ ### Shutting Down
36
+
37
+ `app.close()` shuts the app down and releases everything it holds:
38
+
39
+ ```js
40
+ const app = await createApp(PORT, import.meta.dirname)
41
+
42
+ await app.close()
43
+ ```
44
+
45
+ It stops the server, tears down the Ctrl+D shutdown handler, and calls [`onClose`](#onclose). Pass `close(true)` to force-close active connections rather than waiting for in-flight requests to finish. That argument is handed straight to `server.stop()`, so it behaves exactly as it does in `Bun.serve()`.
46
+
47
+ This matters most for tests that start and stop an app per case. Calling `server.stop()` on its own stops the HTTP listener but leaves the Ctrl+D shutdown handler attached to `stdin`, which holds an open handle and can keep the process from exiting. `app.close()` does not have that problem.
32
48
 
33
49
  ### Adding Routes
34
50
 
@@ -465,7 +481,7 @@ Yields these routes:
465
481
 
466
482
  ### `onClose`
467
483
 
468
- When the app is started, the app can be shutdown gracefully by pressing Ctrl+D in the terminal. The `onClose` hook will be called during that shutdown if it's defined. `onClose` can also be `async` as well.
484
+ When the app is started, the app can be shutdown gracefully by pressing Ctrl+D in the terminal. That handler is only wired up when `stdin` is a TTY, so it's skipped in CI, in test runners, and anywhere `stdin` is piped. The `onClose` hook will be called during that shutdown if it's defined, and it's also called by [`app.close()`](#shutting-down). `onClose` can also be `async` as well.
469
485
 
470
486
  ```js
471
487
  const app = await createApp(PORT, import.meta.dirname, {
package/dist/index.d.ts CHANGED
@@ -7,10 +7,12 @@ export type { AppOptions, EndpointRequest, FormattedError, Middleware, NextFn, R
7
7
  export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
8
8
  export type { SocketCommands } from './socket';
9
9
  type OutputRoutes = Record<string, string[]>;
10
+ type CloseFn = (force?: boolean) => Promise<void>;
10
11
  export type App = {
11
12
  server: Server;
12
13
  commands: SocketCommands;
13
14
  routes: OutputRoutes;
15
+ close: CloseFn;
14
16
  };
15
17
  export declare function createApp(port: number, rootPath: string, opts?: AppOptions): Promise<App>;
16
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAIV,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AAExB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhD,YAAY,EACV,UAAU,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,MAAM,EACN,OAAO,EACP,MAAM,EACN,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AA4C5C,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,YAAY,CAAA;CACrB,CAAA;AA6YD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,GAAG,CAAC,CAad"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAIV,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,cAAc,UAAU,CAAA;AAExB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhD,YAAY,EACV,UAAU,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,MAAM,EACN,OAAO,EACP,MAAM,EACN,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AA4C5C,KAAK,OAAO,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEjD,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AA4ZD,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,GAAG,CAAC,CAad"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A more opinionated web server for building REST-ful applications",
4
4
  "author": "Travis J True",
5
5
  "license": "MIT",
6
- "version": "0.7.0",
6
+ "version": "0.8.0",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": {
package/src/index.ts CHANGED
@@ -109,10 +109,13 @@ type AppRoutes = {
109
109
  socket: SocketRoute[]
110
110
  }
111
111
 
112
+ type CloseFn = (force?: boolean) => Promise<void>
113
+
112
114
  export type App = {
113
115
  server: Server
114
116
  commands: SocketCommands
115
117
  routes: OutputRoutes
118
+ close: CloseFn
116
119
  }
117
120
 
118
121
  const ALLOWED_FILES_META = ['meta.js', 'meta.ts']
@@ -132,16 +135,6 @@ const ALLOWED_FILES_METHODS = [
132
135
  'delete.ts',
133
136
  ]
134
137
 
135
- /* istanbul ignore if */
136
- if (process.stdin.isTTY) {
137
- process.stdin.setRawMode(true)
138
- }
139
-
140
- const rl = readline.createInterface({
141
- input: stdin,
142
- output: stdout,
143
- })
144
-
145
138
  function methodNotAllowedHandler (_req: unknown): never {
146
139
  throw new MethodNotAllowedError()
147
140
  }
@@ -382,8 +375,8 @@ function buildSocketRoutes (mergedRoutes: ChainRoute[]): SocketRoute[] {
382
375
  }))
383
376
  }
384
377
 
385
- function buildModuleRoutes (routePaths: SocketRoute[]): ModuleRoute[] {
386
- return routePaths.map(route => {
378
+ function buildModuleRoutes (socketRoutes: SocketRoute[]): ModuleRoute[] {
379
+ return socketRoutes.map(route => {
387
380
  const handler: EndpointHandler = async (bunReq, server) => {
388
381
  const req = buildEndpointRequest(bunReq, server)
389
382
 
@@ -494,7 +487,7 @@ function processIO (
494
487
  port: number,
495
488
  server: Server,
496
489
  opts: AppOptions,
497
- ): void {
490
+ ): CloseFn {
498
491
  const onClose = opts.onClose || (() => {})
499
492
 
500
493
  console.info(`Running on port: ${port}`)
@@ -502,12 +495,37 @@ function processIO (
502
495
  console.info('Press Ctrl+D to gracefully shutdown')
503
496
  console.info('')
504
497
 
505
- /* istanbul ignore next */
506
- rl.on('close', async () => {
507
- await server.stop()
498
+ const shutdown = async (force = false): Promise<void> => {
499
+ await server.stop(force)
508
500
  await onClose()
509
- process.exit(0)
501
+ }
502
+
503
+ if (!stdin.isTTY) {
504
+ return shutdown
505
+ }
506
+
507
+ stdin.setRawMode(true)
508
+
509
+ const rl = readline.createInterface({
510
+ input: stdin,
511
+ output: stdout,
510
512
  })
513
+
514
+ /* istanbul ignore next */
515
+ const handleClose = async (): Promise<void> => {
516
+ await shutdown()
517
+ process.exit(0)
518
+ }
519
+
520
+ rl.on('close', handleClose)
521
+
522
+ return async (force = false) => {
523
+ rl.off('close', handleClose)
524
+ rl.close()
525
+ stdin.setRawMode(false)
526
+
527
+ await shutdown(force)
528
+ }
511
529
  }
512
530
 
513
531
  export async function createApp (
@@ -519,12 +537,12 @@ export async function createApp (
519
537
  const routes = await buildRoutes(rootPath, state, opts)
520
538
  const server = buildServer(port, routes, state, opts)
521
539
  const commands = buildSocketCommands(state)
522
-
523
- processIO(port, server, opts)
540
+ const close = processIO(port, server, opts)
524
541
 
525
542
  return {
526
543
  routes: routes.output,
527
544
  server,
528
545
  commands,
546
+ close,
529
547
  }
530
548
  }