@rvoh/psychic 3.4.1 → 3.5.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/cjs/src/cli/helpers/PackageManager.js +32 -0
- package/dist/cjs/src/server/index.js +28 -2
- package/dist/esm/src/cli/helpers/PackageManager.js +32 -0
- package/dist/esm/src/server/index.js +28 -2
- package/dist/types/src/cli/helpers/PackageManager.d.ts +3 -1
- package/dist/types/src/server/index.d.ts +2 -1
- package/package.json +2 -8
|
@@ -11,6 +11,11 @@ export default class PackageManager {
|
|
|
11
11
|
switch (this.packageManager) {
|
|
12
12
|
case 'npm':
|
|
13
13
|
return { command: 'npm', args: ['install', '--save-dev', ...list] };
|
|
14
|
+
case 'bun':
|
|
15
|
+
return { command: 'bun', args: ['add', '--dev', ...list] };
|
|
16
|
+
case 'deno':
|
|
17
|
+
// Deno needs an explicit registry prefix to add npm packages.
|
|
18
|
+
return { command: 'deno', args: ['add', '--dev', ...denoSpecifiers(list)] };
|
|
14
19
|
default:
|
|
15
20
|
return { command: this.packageManager, args: ['add', '-D', ...list] };
|
|
16
21
|
}
|
|
@@ -19,6 +24,10 @@ export default class PackageManager {
|
|
|
19
24
|
switch (this.packageManager) {
|
|
20
25
|
case 'npm':
|
|
21
26
|
return { command: 'npm', args: ['install', ...list] };
|
|
27
|
+
case 'bun':
|
|
28
|
+
return { command: 'bun', args: ['add', ...list] };
|
|
29
|
+
case 'deno':
|
|
30
|
+
return { command: 'deno', args: ['add', ...denoSpecifiers(list)] };
|
|
22
31
|
default:
|
|
23
32
|
return { command: this.packageManager, args: ['add', ...list] };
|
|
24
33
|
}
|
|
@@ -32,6 +41,14 @@ export default class PackageManager {
|
|
|
32
41
|
command: 'npm',
|
|
33
42
|
args: args.length ? ['run', cmd, '--', ...args] : ['run', cmd],
|
|
34
43
|
};
|
|
44
|
+
case 'bun':
|
|
45
|
+
// `bun <script>` is treated as file execution; `bun run` is required to
|
|
46
|
+
// resolve a package.json script. Bun forwards trailing args directly.
|
|
47
|
+
return { command: 'bun', args: ['run', cmd, ...args] };
|
|
48
|
+
case 'deno':
|
|
49
|
+
// Deno has no `<pm> <script>` shorthand; `deno task` resolves a
|
|
50
|
+
// package.json script (or deno.json task) and forwards trailing args.
|
|
51
|
+
return { command: 'deno', args: ['task', cmd, ...args] };
|
|
35
52
|
default:
|
|
36
53
|
return { command: this.packageManager, args: [cmd, ...args] };
|
|
37
54
|
}
|
|
@@ -42,8 +59,23 @@ export default class PackageManager {
|
|
|
42
59
|
return { command: 'npm', args: ['exec', '--', cmd, ...args] };
|
|
43
60
|
case 'yarn':
|
|
44
61
|
return { command: 'yarn', args: [cmd, ...args] };
|
|
62
|
+
case 'bun':
|
|
63
|
+
return { command: 'bunx', args: [cmd, ...args] };
|
|
64
|
+
case 'deno':
|
|
65
|
+
// `deno run` against an npm: specifier is Deno's equivalent of npx;
|
|
66
|
+
// -A grants the permissions the executed tool needs.
|
|
67
|
+
return { command: 'deno', args: ['run', '-A', denoSpecifier(cmd), ...args] };
|
|
45
68
|
default:
|
|
46
69
|
return { command: this.packageManager, args: ['exec', cmd, ...args] };
|
|
47
70
|
}
|
|
48
71
|
}
|
|
49
72
|
}
|
|
73
|
+
// Deno requires npm/jsr packages to carry an explicit registry prefix
|
|
74
|
+
// (`npm:lodash`); bare names are treated as local/JSR specifiers. Leave any
|
|
75
|
+
// already-prefixed specifier untouched.
|
|
76
|
+
function denoSpecifier(pkg) {
|
|
77
|
+
return /^(npm|jsr):/.test(pkg) ? pkg : `npm:${pkg}`;
|
|
78
|
+
}
|
|
79
|
+
function denoSpecifiers(list) {
|
|
80
|
+
return list.map(denoSpecifier);
|
|
81
|
+
}
|
|
@@ -138,11 +138,37 @@ export default class PsychicServer {
|
|
|
138
138
|
await this.stop();
|
|
139
139
|
process.exit();
|
|
140
140
|
}
|
|
141
|
-
async stop({ bypassClosingDbConnections = false } = {}) {
|
|
141
|
+
async stop({ bypassClosingDbConnections = false, gracefulShutdownTimeoutMillis = 10_000, } = {}) {
|
|
142
142
|
for (const hook of PsychicApp.getOrFail().specialHooks.serverShutdown) {
|
|
143
143
|
await hook(this);
|
|
144
144
|
}
|
|
145
|
-
this.httpServer
|
|
145
|
+
if (this.httpServer) {
|
|
146
|
+
const httpServer = this.httpServer;
|
|
147
|
+
await new Promise(resolve => {
|
|
148
|
+
// Bounded grace period: if still-active requests haven't finished by
|
|
149
|
+
// the timeout, force the remaining sockets so shutdown can never hang
|
|
150
|
+
// forever (the original bug). `closeIdle/AllConnections` are Node
|
|
151
|
+
// >= 18.2; on older runtimes the optional calls no-op and prior
|
|
152
|
+
// (potentially hanging) behavior is retained rather than throwing.
|
|
153
|
+
const forceTimer = setTimeout(() => httpServer.closeAllConnections?.(), gracefulShutdownTimeoutMillis);
|
|
154
|
+
forceTimer.unref?.();
|
|
155
|
+
// `close` stops accepting new connections and fires its callback only
|
|
156
|
+
// once every existing connection has ended.
|
|
157
|
+
httpServer.close(() => {
|
|
158
|
+
clearTimeout(forceTimer);
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
// Immediately drop *idle* keep-alive sockets. These (browsers, fetch
|
|
162
|
+
// agents, reverse proxies sitting between requests) are what kept
|
|
163
|
+
// `close()`'s callback from ever firing — and, transitively, a leased
|
|
164
|
+
// DB pool client from being released — causing shutdown to hang (a
|
|
165
|
+
// SIGTERM drain that never completes; a feature-spec `afterAll` that
|
|
166
|
+
// blocks for the full hook timeout). Dropping only *idle* sockets
|
|
167
|
+
// does NOT abort in-flight requests, so a normal graceful shutdown
|
|
168
|
+
// still lets active handlers finish within the grace period above.
|
|
169
|
+
httpServer.closeIdleConnections?.();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
146
172
|
if (!bypassClosingDbConnections) {
|
|
147
173
|
await closeAllDbConnections();
|
|
148
174
|
}
|
|
@@ -11,6 +11,11 @@ export default class PackageManager {
|
|
|
11
11
|
switch (this.packageManager) {
|
|
12
12
|
case 'npm':
|
|
13
13
|
return { command: 'npm', args: ['install', '--save-dev', ...list] };
|
|
14
|
+
case 'bun':
|
|
15
|
+
return { command: 'bun', args: ['add', '--dev', ...list] };
|
|
16
|
+
case 'deno':
|
|
17
|
+
// Deno needs an explicit registry prefix to add npm packages.
|
|
18
|
+
return { command: 'deno', args: ['add', '--dev', ...denoSpecifiers(list)] };
|
|
14
19
|
default:
|
|
15
20
|
return { command: this.packageManager, args: ['add', '-D', ...list] };
|
|
16
21
|
}
|
|
@@ -19,6 +24,10 @@ export default class PackageManager {
|
|
|
19
24
|
switch (this.packageManager) {
|
|
20
25
|
case 'npm':
|
|
21
26
|
return { command: 'npm', args: ['install', ...list] };
|
|
27
|
+
case 'bun':
|
|
28
|
+
return { command: 'bun', args: ['add', ...list] };
|
|
29
|
+
case 'deno':
|
|
30
|
+
return { command: 'deno', args: ['add', ...denoSpecifiers(list)] };
|
|
22
31
|
default:
|
|
23
32
|
return { command: this.packageManager, args: ['add', ...list] };
|
|
24
33
|
}
|
|
@@ -32,6 +41,14 @@ export default class PackageManager {
|
|
|
32
41
|
command: 'npm',
|
|
33
42
|
args: args.length ? ['run', cmd, '--', ...args] : ['run', cmd],
|
|
34
43
|
};
|
|
44
|
+
case 'bun':
|
|
45
|
+
// `bun <script>` is treated as file execution; `bun run` is required to
|
|
46
|
+
// resolve a package.json script. Bun forwards trailing args directly.
|
|
47
|
+
return { command: 'bun', args: ['run', cmd, ...args] };
|
|
48
|
+
case 'deno':
|
|
49
|
+
// Deno has no `<pm> <script>` shorthand; `deno task` resolves a
|
|
50
|
+
// package.json script (or deno.json task) and forwards trailing args.
|
|
51
|
+
return { command: 'deno', args: ['task', cmd, ...args] };
|
|
35
52
|
default:
|
|
36
53
|
return { command: this.packageManager, args: [cmd, ...args] };
|
|
37
54
|
}
|
|
@@ -42,8 +59,23 @@ export default class PackageManager {
|
|
|
42
59
|
return { command: 'npm', args: ['exec', '--', cmd, ...args] };
|
|
43
60
|
case 'yarn':
|
|
44
61
|
return { command: 'yarn', args: [cmd, ...args] };
|
|
62
|
+
case 'bun':
|
|
63
|
+
return { command: 'bunx', args: [cmd, ...args] };
|
|
64
|
+
case 'deno':
|
|
65
|
+
// `deno run` against an npm: specifier is Deno's equivalent of npx;
|
|
66
|
+
// -A grants the permissions the executed tool needs.
|
|
67
|
+
return { command: 'deno', args: ['run', '-A', denoSpecifier(cmd), ...args] };
|
|
45
68
|
default:
|
|
46
69
|
return { command: this.packageManager, args: ['exec', cmd, ...args] };
|
|
47
70
|
}
|
|
48
71
|
}
|
|
49
72
|
}
|
|
73
|
+
// Deno requires npm/jsr packages to carry an explicit registry prefix
|
|
74
|
+
// (`npm:lodash`); bare names are treated as local/JSR specifiers. Leave any
|
|
75
|
+
// already-prefixed specifier untouched.
|
|
76
|
+
function denoSpecifier(pkg) {
|
|
77
|
+
return /^(npm|jsr):/.test(pkg) ? pkg : `npm:${pkg}`;
|
|
78
|
+
}
|
|
79
|
+
function denoSpecifiers(list) {
|
|
80
|
+
return list.map(denoSpecifier);
|
|
81
|
+
}
|
|
@@ -138,11 +138,37 @@ export default class PsychicServer {
|
|
|
138
138
|
await this.stop();
|
|
139
139
|
process.exit();
|
|
140
140
|
}
|
|
141
|
-
async stop({ bypassClosingDbConnections = false } = {}) {
|
|
141
|
+
async stop({ bypassClosingDbConnections = false, gracefulShutdownTimeoutMillis = 10_000, } = {}) {
|
|
142
142
|
for (const hook of PsychicApp.getOrFail().specialHooks.serverShutdown) {
|
|
143
143
|
await hook(this);
|
|
144
144
|
}
|
|
145
|
-
this.httpServer
|
|
145
|
+
if (this.httpServer) {
|
|
146
|
+
const httpServer = this.httpServer;
|
|
147
|
+
await new Promise(resolve => {
|
|
148
|
+
// Bounded grace period: if still-active requests haven't finished by
|
|
149
|
+
// the timeout, force the remaining sockets so shutdown can never hang
|
|
150
|
+
// forever (the original bug). `closeIdle/AllConnections` are Node
|
|
151
|
+
// >= 18.2; on older runtimes the optional calls no-op and prior
|
|
152
|
+
// (potentially hanging) behavior is retained rather than throwing.
|
|
153
|
+
const forceTimer = setTimeout(() => httpServer.closeAllConnections?.(), gracefulShutdownTimeoutMillis);
|
|
154
|
+
forceTimer.unref?.();
|
|
155
|
+
// `close` stops accepting new connections and fires its callback only
|
|
156
|
+
// once every existing connection has ended.
|
|
157
|
+
httpServer.close(() => {
|
|
158
|
+
clearTimeout(forceTimer);
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
// Immediately drop *idle* keep-alive sockets. These (browsers, fetch
|
|
162
|
+
// agents, reverse proxies sitting between requests) are what kept
|
|
163
|
+
// `close()`'s callback from ever firing — and, transitively, a leased
|
|
164
|
+
// DB pool client from being released — causing shutdown to hang (a
|
|
165
|
+
// SIGTERM drain that never completes; a feature-spec `afterAll` that
|
|
166
|
+
// blocks for the full hook timeout). Dropping only *idle* sockets
|
|
167
|
+
// does NOT abort in-flight requests, so a normal graceful shutdown
|
|
168
|
+
// still lets active handlers finish within the grace period above.
|
|
169
|
+
httpServer.closeIdleConnections?.();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
146
172
|
if (!bypassClosingDbConnections) {
|
|
147
173
|
await closeAllDbConnections();
|
|
148
174
|
}
|
|
@@ -2,11 +2,13 @@ export interface PackageManagerCommand {
|
|
|
2
2
|
command: string;
|
|
3
3
|
args: string[];
|
|
4
4
|
}
|
|
5
|
+
type SupportedPackageManager = 'pnpm' | 'yarn' | 'npm' | 'bun' | 'deno';
|
|
5
6
|
export default class PackageManager {
|
|
6
|
-
static get packageManager():
|
|
7
|
+
static get packageManager(): SupportedPackageManager;
|
|
7
8
|
static add(dependencyOrDependencies: string | string[], { dev }?: {
|
|
8
9
|
dev?: boolean;
|
|
9
10
|
}): PackageManagerCommand;
|
|
10
11
|
static run(cmd: string, args?: string[]): PackageManagerCommand;
|
|
11
12
|
static exec(cmd: string, args?: string[]): PackageManagerCommand;
|
|
12
13
|
}
|
|
14
|
+
export {};
|
|
@@ -17,8 +17,9 @@ export default class PsychicServer {
|
|
|
17
17
|
attach(id: string, obj: any): void;
|
|
18
18
|
$attached: Record<string, any>;
|
|
19
19
|
private shutdownAndExit;
|
|
20
|
-
stop({ bypassClosingDbConnections }?: {
|
|
20
|
+
stop({ bypassClosingDbConnections, gracefulShutdownTimeoutMillis, }?: {
|
|
21
21
|
bypassClosingDbConnections?: boolean;
|
|
22
|
+
gracefulShutdownTimeoutMillis?: number;
|
|
22
23
|
}): Promise<void>;
|
|
23
24
|
serveForRequestSpecs(block: () => void | Promise<void>): Promise<boolean>;
|
|
24
25
|
buildApp(): void;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@rvoh/psychic",
|
|
4
4
|
"description": "Typescript web framework",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.5.0",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -138,11 +138,5 @@
|
|
|
138
138
|
"vitest": "^4.1.0",
|
|
139
139
|
"winston": "^3.14.2"
|
|
140
140
|
},
|
|
141
|
-
"packageManager": "pnpm@
|
|
142
|
-
"pnpm": {
|
|
143
|
-
"overrides": {
|
|
144
|
-
"diff": ">=8.0.3",
|
|
145
|
-
"path-to-regexp": ">=8.4.0"
|
|
146
|
-
}
|
|
147
|
-
}
|
|
141
|
+
"packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d"
|
|
148
142
|
}
|