@blamejs/core 0.8.42 → 0.8.49

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.
Files changed (222) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
package/lib/tracing.js CHANGED
@@ -1,84 +1,41 @@
1
1
  "use strict";
2
2
  /**
3
- * tracing — OpenTelemetry seam without an OTel runtime dependency.
3
+ * @module b.tracing
4
+ * @nav Observability
5
+ * @title Tracing
4
6
  *
5
- * The framework doesn't bundle the OTel SDK — operators install
6
- * `@opentelemetry/api` (and an exporter) themselves when they want
7
- * tracing. This module:
7
+ * @intro
8
+ * Distributed-tracing seam W3C trace-context propagation,
9
+ * OpenTelemetry-shaped span lifecycle, sampling routed through OTel
10
+ * when installed.
8
11
  *
9
- * - Detects if @opentelemetry/api is installed (try/catch require,
10
- * cached). When it's there, every framework span call routes into
11
- * OTel's real tracer and shows up in the operator's exporter
12
- * (Jaeger, Zipkin, OTLP, console, whatever they wired).
13
- * - When OTel ISN'T installed, every call is a pass-through. The
14
- * wrapped function still executes, return values still propagate,
15
- * thrown errors still escape but no span is created and no
16
- * overhead is paid beyond one cached lookup.
12
+ * The framework keeps zero npm runtime deps, so the OTel SDK isn't
13
+ * bundled. `b.tracing.create()` detects `@opentelemetry/api` at
14
+ * first use: when it's installed, every span call flows into the
15
+ * operator's tracer (Jaeger / Zipkin / OTLP / console whatever
16
+ * exporter they wired) and OTel's sampler decides per-span
17
+ * `sampled` flag from the configured `TraceIdRatioBased` /
18
+ * `ParentBased` rules. When OTel is absent every call falls through
19
+ * a pass-through tracer that still executes the wrapped function,
20
+ * propagates return values and exceptions, and emits no span data.
17
21
  *
18
- * Public API:
22
+ * `contextHeaders()` and `extractContext()` always parse and emit
23
+ * the W3C `traceparent` format
24
+ * (`00-<32-hex traceId>-<16-hex spanId>-<2-hex flags>`) regardless
25
+ * of whether OTel is loaded — so operators get trace-ID per request
26
+ * as a free correlation baseline even without a tracer SDK. Span
27
+ * shape mirrors OTel: `setAttribute` / `addEvent` /
28
+ * `recordException` / `setStatus` / `end` / `updateName`.
19
29
  *
20
- * var t = b.tracing.create({
21
- * instrumentationName: "blamejs",
22
- * instrumentationVersion: "1.0.0",
23
- * });
30
+ * `b.tracing.tap("audit.record", attributes, fn)` mirrors
31
+ * `b.metrics.tap` for tracing — wraps `fn` in a span if a registry
32
+ * is active, passes through otherwise. `requestMiddleware()` opens
33
+ * one span per inbound request, extracts any incoming
34
+ * `traceparent`, and promotes `http.route` to the matched route
35
+ * template at response time.
24
36
  *
25
- * // Wrap async work in a span. Returns whatever fn returns.
26
- * var result = await t.span("my-op", async function (span) {
27
- * span.setAttribute("user_id", "abc");
28
- * span.addEvent("cache-miss");
29
- * return await doWork();
30
- * }, { kind: "internal", attributes: { route: "/users" } });
31
- *
32
- * // Sync variant.
33
- * var x = t.spanSync("compute", function (span) { ... return v; });
34
- *
35
- * // Read / write the current active span.
36
- * t.currentSpan(); // null when no active span
37
- * t.setAttributes({ user_id: "abc" }); // sets on current
38
- * t.recordException(err); // records on current
39
- *
40
- * // HTTP propagation. contextHeaders() returns headers to add to
41
- * // outbound requests (W3C `traceparent`); extractContext(headers)
42
- * // pulls a parent context from inbound headers.
43
- * var headers = t.contextHeaders();
44
- * var parentCtx = t.extractContext(req.headers);
45
- *
46
- * // Auto-span request middleware — wraps each handler in a span
47
- * // named after method + route pattern.
48
- * router.use(t.requestMiddleware());
49
- *
50
- * // Framework-internal hot-path tap — wraps fn in a span named
51
- * // `name` if a registry is active; pass-through otherwise. Like
52
- * // metrics.tap() but for tracing instead of counting.
53
- * b.tracing.tap("audit.record", attributes, fn);
54
- *
55
- * Even WITHOUT @opentelemetry/api installed:
56
- * - contextHeaders() / extractContext() still parse and emit the
57
- * W3C traceparent format. So a framework process without OTel
58
- * can still propagate trace IDs through logs and HTTP for
59
- * correlation, even without span telemetry. Operators get the
60
- * "trace ID per request" plumbing as a free baseline.
61
- * - currentSpan() returns a minimal pass-through "span" object so
62
- * operator code can call setAttribute / addEvent / recordException
63
- * unconditionally — they're no-ops without OTel.
64
- *
65
- * Why no @otel runtime dep:
66
- * - The framework keeps zero npm runtime deps. Apps that want tracing
67
- * install OTel themselves; apps that don't pay nothing.
68
- * - The OTel API is unstable enough that pinning a vendored version
69
- * would create more churn than it saves.
70
- *
71
- * Out of scope (with structural reasons):
72
- * - Vendoring the SDK: see above.
73
- * - Sampling decisions: OTel handles this when wired; without OTel
74
- * there's nothing to sample.
75
- * - Exporter integration: belongs to the OTel SDK, not the framework.
76
- * - Custom propagators: framework ships W3C traceparent only. OTel
77
- * adds others (b3, jaeger) when wired by the operator.
78
- * - Async-context propagation across setTimeout / setImmediate:
79
- * OTel's NodeSDK auto-instrumentation handles this when installed;
80
- * without OTel, framework code uses fn-passing rather than
81
- * async-context, which is fine for the surfaces we instrument.
37
+ * @card
38
+ * Distributed-tracing seam W3C trace-context propagation, OpenTelemetry-shaped span lifecycle, sampling routed through OTel when installed.
82
39
  */
