@nubjs/nub-linux-x64 0.2.1 → 0.2.3

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/bin/nub CHANGED
Binary file
package/bin/nubx CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubjs/nub-linux-x64",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Nub binary for linux-x64",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/nubjs/nub",
Binary file
@@ -1,10 +1,21 @@
1
- // Web Locks API polyfill for Node 22.x (native on Node 24.5+).
2
- // Single-process only — locks don't coordinate across workers.
1
+ // Web Locks API polyfill for Node < 24.5 (native on Node 24.5+, #58666).
2
+ // Single-process only — locks don't coordinate across worker threads (a deliberate
3
+ // scope: the common in-process serialization use is covered; cross-thread coordination
4
+ // would need a SharedArrayBuffer waitlist and is out of scope until there's demand).
5
+ //
6
+ // Spec: https://w3c.github.io/web-locks/. Modeled to match Node's own
7
+ // internal/locks.js (the native impl on 24.5+) so behavior is identical across the
8
+ // version boundary — including `steal`, AbortSignal integration, and the option/name
9
+ // validation that the WPT web-locks suite exercises. Requires a `navigator` object to
10
+ // host `navigator.locks`; navigator-shim.mjs backfills that on Node < 21 and MUST run
11
+ // first.
3
12
 
4
13
  if (typeof globalThis.navigator === "object" && typeof globalThis.navigator.locks === "undefined") {
5
- // Track held locks: name → { mode, count } where count > 1 means shared holders
6
- const held = new Map();
7
- const queue = new Map();
14
+ const AbortSig = globalThis.AbortSignal;
15
+
16
+ const abortError = (msg) => new DOMException(msg || "The operation was aborted", "AbortError");
17
+ const stolenError = () => abortError("The lock was stolen by another request");
18
+ const notSupported = (msg) => new DOMException(msg, "NotSupportedError");
8
19
 
9
20
  class Lock {
10
21
  #name;
@@ -17,113 +28,209 @@ if (typeof globalThis.navigator === "object" && typeof globalThis.navigator.lock
17
28
  get mode() { return this.#mode; }
18
29
  }
19
30
 
31
+ // held: name → { mode, holders:Set<Holder> }. A shared grant has N holders sharing
32
+ // one record; an exclusive grant has exactly one. queue: name → Array<Waiter> (FIFO).
33
+ const held = new Map();
34
+ const queue = new Map();
35
+
20
36
  function canAcquire(name, mode) {
21
- const current = held.get(name);
22
- if (!current) return true;
23
- if (mode === "shared" && current.mode === "shared") return true;
24
- return false;
37
+ const rec = held.get(name);
38
+ if (!rec) return true;
39
+ return mode === "shared" && rec.mode === "shared";
25
40
  }
26
41
 
27
- function acquire(name, mode) {
28
- const current = held.get(name);
29
- if (current && mode === "shared" && current.mode === "shared") {
30
- current.count++;
31
- } else {
32
- held.set(name, { mode, count: 1 });
33
- }
42
+ // A FRESH request may be granted immediately only when no waiters are already
43
+ // queued for the name — otherwise it must queue behind them. This is the
44
+ // reader/writer FAIRNESS rule: a new shared request must NOT barge ahead of a
45
+ // pending exclusive request and join the current shared holders, which would
46
+ // starve the writer (WPT mode-mixed "An exclusive lock between shared locks").
47
+ // drainQueue, which only ever grants from the FRONT of the queue, keeps using
48
+ // canAcquire directly.
49
+ function canGrantNow(name, mode) {
50
+ const q = queue.get(name);
51
+ if (q && q.length > 0) return false;
52
+ return canAcquire(name, mode);
34
53
  }
35
54
 
36
- function release(name) {
37
- const current = held.get(name);
38
- if (!current) return;
39
- current.count--;
40
- if (current.count <= 0) {
41
- held.delete(name);
42
- drainQueue(name);
43
- }
55
+ function addHolder(name, mode, holder) {
56
+ const rec = held.get(name);
57
+ if (rec) rec.holders.add(holder);
58
+ else held.set(name, { mode, holders: new Set([holder]) });
59
+ }
60
+
61
+ function removeHolder(name, holder) {
62
+ const rec = held.get(name);
63
+ if (!rec) return;
64
+ rec.holders.delete(holder);
65
+ if (rec.holders.size === 0) held.delete(name);
44
66
  }
45
67
 
68
+ function enqueue(name, waiter) {
69
+ let q = queue.get(name);
70
+ if (!q) queue.set(name, (q = []));
71
+ q.push(waiter);
72
+ }
73
+
74
+ // Grant as many head-of-queue waiters as the current held state allows: a head
75
+ // exclusive takes the lock alone; a run of head shared waiters is all granted.
46
76
  function drainQueue(name) {
47
77
  const q = queue.get(name);
48
78
  if (!q || q.length === 0) return;
79
+ while (q.length > 0) {
80
+ const first = q[0];
81
+ if (!canAcquire(name, first.mode)) break;
82
+ q.shift();
83
+ first.grant(); // synchronously addHolders, so canAcquire reflects it next iter
84
+ if (first.mode === "exclusive") break;
85
+ }
86
+ if (q.length === 0) queue.delete(name);
87
+ }
49
88
 
50
- // Try to grant as many queued requests as possible.
51
- // If the first queued is shared, grant all consecutive shared requests.
52
- // If the first queued is exclusive, grant only that one.
53
- const first = q[0];
54
- if (canAcquire(name, first.mode)) {
55
- if (first.mode === "exclusive") {
56
- q.shift();
57
- first.resolve();
58
- } else {
59
- // Grant all consecutive shared requests.
60
- while (q.length > 0 && q[0].mode === "shared") {
61
- const req = q.shift();
62
- req.resolve();
63
- }
64
- }
89
+ // Run `cb(lock)` as a holder of (name, mode); return the promise that settles when
90
+ // the holder releases (cb's result/throw) or rejects early if a steal BREAKS it.
91
+ // The callback is invoked one microtask later (matching Node), so a synchronously
92
+ // signaled abort is observed before the callback runs.
93
+ function runHolder(name, mode, cb) {
94
+ const lock = new Lock(name, mode);
95
+ let broken = false;
96
+ let rejectReleased;
97
+ const holder = {
98
+ mode,
99
+ break(reason) {
100
+ if (broken) return;
101
+ broken = true;
102
+ // Remove WITHOUT draining — the stealer takes the lock next; queued waiters
103
+ // stay pending until the steal's own holder releases.
104
+ removeHolder(name, holder);
105
+ rejectReleased(reason);
106
+ },
107
+ };
108
+ addHolder(name, mode, holder);
109
+ return new Promise((resolve, reject) => {
110
+ rejectReleased = reject;
111
+ Promise.resolve()
112
+ .then(() => cb(lock))
113
+ .then(
114
+ (value) => { if (!broken) { removeHolder(name, holder); resolve(value); drainQueue(name); } },
115
+ (err) => { if (!broken) { removeHolder(name, holder); reject(err); drainQueue(name); } },
116
+ );
117
+ });
118
+ }
119
+
120
+ // The lock engine: returns the `released` promise. `cb` receives the granted Lock
121
+ // (or null on an ifAvailable miss). Mirrors the grant/steal/ifAvailable/queue shape
122
+ // of Node's internalBinding('locks').request.
123
+ function engineRequest(name, mode, steal, ifAvailable, cb) {
124
+ if (steal) {
125
+ const rec = held.get(name);
126
+ if (rec) for (const h of [...rec.holders]) h.break(stolenError());
127
+ return runHolder(name, "exclusive", cb);
128
+ }
129
+ if (canGrantNow(name, mode)) {
130
+ return runHolder(name, mode, cb);
65
131
  }
132
+ if (ifAvailable) {
133
+ return Promise.resolve().then(() => cb(null));
134
+ }
135
+ return new Promise((resolve, reject) => {
136
+ const waiter = {
137
+ mode,
138
+ settled: false,
139
+ grant() {
140
+ if (waiter.settled) return;
141
+ waiter.settled = true;
142
+ runHolder(name, mode, cb).then(resolve, reject);
143
+ },
144
+ };
145
+ enqueue(name, waiter);
146
+ });
66
147
  }
67
148
 
68
149
  class LockManager {
69
- async request(name, optionsOrCallback, callback) {
70
- let options = {};
71
- if (typeof optionsOrCallback === "function") {
72
- callback = optionsOrCallback;
73
- } else {
74
- options = optionsOrCallback || {};
150
+ async request(name, optionsOrCallback, maybeCallback) {
151
+ let options = optionsOrCallback;
152
+ let callback = maybeCallback;
153
+ if (callback === undefined) {
154
+ callback = options;
155
+ options = undefined;
156
+ }
157
+
158
+ // WebIDL DOMString coercion (throws TypeError on a Symbol, like the binding).
159
+ name = `${name}`;
160
+ if (typeof callback !== "function") {
161
+ throw new TypeError("Failed to execute 'request' on 'LockManager': parameter 2 is not a function.");
75
162
  }
163
+ if (options === undefined || typeof options === "function") options = {};
76
164
 
77
- const mode = options.mode || "exclusive";
78
- const ifAvailable = options.ifAvailable || false;
165
+ const mode = options.mode === undefined ? "exclusive" : options.mode;
166
+ if (mode !== "exclusive" && mode !== "shared") {
167
+ throw new TypeError(`Failed to execute 'request' on 'LockManager': '${mode}' is not a valid value for enumeration LockMode.`);
168
+ }
169
+ const ifAvailable = !!options.ifAvailable;
170
+ const steal = !!options.steal;
79
171
  const signal = options.signal;
172
+ if (signal !== undefined && signal !== null && !(AbortSig && signal instanceof AbortSig)) {
173
+ throw new TypeError("Failed to execute 'request' on 'LockManager': member signal is not of type AbortSignal.");
174
+ }
80
175
 
81
- if (signal?.aborted) {
82
- throw signal.reason || new DOMException("Lock request aborted", "AbortError");
176
+ // Already-aborted signal rejects with its reason BEFORE the option-combo checks
177
+ // (matching Node's signal.throwIfAborted() ordering).
178
+ if (signal && signal.aborted) {
179
+ throw signal.reason || abortError();
180
+ }
181
+ if (name[0] === "-") {
182
+ throw notSupported("Lock name may not start with hyphen '-'");
183
+ }
184
+ if (ifAvailable && steal) {
185
+ throw notSupported("ifAvailable and steal options cannot be used together");
186
+ }
187
+ if (mode !== "exclusive" && steal) {
188
+ throw notSupported("mode must be 'exclusive' when using the steal option");
189
+ }
190
+ if (signal && (steal || ifAvailable)) {
191
+ throw notSupported("signal cannot be used with the steal or ifAvailable options");
83
192
  }
84
193
 
85
- if (!canAcquire(name, mode)) {
86
- if (ifAvailable) {
87
- return callback(null);
88
- }
89
- await new Promise((resolve, reject) => {
90
- if (!queue.has(name)) queue.set(name, []);
91
- queue.get(name).push({ resolve, reject, mode });
92
- if (signal) {
93
- signal.addEventListener("abort", () => {
94
- const q = queue.get(name) || [];
95
- const idx = q.findIndex((e) => e.resolve === resolve);
96
- if (idx !== -1) q.splice(idx, 1);
97
- reject(signal.reason || new DOMException("Lock request aborted", "AbortError"));
98
- }, { once: true });
99
- }
194
+ // Signal path: the callback is deferred and skipped if the signal aborts before
195
+ // it runs; the abort rejects the OUTER promise iff the callback hasn't entered
196
+ // (lockGranted false). Verbatim shape of Node's internal/locks.js so a queued
197
+ // abort releases the slot (callback skipped) and the next waiter is granted.
198
+ if (signal) {
199
+ return new Promise((resolve, reject) => {
200
+ let lockGranted = false;
201
+ const onAbort = () => {
202
+ if (!lockGranted) reject(signal.reason || abortError());
203
+ };
204
+ signal.addEventListener("abort", onAbort, { once: true });
205
+ const wrapped = (lock) =>
206
+ Promise.resolve().then(() => {
207
+ if (signal.aborted) return undefined;
208
+ lockGranted = true;
209
+ return callback(lock);
210
+ });
211
+ const released = engineRequest(name, mode, false, false, wrapped);
212
+ released.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
100
213
  });
101
214
  }
102
215
 
103
- acquire(name, mode);
104
- const lock = new Lock(name, mode);
105
-
106
- try {
107
- return await callback(lock);
108
- } finally {
109
- release(name);
110
- }
216
+ return engineRequest(name, mode, steal, ifAvailable, (lock) => callback(lock));
111
217
  }
112
218
 
113
219
  async query() {
114
- const heldLocks = [];
115
- for (const [name, info] of held) {
116
- for (let i = 0; i < info.count; i++) {
117
- heldLocks.push({ name, mode: info.mode, clientId: "" });
118
- }
220
+ // clientId is per-realm and opaque: `node-<pid>-0` (single-process; this polyfill
221
+ // does not coordinate across worker threads, so the threadId slot is always 0 —
222
+ // and we avoid importing node:worker_threads to keep this module builtin-free for a
223
+ // cheap bootstrap). The cross-context distinct-id WPT cases are browser-specific.
224
+ const clientId = `node-${process.pid}-0`;
225
+ const heldOut = [];
226
+ for (const [name, rec] of held) {
227
+ for (let i = 0; i < rec.holders.size; i++) heldOut.push({ name, mode: rec.mode, clientId });
119
228
  }
120
229
  const pending = [];
121
230
  for (const [name, q] of queue) {
122
- for (const req of q) {
123
- pending.push({ name, mode: req.mode, clientId: "" });
124
- }
231
+ for (const w of q) if (!w.settled) pending.push({ name, mode: w.mode, clientId });
125
232
  }
126
- return { held: heldLocks, pending };
233
+ return { held: heldOut, pending };
127
234
  }
128
235
  }
129
236
 
@@ -0,0 +1,144 @@
1
+ // `navigator` global backfill for Node < 21 (where the global is wholly absent).
2
+ //
3
+ // Node ships `globalThis.navigator` from 21.0.0. Below that it is `undefined`, so
4
+ // any web-platform API hosted ON navigator — chiefly `navigator.locks` (Web Locks,
5
+ // see navigator-locks.mjs) — has no object to attach to and silently does nothing.
6
+ // nub's floor is 18.19, so to make those APIs reach the floor we synthesize the
7
+ // `navigator` object Node 21+ would provide. Companion to navigator-locks.mjs:
8
+ // this MUST run BEFORE it so locks has a host on 18.19–20.x.
9
+ //
10
+ // Shape mirrors Node's internal/navigator.js: a `Navigator` instance with the
11
+ // enumerable prototype getters `hardwareConcurrency`, `language`, `languages`,
12
+ // `userAgent`, `platform` (locks is added separately by navigator-locks.mjs). The
13
+ // userAgent is `Node.js/<major>` — NEVER `Nub/…`: the user is running Node, nub is
14
+ // the augmenter, and a `Nub/` UA would be a brand-boundary leak.
15
+ //
16
+ // VERSION-GATE, not a global read: installNavigatorShim() returns early on Node >= 21
17
+ // from `process.versions.node` WITHOUT ever touching `globalThis.navigator`. That
18
+ // matters because on Node 24.5+ the native `navigator` is a lazy getter whose first
19
+ // access realizes ~30 internal/stream/worker-io builtins — a cold-start regression
20
+ // (test-bootstrap-modules). The shim must never trigger that; the version check makes
21
+ // the fast tier (>= 22.15, navigator always present) a free no-op.
22
+ //
23
+ // node: builtins (node:os) are fetched via `process.getBuiltinModule` when present,
24
+ // else via a createRequire THREADED IN through `setBootstrapCreateRequire` — the same
25
+ // brand-safe, off-the-user-loader-chain pattern worker-polyfill.mjs uses. The narrow
26
+ // floor (18.19.x, 20.11–20.15) lacks `process.getBuiltinModule`, and the shim only
27
+ // touches os lazily (the `hardwareConcurrency` getter), so the threaded require is
28
+ // needed exactly there. `os` is never loaded on Node >= 21 (the early return).
29
+
30
+ let _bootstrapCreateRequire = null;
31
+ export function setBootstrapCreateRequire(fn) {
32
+ _bootstrapCreateRequire = fn;
33
+ }
34
+
35
+ function __getBuiltin(id) {
36
+ if (typeof process.getBuiltinModule === "function") return process.getBuiltinModule(id);
37
+ if (_bootstrapCreateRequire) return _bootstrapCreateRequire(import.meta.url)(id);
38
+ // Last-resort: a bare specifier require off this module's own createRequire is
39
+ // unavailable in ESM without node:module, which is exactly what the threading
40
+ // avoids importing statically. If neither path is wired, surface a clear error.
41
+ throw new Error("navigator-shim: no builtin accessor for " + id);
42
+ }
43
+
44
+ function deriveLanguage() {
45
+ // Approximate Node's ICU default locale from the POSIX locale env, falling back
46
+ // to "en-US" (Node's own fallback). `en_US.UTF-8` → `en-US`.
47
+ const raw =
48
+ process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || "";
49
+ const base = raw.split(".")[0].split("@")[0].replace("_", "-");
50
+ return base && base !== "C" && base !== "POSIX" ? base : "en-US";
51
+ }
52
+
53
+ function navigatorPlatform(platform, arch) {
54
+ // Mirror node/lib/internal/navigator.js getNavigatorPlatform.
55
+ if (platform === "darwin") return "MacIntel";
56
+ if (platform === "win32") return "Win32";
57
+ if (platform === "linux") {
58
+ if (arch === "ia32") return "Linux i686";
59
+ if (arch === "x64") return "Linux x86_64";
60
+ return `Linux ${arch}`;
61
+ }
62
+ if (platform === "freebsd") return arch === "ia32" ? "FreeBSD i386" : arch === "x64" ? "FreeBSD amd64" : `FreeBSD ${arch}`;
63
+ if (platform === "openbsd") return arch === "ia32" ? "OpenBSD i386" : arch === "x64" ? "OpenBSD amd64" : `OpenBSD ${arch}`;
64
+ if (platform === "sunos") return arch === "ia32" ? "SunOS i86pc" : `SunOS ${arch}`;
65
+ if (platform === "aix") return "AIX";
66
+ return `${platform[0].toUpperCase()}${platform.slice(1)} ${arch}`;
67
+ }
68
+
69
+ // Build the Navigator class lazily (only when actually backfilling) so loading this
70
+ // module on the fast tier costs nothing beyond the early-return check.
71
+ function makeNavigator() {
72
+ let _hw, _lang, _langs, _ua, _plat;
73
+ class Navigator {
74
+ get hardwareConcurrency() {
75
+ // os.availableParallelism honors cgroup CPU limits (Node 18.14+/19.4+; present
76
+ // on the 18.19 floor); fall back to cpus().length on the off chance it's absent.
77
+ if (_hw === undefined) {
78
+ const os = __getBuiltin("node:os");
79
+ _hw = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length;
80
+ }
81
+ return _hw;
82
+ }
83
+ get language() {
84
+ return (_lang ??= deriveLanguage());
85
+ }
86
+ get languages() {
87
+ return (_langs ??= Object.freeze([this.language]));
88
+ }
89
+ get userAgent() {
90
+ // `Node.js/<major>` — match Node exactly; never a nub-branded UA.
91
+ return (_ua ??= `Node.js/${process.versions.node.split(".")[0]}`);
92
+ }
93
+ get platform() {
94
+ return (_plat ??= navigatorPlatform(process.platform, process.arch));
95
+ }
96
+ }
97
+ // Match Node's instance shape: the property getters live on the prototype and are
98
+ // ENUMERABLE (Node sets them with kEnumerableProperty), so `for (const k in
99
+ // navigator)` walks them while `Object.keys(navigator)` (own enumerable) is empty.
100
+ // Class-body getters default to enumerable:false, so flip them in place.
101
+ for (const k of ["hardwareConcurrency", "language", "languages", "userAgent", "platform"]) {
102
+ Object.defineProperty(Navigator.prototype, k, { enumerable: true });
103
+ }
104
+ return { Navigator, instance: new Navigator() };
105
+ }
106
+
107
+ // Install the navigator backfill if and only if the running Node lacks the global
108
+ // (i.e. Node < 21). Idempotent and side-effect-free on Node >= 21.
109
+ export function installNavigatorShim() {
110
+ const major = parseInt(process.versions.node.split(".")[0], 10);
111
+ // Node 21+ ships navigator natively — never read the (possibly lazy) global here.
112
+ if (major >= 21) return;
113
+ // Defensive idempotency for the < 21 path (navigator is genuinely undefined there,
114
+ // so this read can't trigger a lazy realization).
115
+ if (typeof globalThis.navigator !== "undefined") return;
116
+
117
+ const { Navigator, instance } = makeNavigator();
118
+
119
+ // The binding on globalThis is NON-ENUMERABLE (invisible to Object.keys(globalThis)
120
+ // / for-in) — nub's additive-global contract, matching how reportError and Worker
121
+ // are installed. Configurable + writable so user code may still override it.
122
+ Object.defineProperty(globalThis, "navigator", {
123
+ value: instance,
124
+ enumerable: false,
125
+ writable: true,
126
+ configurable: true,
127
+ });
128
+ // Node also exposes the `Navigator` constructor globally; mirror it (non-enumerable).
129
+ if (typeof globalThis.Navigator === "undefined") {
130
+ Object.defineProperty(globalThis, "Navigator", {
131
+ value: Navigator,
132
+ enumerable: false,
133
+ writable: true,
134
+ configurable: true,
135
+ });
136
+ }
137
+ }
138
+
139
+ // Fast tier / modern compat (getBuiltinModule present, Node >= 20.16): install eagerly
140
+ // at module eval — a guaranteed no-op above Node 21 thanks to the version gate, and
141
+ // correct on a 20.16–20.x compat run where navigator is still absent. On the narrow
142
+ // floor below getBuiltinModule the compat entry calls setBootstrapCreateRequire(...)
143
+ // + installNavigatorShim() explicitly (mirroring worker-polyfill's wiring).
144
+ if (typeof process.getBuiltinModule === "function") installNavigatorShim();