@jongleberry/vurst-runtime 0.0.1
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.d.ts +19 -0
- package/index.js +167 -0
- package/package.json +13 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface NativeRuntime {
|
|
2
|
+
beginShutdown(): void
|
|
3
|
+
getPendingCount(): number
|
|
4
|
+
isShuttingDown(): boolean
|
|
5
|
+
resetForTests(): void
|
|
6
|
+
runNativeFunction(fn: Function, receiver: unknown, args: unknown[]): unknown
|
|
7
|
+
waitForDrain(): Promise<void>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare function createNativeRuntime(): NativeRuntime
|
|
11
|
+
|
|
12
|
+
export declare function wrapNativeAddon<T extends Record<PropertyKey, unknown>>(
|
|
13
|
+
addon: T,
|
|
14
|
+
runtime?: NativeRuntime,
|
|
15
|
+
): T
|
|
16
|
+
|
|
17
|
+
export declare function beginNativeAddonShutdown(): void
|
|
18
|
+
export declare function isNativeAddonShuttingDown(): boolean
|
|
19
|
+
export declare function waitForNativeAddonWorkToDrain(): Promise<void>
|
package/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Shutdown drain wrapper for @jongleberry/vurst-* N-API packages.
|
|
4
|
+
//
|
|
5
|
+
// Provides a keep-alive handle while native work is in flight, shutdown
|
|
6
|
+
// signalling, and a drain-wait primitive for graceful process exit.
|
|
7
|
+
|
|
8
|
+
const SHUTDOWN_ERROR_MESSAGE =
|
|
9
|
+
'Rust N-API runtime is shutting down; refusing to start new native work.'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {unknown} value
|
|
13
|
+
* @returns {value is PromiseLike<unknown>}
|
|
14
|
+
*/
|
|
15
|
+
function isPromiseLike(value) {
|
|
16
|
+
if (value == null) return false
|
|
17
|
+
if (typeof value !== 'object' && typeof value !== 'function') return false
|
|
18
|
+
return typeof (/** @type {any} */ (value).then) === 'function'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create an isolated runtime instance.
|
|
23
|
+
*
|
|
24
|
+
* Returns an object with:
|
|
25
|
+
* - `beginShutdown()` — mark runtime as shutting down
|
|
26
|
+
* - `getPendingCount()` — number of in-flight native calls
|
|
27
|
+
* - `isShuttingDown()` — whether shutdown has been signalled
|
|
28
|
+
* - `resetForTests()` — reset all state (use only in tests)
|
|
29
|
+
* - `runNativeFunction(fn, receiver, args)` — track and call a native fn
|
|
30
|
+
* - `waitForDrain()` — Promise that resolves when pendingCount reaches 0
|
|
31
|
+
*/
|
|
32
|
+
function createNativeRuntime() {
|
|
33
|
+
let pendingCount = 0
|
|
34
|
+
let shuttingDown = false
|
|
35
|
+
/** @type {NodeJS.Timeout | null} */
|
|
36
|
+
let keepAliveHandle = null
|
|
37
|
+
/** @type {Set<() => void>} */
|
|
38
|
+
const drainResolvers = new Set()
|
|
39
|
+
|
|
40
|
+
function ensureKeepAliveHandle() {
|
|
41
|
+
if (keepAliveHandle != null) return
|
|
42
|
+
// Keep the event loop alive while native work is in flight. Some N-API async
|
|
43
|
+
// completions do not show up as ordinary JS handles, so without this guard a
|
|
44
|
+
// worker can start tearing down before the Promise settles back into JS.
|
|
45
|
+
keepAliveHandle = setInterval(() => {}, 60_000)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function releaseKeepAliveHandle() {
|
|
49
|
+
if (pendingCount !== 0 || keepAliveHandle == null) return
|
|
50
|
+
clearInterval(keepAliveHandle)
|
|
51
|
+
keepAliveHandle = null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveDrainWaiters() {
|
|
55
|
+
if (pendingCount !== 0) return
|
|
56
|
+
for (const resolve of drainResolvers) resolve()
|
|
57
|
+
drainResolvers.clear()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @template T
|
|
62
|
+
* @param {PromiseLike<T>} promise
|
|
63
|
+
* @returns {Promise<T>}
|
|
64
|
+
*/
|
|
65
|
+
function trackPromise(promise) {
|
|
66
|
+
pendingCount += 1
|
|
67
|
+
ensureKeepAliveHandle()
|
|
68
|
+
|
|
69
|
+
return Promise.resolve(promise).finally(() => {
|
|
70
|
+
pendingCount -= 1
|
|
71
|
+
releaseKeepAliveHandle()
|
|
72
|
+
resolveDrainWaiters()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {Function} fn
|
|
78
|
+
* @param {unknown} receiver
|
|
79
|
+
* @param {unknown[]} args
|
|
80
|
+
* @returns {unknown}
|
|
81
|
+
*/
|
|
82
|
+
function runNativeFunction(fn, receiver, args) {
|
|
83
|
+
if (shuttingDown) throw new Error(SHUTDOWN_ERROR_MESSAGE)
|
|
84
|
+
const result = Reflect.apply(fn, receiver, args)
|
|
85
|
+
if (!isPromiseLike(result)) return result
|
|
86
|
+
return trackPromise(result)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @returns {Promise<void>} */
|
|
90
|
+
function waitForDrain() {
|
|
91
|
+
if (pendingCount === 0) return Promise.resolve()
|
|
92
|
+
return new Promise(resolve => drainResolvers.add(resolve))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function beginShutdown() {
|
|
96
|
+
shuttingDown = true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resetForTests() {
|
|
100
|
+
shuttingDown = false
|
|
101
|
+
pendingCount = 0
|
|
102
|
+
drainResolvers.clear()
|
|
103
|
+
if (keepAliveHandle != null) clearInterval(keepAliveHandle)
|
|
104
|
+
keepAliveHandle = null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
beginShutdown,
|
|
109
|
+
getPendingCount: () => pendingCount,
|
|
110
|
+
isShuttingDown: () => shuttingDown,
|
|
111
|
+
resetForTests,
|
|
112
|
+
runNativeFunction,
|
|
113
|
+
waitForDrain,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sharedRuntime = createNativeRuntime()
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Wrap a native N-API addon object so every function call is tracked through
|
|
121
|
+
* the runtime for shutdown/drain handling.
|
|
122
|
+
*
|
|
123
|
+
* @template {Record<PropertyKey, unknown>} T
|
|
124
|
+
* @param {T} addon
|
|
125
|
+
* @param {ReturnType<typeof createNativeRuntime>} [runtime]
|
|
126
|
+
* @returns {T}
|
|
127
|
+
*/
|
|
128
|
+
function wrapNativeAddon(addon, runtime) {
|
|
129
|
+
const rt = runtime ?? sharedRuntime
|
|
130
|
+
/** @type {Map<PropertyKey, { original: Function, wrapped: Function }>} */
|
|
131
|
+
const wrappedFunctions = new Map()
|
|
132
|
+
|
|
133
|
+
return new Proxy(addon, {
|
|
134
|
+
get(target, prop, receiver) {
|
|
135
|
+
const value = Reflect.get(target, prop, receiver)
|
|
136
|
+
if (typeof value !== 'function') return value
|
|
137
|
+
|
|
138
|
+
const cached = wrappedFunctions.get(prop)
|
|
139
|
+
if (cached != null && cached.original === value) return cached.wrapped
|
|
140
|
+
|
|
141
|
+
const wrapped = (...args) => rt.runNativeFunction(value, target, args)
|
|
142
|
+
wrappedFunctions.set(prop, { original: value, wrapped })
|
|
143
|
+
return wrapped
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Signal the shared runtime to stop accepting new native work. */
|
|
149
|
+
function beginNativeAddonShutdown() {
|
|
150
|
+
sharedRuntime.beginShutdown()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @returns {boolean} */
|
|
154
|
+
function isNativeAddonShuttingDown() {
|
|
155
|
+
return sharedRuntime.isShuttingDown()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @returns {Promise<void>} */
|
|
159
|
+
function waitForNativeAddonWorkToDrain() {
|
|
160
|
+
return sharedRuntime.waitForDrain()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports.createNativeRuntime = createNativeRuntime
|
|
164
|
+
module.exports.wrapNativeAddon = wrapNativeAddon
|
|
165
|
+
module.exports.beginNativeAddonShutdown = beginNativeAddonShutdown
|
|
166
|
+
module.exports.isNativeAddonShuttingDown = isNativeAddonShuttingDown
|
|
167
|
+
module.exports.waitForNativeAddonWorkToDrain = waitForNativeAddonWorkToDrain
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jongleberry/vurst-runtime",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Shutdown drain wrapper for @jongleberry/vurst-* N-API packages.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": { "node": ">= 18" },
|
|
9
|
+
"repository": { "type": "git", "url": "git+https://github.com/jonathanong/vurst.git" },
|
|
10
|
+
"files": ["index.js", "index.d.ts", "README.md"],
|
|
11
|
+
"keywords": ["rust", "napi", "shutdown", "drain", "graceful"],
|
|
12
|
+
"publishConfig": { "access": "public", "provenance": true }
|
|
13
|
+
}
|