83
40
 
84
41
  var C = require("./constants");
@@ -205,6 +162,42 @@ function _passthroughTracer() {
205
162
 
206
163
  // ---- Registry factory ----
207
164
 
165
+ /**
166
+ * @primitive b.tracing.create
167
+ * @signature b.tracing.create(opts)
168
+ * @since 0.4.0
169
+ * @status stable
170
+ * @related b.tracing.tap, b.metrics.create, b.observability.tap
171
+ *
172
+ * Build a tracing registry. The returned registry exposes `span`,
173
+ * `spanSync`, `currentSpan`, `setAttributes`, `recordException`,
174
+ * `contextHeaders` / `extractContext` for W3C traceparent
175
+ * propagation, `requestMiddleware()` for per-request auto-spans, and
176
+ * `tap()` for framework hot-path wrapping. Detects
177
+ * `@opentelemetry/api` once at first use; without OTel installed the
178
+ * registry runs in pass-through mode but still propagates trace IDs
179
+ * over HTTP.
180
+ *
181
+ * @opts
182
+ * instrumentationName: string, // OTel tracer name; default "blamejs"
183
+ * instrumentationVersion: string, // OTel tracer version; default "0.0.0"
184
+ *
185
+ * @example
186
+ * var t = b.tracing.create({
187
+ * instrumentationName: "myapp",
188
+ * instrumentationVersion: "1.2.3",
189
+ * });
190
+ *
191
+ * var users = await t.span("load-users", async function (span) {
192
+ * span.setAttribute("user_id", "abc");
193
+ * span.addEvent("cache-miss");
194
+ * return await db.query("SELECT id, email FROM users");
195
+ * }, { kind: "internal", attributes: { route: "/users" } });
196
+ *
197
+ * // Outbound — propagate the active trace.
198
+ * var headers = t.contextHeaders();
199
+ * // → { traceparent: "00-<32 hex>-<16 hex>-01" } when a span is active
200
+ */
208
201
  function create(opts) {
209
202
  opts = opts || {};
210
203
  validateOpts(opts, [
@@ -461,6 +454,29 @@ function create(opts) {
461
454
 
462
455
  var _globalRegistry = null;
463
456
 
457
+ /**
458
+ * @primitive b.tracing.tap
459
+ * @signature b.tracing.tap(name, attributes, fn)
460
+ * @since 0.4.0
461
+ * @related b.tracing.create, b.metrics.tap, b.observability.tap
462
+ *
463
+ * Framework hot-path tracing tap. Modules call
464
+ * `tap("audit.record", { action: "login" }, fn)` without importing a
465
+ * registry. Until `b.tracing.create()` runs the call passes `fn(null)`
466
+ * through directly (zero overhead, no span); afterwards the active
467
+ * registry wraps `fn` in a span named `name` with the supplied
468
+ * attributes. The two-arg form `tap(name, fn)` is permitted when no
469
+ * attributes are needed.
470
+ *
471
+ * @example
472
+ * // Module-level — passthrough until a registry exists.
473
+ * var rows = b.tracing.tap("db.query", { table: "users" }, function () {
474
+ * return db.queryAll("SELECT id FROM users");
475
+ * });
476
+ *
477
+ * // Two-arg — no attributes:
478
+ * b.tracing.tap("queue.enqueue", function () { return enqueueJob(); });
479
+ */
464
480
  function tap(name, attributes, fn) {
465
481
  if (typeof attributes === "function") {
466
482
  fn = attributes; attributes = null;
package/lib/uuid.js CHANGED
@@ -1,32 +1,30 @@
1
1
  "use strict";
2
2
  /**
3
- * uuid — RFC 4122 v4 (random) + RFC 9562 v7 (time-ordered).
3
+ * @module b.uuid
4
+ * @featured true
5
+ * @nav Tools
6
+ * @title UUID
4
7
  *
5
- * Two flavors:
8
+ * @intro
9
+ * RFC 4122 v4 (random) + RFC 9562 v7 (time-ordered).
6
10
  *
7
- * b.uuid.v4() — fully random 128-bit UUID. Standard, portable,
8
- * the default choice when ordering doesn't matter.
11
+ * v4 is fully random the standard portable choice when ordering
12
+ * doesn't matter. v7 prefixes a 48-bit Unix-millisecond timestamp,
13
+ * then 74 random bits — IDs sort by creation time even
14
+ * lexicographically, ideal as a database PK because B-tree inserts
15
+ * stay near the right edge.
9
16
  *
10
- * b.uuid.v7() — Unix-millisecond timestamp prefix + 74 random
11
- * bits. Time-ordered (sorts by creation time even
12
- * lexicographically), ideal as a database PK
13
- * because B-tree inserts stay near the right edge
14
- * — no random scattering across the index.
17
+ * All entropy comes from `b.crypto.generateBytes`, which routes
18
+ * through Node's `crypto.randomBytes` same source as
19
+ * `crypto.randomUUID()`.
15
20
  *
16
- * b.uuid.parse(str) — { ok, version, bytes }. Validates the canonical
17
- * 8-4-4-4-12 hex form and the version + variant
18
- * bits. Returns ok:false (no throw) on bad input.
21
+ * Why ship v7 ourselves? Native `crypto.randomUUID()` only emits
22
+ * v4. v7 is the modern recommendation for any UUID landing in a
23
+ * sortable column (job queues, audit chain extensions, anything
24
+ * where insertion order matters for index locality).
19
25
  *
20
- * b.uuid.isValid(str) — boolean shorthand. No version/variant check
21
- * beyond shape operators who care use parse().
22
- *
23
- * All entropy comes from `b.crypto.generateBytes`, which routes through
24
- * `node:crypto.randomBytes` — same source as `crypto.randomUUID()`.
25
- *
26
- * Why ship v7 ourselves? Native `crypto.randomUUID()` only emits v4.
27
- * v7 is the modern recommendation for any UUID landing in a sortable
28
- * column (jobs queue, audit chain extensions, anything where insertion
29
- * order matters for index locality).
26
+ * @card
27
+ * RFC 4122 v4 (random) + RFC 9562 v7 (time-ordered).
30
28
  */
31
29
  var C = require("./constants");
32
30
  var { generateBytes } = require("./crypto");
@@ -67,6 +65,20 @@ function _bytesToString(bytes) {
67
65
  hex.slice(HEX_CLOCK_SEQ_END, HEX_NODE_END);
68
66
  }
69
67
 
68
+ /**
69
+ * @primitive b.uuid.v4
70
+ * @signature b.uuid.v4()
71
+ * @since 0.1.0
72
+ * @related b.uuid.v7, b.uuid.parse
73
+ *
74
+ * Fully random 128-bit UUID. Standard, portable; the default choice
75
+ * when ordering doesn't matter. Returns the canonical 8-4-4-4-12
76
+ * hex form.
77
+ *
78
+ * @example
79
+ * var id = b.uuid.v4();
80
+ * // → "f47ac10b-58cc-4372-a567-0e02b2c3d479"
81
+ */
70
82
  function v4() {
71
83
  var b = generateBytes(UUID_BYTE_LEN);
72
84
  // version = 4 (0100): high nibble of byte 6
@@ -76,6 +88,34 @@ function v4() {
76
88
  return _bytesToString(b);
77
89
  }
78
90
 
91
+ /**
92
+ * @primitive b.uuid.v7
93
+ * @signature b.uuid.v7(opts?)
94
+ * @since 0.4.0
95
+ * @related b.uuid.v4, b.uuid.parse
96
+ *
97
+ * RFC 9562 §5.7 time-ordered UUID. The first 48 bits encode a Unix
98
+ * millisecond timestamp (big-endian); the next 4 bits are version (7);
99
+ * the remaining 74 bits are random. IDs generated within the same
100
+ * millisecond sort by their random suffix; across milliseconds they
101
+ * sort by time. B-tree index locality is dramatically better than v4
102
+ * for INSERT-heavy tables.
103
+ *
104
+ * @opts
105
+ * now: number, // override the timestamp (testing / fixtures)
106
+ *
107
+ * @example
108
+ * var id = b.uuid.v7();
109
+ * // → "01941bf3-9c4a-7d8e-9c11-3a4b5c6d7e8f"
110
+ *
111
+ * // Deterministic fixture: same ms produces the same time prefix.
112
+ * var fixed = b.uuid.v7({ now: Date.UTC(2026, 0, 1) });
113
+ *
114
+ * // v7 sorts by time even as plain strings:
115
+ * var earlier = b.uuid.v7({ now: 1700000000000 });
116
+ * var later = b.uuid.v7({ now: 1700000001000 });
117
+ * earlier < later; // → true
118
+ */
79
119
  function v7(opts) {
80
120
  // RFC 9562 §5.7 layout:
81
121
  // bytes 0-5 : 48-bit big-endian Unix timestamp in milliseconds
@@ -101,6 +141,27 @@ function v7(opts) {
101
141
  return _bytesToString(b);
102
142
  }
103
143
 
144
+ /**
145
+ * @primitive b.uuid.parse
146
+ * @signature b.uuid.parse(str)
147
+ * @since 0.1.0
148
+ * @related b.uuid.isValid
149
+ *
150
+ * Strict parse: validates canonical form AND version (1-7) AND
151
+ * RFC 4122 variant. Returns `{ ok: true, version, bytes }` on success;
152
+ * `{ ok: false, reason }` on failure. Never throws — operators who
153
+ * want a thrown error layer one on top.
154
+ *
155
+ * @example
156
+ * var parsed = b.uuid.parse("f47ac10b-58cc-4372-a567-0e02b2c3d479");
157
+ * if (parsed.ok) {
158
+ * console.log(parsed.version); // → 4
159
+ * console.log(parsed.bytes); // → <Buffer f4 7a c1 0b ...>
160
+ * }
161
+ *
162
+ * b.uuid.parse("not-a-uuid").ok; // → false
163
+ * b.uuid.parse("not-a-uuid").reason; // → "malformed"
164
+ */
104
165
  function parse(str) {
105
166
  if (typeof str !== "string") return { ok: false, reason: "not-a-string" };
106
167
  // Length cap before regex — RFC 4122 canonical form is exactly 36
@@ -118,6 +179,20 @@ function parse(str) {
118
179
  return { ok: true, version: version, bytes: bytes };
119
180
  }
120
181
 
182
+ /**
183
+ * @primitive b.uuid.isValid
184
+ * @signature b.uuid.isValid(str)
185
+ * @since 0.1.0
186
+ * @related b.uuid.parse
187
+ *
188
+ * Loose shape-only check — returns `true` for any 8-4-4-4-12 hex
189
+ * string regardless of version or variant bits. Cheap. Use `parse()`
190
+ * when version/variant matter (most operator code does).
191
+ *
192
+ * @example
193
+ * b.uuid.isValid("f47ac10b-58cc-4372-a567-0e02b2c3d479"); // → true
194
+ * b.uuid.isValid("not-a-uuid"); // → false
195
+ */
121
196
  function isValid(str) {
122
197
  if (typeof str !== "string") return false;
123
198
  if (str.length > UUID_STR_MAX_LEN) return false;