@blamejs/core 0.6.22 → 0.6.23

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/CHANGELOG.md CHANGED
@@ -8,6 +8,7 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.6.x
10
10
 
11
+ - **0.6.23** (2026-05-02) — v0.6.22 follow-up cleanup. The cron-repeat call site in queue-local.js was passing both `availableAt` AND `delaySeconds` (the former was the precise next-fire ms; the latter was a redundant `Math.floor((nextMs - nowMs) / 1000)` computation that only existed to work around the bug v0.6.22 fixed). Dropped — the cron repeat now passes `availableAt` alone, matching the queue's documented precedence rule. The enqueue() docstring gains a 20-line "SCHEDULING PRECEDENCE" header documenting that `opts.availableAt` wins over `opts.delaySeconds` when both are passed, why the framework chose that direction, and which callers should use which form. New round-trip preservation regression test (`testEnqueueRoundTripsAvailableAt`) covers three precise targets, the delaySeconds-only path, and the both-opts-set case — gates against any future "I'll just rederive it from the floored seconds" mistake. Audited the rest of the framework for the same `(absolute-time, relative-time)` opt-overlap shape (cache.set, session.rotate, apiKey, dualControl): queue is the only primitive carrying both forms, so a generalized `b.time.resolveTimePoint` primitive would be premature with one call site.
11
12
  - **0.6.22** (2026-05-02) — `b.queue.enqueue({ availableAt })` is now honoured. Previously the local-protocol enqueue() ignored opts.availableAt entirely, recomputing from `Date.now() + delaySeconds*1000`. The cron-repeat path passes both fields (the exact next-fire ms in availableAt + the floored seconds in delaySeconds), and the enqueue's recomputation lost sub-second precision plus drifted on the internal clock-vs-caller delta. Symptoms: cron-scheduled jobs landed up to 999ms off the intended boundary; the queue-flow-repeat smoke test was intermittently flaky on slow CI runners (caught by ubuntu-latest on the v0.6.21 commit). Fix: enqueue() honours opts.availableAt directly when finite; falls back to delaySeconds-based shorthand otherwise. Operators relying on `enqueue({ availableAt: T })` for non-cron scheduled jobs (e.g. "deliver this notification at exactly 09:00 tomorrow") now get the requested time instead of nowMs+0.
12
13
  - **0.6.21** (2026-05-02) — closes the medium / low audit findings flagged after v0.6.18. **Cluster cache `invalidateTag`** now actually works — the cluster backend gained a `_blamejs_cache_tags` junction table (`(cacheKey, tag)` PK + index on `tag`), tag-aware `set` / `del` / `clear` / `_sweep`, plus `getTags(key)`. The old NOT_SUPPORTED-on-cluster path is gone; multi-tag rotation, mid-flight tag replacement on update, and namespace-scoped sweeps all covered. **Multi-column cursor pagination** — `b.pagination.cursor({ orderBy: [{column,direction},...] })` accepts a string (single column), an array of strings (multi, all using `opts.direction`), or an array of `{column,direction}` objects (mixed directions). The keyset WHERE expands to the standard OR cascade so successive pages can't skip or repeat rows when ties on the leading columns are broken by trailing ones. `_id` is appended as a tiebreaker if not in the chain. The Query class also gained chained `orderBy(col, dir)` calls — second-and-later calls extend a multi-column ORDER BY in the SQL. Cursor format is bumped to encode `{ orderKey, vals, forward }` instead of the old `{ orderBy, dir, orderByVal, id, forward }` — pre-1.0 break, no compat shim. **DoH POST mode** (RFC 8484 §4.1) — `b.network.dns.useDnsOverHttps({ method })` accepts `"GET" | "POST" | undefined` (auto). Auto switches to POST when the GET URL would exceed 2048 bytes (long DNS names). **DoT connection pooling** — per-`(host:port)` cached TLS socket with a 2-minute idle timeout, serialized in-flight queries per socket. Eliminates the per-query handshake. **INI parser** shipped as `b.parsers.ini` — covers Windows .ini / .gitconfig / systemd-unit / php.ini / tox.ini shape: sections (incl. `[parent.child]` / `[parent "child"]` nesting), `;` and `#` comments (inline + leading), single + double quoting with `\n` `\t` `\\` `\"` `\'` escapes, boolean coercion (`true`/`false`/`yes`/`no`/`on`/`off`), decimal + hex integers + floats. Prototype-pollution defense (`__proto__` / `constructor` / `prototype` rejected). Duplicate-key policy throws by default; `onDuplicate: "first" | "last"` opts in to silent shadowing. Section / per-section key / value-bytes caps configurable. **Cookie-jar file persistence** — `b.httpClient.cookieJar.create({ persist: "file", file: "/abs/path", vault: b.vault })` loads at construct, debounce-flushes on every set/clear, plus `flush()` and `close()` for explicit lifecycle. With `vault`, on-disk bytes are sealed; without, plaintext JSON (operator chooses). **Comment cleanups per rule §4** — `vault/index.js` stale `// later` removed, `mail.js` "future patch" wording rewritten as scope, `bundler.js` "What it does NOT do today (deliberately deferred)" rewritten as "Out of scope", `archive.js` "v1 scope cuts (deferred)" rewritten as "Out of scope", `framework-schema.js` "next release" wording rewritten to describe what's actually shipped, `http-client.js` "out of reach today" / "we'll plumb it through when h3 lands" rewritten as scope statements. **README CLI section** updated to reflect the v0.6.17 + v0.6.18 + v0.6.19 + v0.6.20 additions (security / config-drift / file-type / password / erase / retention) — was missing 6 subcommands.
13
14
  - **0.6.20** (2026-05-02) — CI / packaging fix-up. The npm-publish workflow's "Attach SBOM as release asset" step started failing with HTTP 422 ("Cannot upload assets to an immutable release") because the operator's manual `gh release create` had already been published when the workflow ran. Two changes: (1) `sbom.cyclonedx.json` is now bundled into the npm tarball (`files` block in package.json), so `npm install @blamejs/core && cat node_modules/@blamejs/core/sbom.cyclonedx.json` is the canonical SBOM access path. The prepack guard's known-allowed list covers the just-in-time generation. (2) The workflow's GH-release-attach step is now non-fatal: it tries to upload, logs a warning if the release is immutable, and lets the publish proceed regardless. The npm tarball is the load-bearing artifact; the GitHub release attachment was only ever supplementary. `.gitignore` adds `sbom.cyclonedx.json` so a stray local `npm sbom` doesn't pollute the repo.
@@ -109,12 +109,24 @@ function create(_config) {
109
109
  cluster.requireLeader();
110
110
  opts = opts || {};
111
111
  var nowMs = Date.now();
112
- // Caller-supplied opts.availableAt wins (cron-repeat pre-computes
113
- // the exact next-fire ms). delaySeconds is the operator-shorthand
114
- // alternative — used when opts.availableAt isn't provided. Falling
115
- // through delaySeconds without honouring an explicit availableAt
116
- // loses sub-second precision (delaySeconds is integer-floored)
117
- // and drifts on the internal clock vs the caller's intended ms.
112
+ // ----------------------------------------------------------------------
113
+ // SCHEDULING PRECEDENCE the contract for every queue.enqueue caller
114
+ // ----------------------------------------------------------------------
115
+ // The queue accepts TWO ways to express "when should this job run":
116
+ // - opts.availableAt (absolute unix-ms) — precise, framework-internal
117
+ // - opts.delaySeconds (integer seconds) — operator shorthand
118
+ //
119
+ // PRECEDENCE: opts.availableAt wins when finite. Operators passing both
120
+ // get the absolute value; the relative form is computed from
121
+ // Date.now() and is therefore strictly less precise (loses sub-second
122
+ // resolution AND drifts on the internal-clock-vs-caller delta).
123
+ //
124
+ // If you're a caller computing a precise time (cron-repeat,
125
+ // scheduler.scheduleAt, notify.deferUntil), pass opts.availableAt
126
+ // ONLY. Don't pass both — the second one is noise (and was the
127
+ // shape that caused the v0.6.21 silent-drift bug). If you only have
128
+ // a relative offset, pass opts.delaySeconds ONLY.
129
+ // ----------------------------------------------------------------------
118
130
  var availableAt;
119
131
  if (typeof opts.availableAt === "number" && isFinite(opts.availableAt)) {
120
132
  availableAt = opts.availableAt;
@@ -259,8 +271,12 @@ function create(_config) {
259
271
  await enqueue(unsealedRow.queueName,
260
272
  unsealedRow.payload ? safeJson.parse(unsealedRow.payload) : null,
261
273
  {
274
+ // availableAt is the precise next-fire ms — pass it alone.
275
+ // Don't also pass delaySeconds (the v0.6.22 / v0.6.23 fix
276
+ // codified that opts.availableAt wins, but mixing both is
277
+ // the shape that masked the silent-drift bug; keep this
278
+ // call site clean as documentation by example).
262
279
  availableAt: nextMs,
263
- delaySeconds: Math.max(0, Math.floor((nextMs - nowMs) / 1000)),
264
280
  repeat: { cron: unsealedRow.repeatCron, timezone: unsealedRow.repeatTimezone },
265
281
  priority: Number(unsealedRow.priority) || 0,
266
282
  classification: unsealedRow.classification || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:2dcbd08d-cbaf-408b-8b68-8982ea425a0f",
5
+ "serialNumber": "urn:uuid:47563cfb-199b-4000-ba99-0e3102e87442",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-02T07:32:02.459Z",
8
+ "timestamp": "2026-05-02T14:07:01.711Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.6.22",
22
+ "bom-ref": "@blamejs/core@0.6.23",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.6.22",
25
+ "version": "0.6.23",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.6.22",
29
+ "purl": "pkg:npm/%40blamejs/core@0.6.23",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.6.22",
57
+ "ref": "@blamejs/core@0.6.23",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]