@blamejs/core 0.15.11 → 0.15.13

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 (305) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +7 -23
  4. package/lib/acme.js +13 -16
  5. package/lib/agent-event-bus.js +5 -4
  6. package/lib/agent-idempotency.js +2 -5
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-tenant.js +2 -5
  10. package/lib/ai-adverse-decision.js +2 -15
  11. package/lib/ai-capability.js +1 -6
  12. package/lib/ai-dp.js +1 -5
  13. package/lib/ai-input.js +2 -2
  14. package/lib/ai-pref.js +3 -8
  15. package/lib/ai-quota.js +3 -14
  16. package/lib/api-key.js +37 -28
  17. package/lib/archive-adapters.js +2 -4
  18. package/lib/archive-entry-policy.js +32 -0
  19. package/lib/archive-read.js +5 -17
  20. package/lib/archive-tar-read.js +5 -16
  21. package/lib/archive.js +2 -10
  22. package/lib/arg-parser.js +7 -6
  23. package/lib/asyncapi-traits.js +2 -6
  24. package/lib/atomic-file.js +108 -31
  25. package/lib/audit-chain.js +133 -53
  26. package/lib/audit-daily-review.js +24 -14
  27. package/lib/audit-emit.js +82 -0
  28. package/lib/audit-sign.js +257 -0
  29. package/lib/audit.js +84 -0
  30. package/lib/auth/access-lock.js +5 -27
  31. package/lib/auth/dpop.js +23 -35
  32. package/lib/auth/fido-mds3.js +9 -15
  33. package/lib/auth/jwt-external.js +26 -8
  34. package/lib/auth/jwt.js +13 -15
  35. package/lib/auth/lockout.js +6 -25
  36. package/lib/auth/oauth.js +67 -45
  37. package/lib/auth/oid4vci.js +55 -32
  38. package/lib/auth/oid4vp.js +3 -2
  39. package/lib/auth/openid-federation.js +6 -6
  40. package/lib/auth/passkey.js +3 -3
  41. package/lib/auth/password.js +7 -1
  42. package/lib/auth/saml.js +37 -27
  43. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  44. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  45. package/lib/auth/sd-jwt-vc.js +1 -1
  46. package/lib/auth/status-list.js +7 -7
  47. package/lib/auth/step-up.js +6 -12
  48. package/lib/auth-bot-challenge.js +6 -37
  49. package/lib/backup/bundle.js +11 -18
  50. package/lib/backup/index.js +14 -47
  51. package/lib/bounded-map.js +112 -1
  52. package/lib/breach-deadline.js +1 -11
  53. package/lib/cache.js +7 -18
  54. package/lib/cbor.js +2 -1
  55. package/lib/cdn-cache-control.js +8 -9
  56. package/lib/cert.js +3 -10
  57. package/lib/chain-writer.js +162 -47
  58. package/lib/cli.js +5 -1
  59. package/lib/client-hints.js +10 -10
  60. package/lib/cloud-events.js +40 -31
  61. package/lib/cluster-storage.js +2 -38
  62. package/lib/cluster.js +4 -2
  63. package/lib/cms-codec.js +2 -1
  64. package/lib/codepoint-class.js +67 -1
  65. package/lib/compliance-ai-act-logging.js +1 -6
  66. package/lib/compliance-ai-act-transparency.js +2 -4
  67. package/lib/compliance-eaa.js +1 -11
  68. package/lib/compliance-sanctions-fetcher.js +21 -28
  69. package/lib/compliance-sanctions.js +2 -14
  70. package/lib/compliance.js +2 -9
  71. package/lib/config-drift.js +2 -12
  72. package/lib/content-digest.js +10 -8
  73. package/lib/cookies.js +5 -11
  74. package/lib/cose.js +19 -11
  75. package/lib/cra-report.js +1 -11
  76. package/lib/crypto-field.js +5 -10
  77. package/lib/crypto.js +120 -3
  78. package/lib/csp.js +235 -3
  79. package/lib/daemon.js +42 -41
  80. package/lib/data-act.js +19 -9
  81. package/lib/db-query.js +6 -40
  82. package/lib/db.js +34 -12
  83. package/lib/dbsc.js +5 -6
  84. package/lib/ddl-change-control.js +18 -14
  85. package/lib/deprecate.js +4 -5
  86. package/lib/did.js +6 -2
  87. package/lib/dsr.js +8 -12
  88. package/lib/external-db-migrate.js +21 -22
  89. package/lib/external-db.js +14 -26
  90. package/lib/fda-21cfr11.js +12 -8
  91. package/lib/fdx.js +22 -18
  92. package/lib/file-upload.js +80 -66
  93. package/lib/flag-evaluation-context.js +5 -8
  94. package/lib/framework-error.js +12 -0
  95. package/lib/framework-schema.js +19 -0
  96. package/lib/fsm.js +7 -12
  97. package/lib/gate-contract.js +869 -38
  98. package/lib/gdpr-ropa.js +4 -13
  99. package/lib/graphql-federation.js +1 -1
  100. package/lib/guard-agent-registry.js +2 -1
  101. package/lib/guard-all.js +1 -0
  102. package/lib/guard-archive.js +9 -30
  103. package/lib/guard-auth.js +23 -80
  104. package/lib/guard-cidr.js +20 -96
  105. package/lib/guard-csv.js +135 -196
  106. package/lib/guard-domain.js +23 -106
  107. package/lib/guard-dsn.js +16 -13
  108. package/lib/guard-email.js +46 -53
  109. package/lib/guard-envelope.js +1 -1
  110. package/lib/guard-event-bus-topic.js +2 -1
  111. package/lib/guard-filename.js +18 -62
  112. package/lib/guard-graphql.js +28 -75
  113. package/lib/guard-html-wcag.js +15 -2
  114. package/lib/guard-html.js +65 -117
  115. package/lib/guard-idempotency-key.js +2 -1
  116. package/lib/guard-image.js +280 -77
  117. package/lib/guard-imap-command.js +8 -9
  118. package/lib/guard-json.js +87 -103
  119. package/lib/guard-jsonpath.js +20 -88
  120. package/lib/guard-jwt.js +32 -114
  121. package/lib/guard-list-id.js +2 -7
  122. package/lib/guard-list-unsubscribe.js +2 -7
  123. package/lib/guard-mail-compose.js +5 -6
  124. package/lib/guard-mail-move.js +2 -1
  125. package/lib/guard-mail-query.js +5 -3
  126. package/lib/guard-mail-sieve.js +6 -7
  127. package/lib/guard-managesieve-command.js +5 -4
  128. package/lib/guard-markdown.js +76 -110
  129. package/lib/guard-message-id.js +5 -6
  130. package/lib/guard-mime.js +20 -99
  131. package/lib/guard-oauth.js +19 -73
  132. package/lib/guard-pdf.js +65 -72
  133. package/lib/guard-pop3-command.js +12 -13
  134. package/lib/guard-posture-chain.js +2 -1
  135. package/lib/guard-regex.js +24 -99
  136. package/lib/guard-saga-config.js +2 -1
  137. package/lib/guard-shell.js +22 -87
  138. package/lib/guard-smtp-command.js +8 -11
  139. package/lib/guard-sql.js +15 -13
  140. package/lib/guard-stream-args.js +2 -1
  141. package/lib/guard-svg.js +95 -140
  142. package/lib/guard-template.js +23 -80
  143. package/lib/guard-tenant-id.js +2 -1
  144. package/lib/guard-text.js +592 -0
  145. package/lib/guard-time.js +27 -95
  146. package/lib/guard-uuid.js +21 -93
  147. package/lib/guard-xml.js +76 -106
  148. package/lib/guard-yaml.js +24 -60
  149. package/lib/http-client-cache.js +8 -13
  150. package/lib/http-client-cookie-jar.js +2 -4
  151. package/lib/http-client.js +8 -21
  152. package/lib/http-message-signature.js +26 -8
  153. package/lib/i18n-messageformat.js +5 -7
  154. package/lib/i18n.js +83 -26
  155. package/lib/inbox.js +8 -8
  156. package/lib/incident-report.js +3 -21
  157. package/lib/ip-utils.js +49 -6
  158. package/lib/jobs.js +3 -2
  159. package/lib/keychain.js +6 -18
  160. package/lib/legal-hold.js +6 -15
  161. package/lib/log-stream-cloudwatch.js +44 -112
  162. package/lib/log-stream-otlp-grpc.js +28 -14
  163. package/lib/log-stream-otlp.js +16 -80
  164. package/lib/log-stream-syslog.js +6 -0
  165. package/lib/log-stream-webhook.js +20 -92
  166. package/lib/log.js +24 -2
  167. package/lib/mail-arc-sign.js +8 -3
  168. package/lib/mail-arf.js +3 -2
  169. package/lib/mail-auth.js +40 -66
  170. package/lib/mail-bimi.js +19 -39
  171. package/lib/mail-crypto-pgp.js +3 -2
  172. package/lib/mail-dav.js +8 -35
  173. package/lib/mail-deploy.js +6 -9
  174. package/lib/mail-dkim.js +19 -38
  175. package/lib/mail-greylist.js +15 -26
  176. package/lib/mail-helo.js +2 -3
  177. package/lib/mail-journal.js +2 -1
  178. package/lib/mail-mdn.js +4 -3
  179. package/lib/mail-rbl.js +5 -8
  180. package/lib/mail-scan.js +2 -2
  181. package/lib/mail-send-deliver.js +33 -20
  182. package/lib/mail-server-imap.js +32 -87
  183. package/lib/mail-server-jmap.js +35 -51
  184. package/lib/mail-server-managesieve.js +29 -83
  185. package/lib/mail-server-mx.js +33 -74
  186. package/lib/mail-server-net.js +177 -0
  187. package/lib/mail-server-pop3.js +30 -83
  188. package/lib/mail-server-rate-limit.js +30 -6
  189. package/lib/mail-server-submission.js +34 -73
  190. package/lib/mail-server-tls.js +89 -0
  191. package/lib/mail-spam-score.js +7 -8
  192. package/lib/mail-store.js +3 -2
  193. package/lib/mail.js +11 -11
  194. package/lib/markup-escape.js +31 -0
  195. package/lib/markup-tokenizer.js +54 -0
  196. package/lib/mcp-tool-registry.js +26 -23
  197. package/lib/mcp.js +1 -1
  198. package/lib/mdoc.js +11 -12
  199. package/lib/metrics.js +32 -30
  200. package/lib/middleware/age-gate.js +3 -23
  201. package/lib/middleware/api-encrypt.js +11 -22
  202. package/lib/middleware/assetlinks.js +2 -7
  203. package/lib/middleware/bearer-auth.js +2 -1
  204. package/lib/middleware/body-parser.js +79 -54
  205. package/lib/middleware/bot-disclose.js +7 -10
  206. package/lib/middleware/bot-guard.js +2 -10
  207. package/lib/middleware/csrf-protect.js +13 -2
  208. package/lib/middleware/daily-byte-quota.js +6 -26
  209. package/lib/middleware/deny-response.js +19 -1
  210. package/lib/middleware/fetch-metadata.js +7 -0
  211. package/lib/middleware/idempotency-key.js +4 -20
  212. package/lib/middleware/rate-limit.js +20 -28
  213. package/lib/middleware/scim-server.js +3 -2
  214. package/lib/middleware/security-headers.js +9 -1
  215. package/lib/middleware/security-txt.js +2 -6
  216. package/lib/middleware/tus-upload.js +12 -27
  217. package/lib/middleware/web-app-manifest.js +2 -7
  218. package/lib/migrations.js +4 -13
  219. package/lib/mime-parse.js +34 -17
  220. package/lib/module-loader.js +44 -0
  221. package/lib/money.js +105 -0
  222. package/lib/mtls-ca.js +116 -36
  223. package/lib/network-byte-quota.js +4 -18
  224. package/lib/network-dane.js +8 -6
  225. package/lib/network-dns-resolver.js +97 -6
  226. package/lib/network-dns.js +22 -26
  227. package/lib/network-dnssec.js +16 -16
  228. package/lib/network-heartbeat.js +6 -5
  229. package/lib/network-proxy.js +3 -7
  230. package/lib/network-smtp-policy.js +29 -41
  231. package/lib/network-tls.js +74 -55
  232. package/lib/network.js +2 -6
  233. package/lib/nis2-report.js +1 -11
  234. package/lib/nonce-store.js +81 -3
  235. package/lib/notify.js +7 -12
  236. package/lib/numeric-bounds.js +22 -8
  237. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  238. package/lib/object-store/azure-blob.js +5 -45
  239. package/lib/object-store/gcs.js +8 -71
  240. package/lib/object-store/http-put.js +1 -5
  241. package/lib/object-store/http-request.js +128 -0
  242. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  243. package/lib/object-store/sigv4.js +9 -77
  244. package/lib/observability-otlp-exporter.js +9 -25
  245. package/lib/observability.js +95 -0
  246. package/lib/openapi-paths-builder.js +7 -3
  247. package/lib/otel-export.js +7 -10
  248. package/lib/outbox.js +43 -37
  249. package/lib/pagination.js +11 -15
  250. package/lib/parsers/safe-env.js +5 -4
  251. package/lib/parsers/safe-ini.js +10 -4
  252. package/lib/parsers/safe-toml.js +11 -9
  253. package/lib/parsers/safe-xml.js +2 -2
  254. package/lib/parsers/safe-yaml.js +7 -6
  255. package/lib/pick.js +63 -5
  256. package/lib/pipl-cn.js +69 -59
  257. package/lib/privacy-pass.js +8 -6
  258. package/lib/problem-details.js +2 -5
  259. package/lib/pubsub.js +4 -8
  260. package/lib/redact.js +2 -1
  261. package/lib/render.js +4 -2
  262. package/lib/request-helpers.js +133 -6
  263. package/lib/restore.js +5 -22
  264. package/lib/retention.js +3 -5
  265. package/lib/safe-async.js +457 -0
  266. package/lib/safe-buffer.js +137 -6
  267. package/lib/safe-decompress.js +2 -1
  268. package/lib/safe-dns.js +2 -1
  269. package/lib/safe-ical.js +12 -26
  270. package/lib/safe-json.js +7 -8
  271. package/lib/safe-jsonpath.js +6 -5
  272. package/lib/safe-mime.js +25 -29
  273. package/lib/safe-redirect.js +2 -5
  274. package/lib/safe-schema.js +7 -6
  275. package/lib/safe-sql.js +126 -0
  276. package/lib/safe-vcard.js +12 -26
  277. package/lib/sandbox-worker.js +9 -2
  278. package/lib/sandbox.js +3 -2
  279. package/lib/scheduler.js +5 -12
  280. package/lib/sec-cyber.js +82 -37
  281. package/lib/seeders.js +9 -21
  282. package/lib/self-update.js +92 -69
  283. package/lib/session-device-binding.js +112 -66
  284. package/lib/session.js +194 -6
  285. package/lib/sql.js +225 -78
  286. package/lib/sse.js +6 -10
  287. package/lib/static.js +136 -98
  288. package/lib/storage.js +4 -2
  289. package/lib/structured-fields.js +313 -17
  290. package/lib/tenant-quota.js +2 -20
  291. package/lib/tsa.js +11 -15
  292. package/lib/validate-opts.js +154 -8
  293. package/lib/vault/seal-pem-file.js +30 -48
  294. package/lib/vault-aad.js +3 -2
  295. package/lib/vc.js +2 -7
  296. package/lib/vex.js +35 -13
  297. package/lib/web-push-vapid.js +23 -20
  298. package/lib/webhook-dispatcher.js +706 -0
  299. package/lib/webhook.js +43 -18
  300. package/lib/websocket-channels.js +9 -7
  301. package/lib/websocket.js +7 -3
  302. package/lib/worm.js +2 -3
  303. package/lib/ws-client.js +8 -10
  304. package/package.json +1 -1
  305. package/sbom.cdx.json +6 -6
package/lib/sql.js CHANGED
@@ -1107,39 +1107,11 @@ function _checkRawFragment(sql, params, opts, where) {
1107
1107
  // comments are skipped. Single linear pass, no backtracking regex. Same
1108
1108
  // shape as db-query.js's scanner.
1109
1109
  function _assertRawNoStringLiteral(sql, where) {
1110
- var i = 0;
1111
- var len = sql.length;
1112
- while (i < len) {
1113
- var ch = sql.charAt(i);
1114
- var next = i + 1 < len ? sql.charAt(i + 1) : "";
1115
- if (ch === '"') {
1116
- i += 1;
1117
- while (i < len) {
1118
- if (sql.charAt(i) === '"') {
1119
- if (sql.charAt(i + 1) === '"') { i += 2; continue; }
1120
- i += 1; break;
1121
- }
1122
- i += 1;
1123
- }
1124
- continue;
1125
- }
1126
- if (ch === "-" && next === "-") {
1127
- while (i < len && sql.charAt(i) !== "\n") i += 1;
1128
- continue;
1129
- }
1130
- if (ch === "/" && next === "*") {
1131
- i += 2;
1132
- while (i < len && !(sql.charAt(i) === "*" && sql.charAt(i + 1) === "/")) i += 1;
1133
- i += 2;
1134
- continue;
1135
- }
1136
- if (ch === "'") {
1137
- throw _err(where + ": raw SQL must not contain a string literal ('...') - bind " +
1138
- "every value with a ? placeholder, or pass { allowLiterals: true } when the " +
1139
- "literal is static and operator-controlled", "sql-builder/raw-literal");
1140
- }
1141
- i += 1;
1142
- }
1110
+ safeSql.assertNoRawStringLiteral(sql, where, function (w) {
1111
+ return _err(w + ": raw SQL must not contain a string literal ('...') - bind " +
1112
+ "every value with a ? placeholder, or pass { allowLiterals: true } when the " +
1113
+ "literal is static and operator-controlled", "sql-builder/raw-literal");
1114
+ });
1143
1115
  }
1144
1116
 
1145
1117
  // Refuse the two-char Postgres JSONB key-existence tokens (?| / ?&) in a raw
@@ -1188,51 +1160,10 @@ function _assertNoRawJsonbKeyOp(sql, where) {
1188
1160
  var _countPlaceholders = safeSql.countPlaceholders;
1189
1161
 
1190
1162
  // Translate the builder's `?` placeholders to a dialect's positional form
1191
- // (Postgres `$1..$N`; SQLite / MySQL keep `?`). Quote / comment-aware single
1192
- // pass - a `?` inside a string literal, a quoted identifier, or a comment is
1193
- // NOT a placeholder and is left untouched. This is the toExternalSql terminal
1194
- // for code that hands the SQL to an operator-supplied driver directly (no
1195
- // clusterStorage in the path to do the rewrite). The translator lives here -
1196
- // the builder owns its driver-final output rendering - rather than reaching
1197
- // into clusterStorage (which transitively requires this module).
1198
- function _toPositional(sql, dialect) {
1199
- if (dialect !== "postgres") return sql;
1200
- var out = "";
1201
- var n = 0;
1202
- var i = 0;
1203
- var len = sql.length;
1204
- while (i < len) {
1205
- var c = sql.charAt(i);
1206
- var nx = i + 1 < len ? sql.charAt(i + 1) : "";
1207
- if (c === "'" || c === '"' || c === "`") {
1208
- out += c;
1209
- i += 1;
1210
- while (i < len) {
1211
- var q = sql.charAt(i);
1212
- if (q === c) {
1213
- if (sql.charAt(i + 1) === c) { out += c + c; i += 2; continue; }
1214
- out += c; i += 1; break;
1215
- }
1216
- out += q; i += 1;
1217
- }
1218
- continue;
1219
- }
1220
- if (c === "-" && nx === "-") {
1221
- while (i < len && sql.charAt(i) !== "\n") { out += sql.charAt(i); i += 1; }
1222
- continue;
1223
- }
1224
- if (c === "/" && nx === "*") {
1225
- out += "/*"; i += 2;
1226
- while (i < len && !(sql.charAt(i) === "*" && sql.charAt(i + 1) === "/")) { out += sql.charAt(i); i += 1; }
1227
- if (i < len) { out += "*/"; i += 2; }
1228
- continue;
1229
- }
1230
- if (c === "?") { n += 1; out += "$" + n; i += 1; continue; }
1231
- out += c;
1232
- i += 1;
1233
- }
1234
- return out;
1235
- }
1163
+ // (Postgres `$1..$N`; SQLite / MySQL keep `?`); composed from safe-sql so the
1164
+ // quote / comment / backtick skip rules live in one place. The toExternalSql
1165
+ // terminal for code that hands the SQL to an operator-supplied driver directly.
1166
+ var _toPositional = safeSql.toPositional;
1236
1167
 
1237
1168
  /**
1238
1169
  * @primitive b.sql.toExternalSql
@@ -1922,6 +1853,163 @@ class InsertBuilder extends Builder {
1922
1853
  }
1923
1854
  }
1924
1855
 
1856
+ // ---- INSERT ... SELECT ... WHERE (conditional / append-only) --------
1857
+ //
1858
+ // A conditional INSERT that materialises its row from a value-less SELECT
1859
+ // guarded by a WHERE - INSERT INTO t (cols) SELECT <cells> WHERE <guard>.
1860
+ // The append-only-ledger debit idiom: the new row is written ONLY when a
1861
+ // guard derived from the table itself holds (a store-credit / gift-card /
1862
+ // points / metered-quota balance that lives on the latest row, with no
1863
+ // mutable counter row to increment()). The SELECT has no FROM - it is a
1864
+ // single computed row that the WHERE either admits (one row inserted) or
1865
+ // rejects (zero rows), evaluated atomically inside the INSERT against the
1866
+ // table the guard's correlated subquery / EXISTS references, so two racing
1867
+ // debits cannot both pass the balance check.
1868
+ //
1869
+ // Standard-SQL across sqlite / Postgres / MySQL - the only dialect-divergent
1870
+ // clause is RETURNING (Postgres / SQLite; refused on MySQL by the shared
1871
+ // _renderReturning, which the MySQL caller replaces with an explicit read).
1872
+ // Every SELECT cell routes through the SAME _renderValueCell choke-point
1873
+ // INSERT VALUES uses (so a cell is a bound ?, a b.sql.cast(...) ?::type, or a
1874
+ // b.sql.fn(...) allowlisted server function - no param), and the guard is a
1875
+ // full Predicate (the whole where-family: whereExists / whereSub / whereOp /
1876
+ // whereRaw / whereGroup compose, which is how a balance fence is expressed).
1877
+ //
1878
+ // Safety default: an INSERT...SELECT with no WHERE is just an INSERT...VALUES
1879
+ // and shipping it un-guarded is almost always a bug, so the verb THROWS
1880
+ // without a where() unless allowNoWhere() opts in - the same discipline
1881
+ // update / delete apply.
1882
+ class InsertSelectWhereBuilder extends Builder {
1883
+ constructor(tableNameOrRef, opts) {
1884
+ super("insert-select-where", tableNameOrRef, opts);
1885
+ this._columns = null;
1886
+ this._values = null; // single row, aligned to _columns
1887
+ this._where = new Predicate(this, "AND");
1888
+ this._returning = null;
1889
+ this._allowNoWhere = false;
1890
+ }
1891
+
1892
+ // Declare the target column list (validated + gated). values() infers it
1893
+ // from a row object's keys when omitted, exactly like INSERT.
1894
+ columns(cols) {
1895
+ if (!Array.isArray(cols) || cols.length === 0) {
1896
+ throw _err("columns() expects a non-empty array", "sql-builder/bad-columns");
1897
+ }
1898
+ var self = this;
1899
+ cols.forEach(function (c) { self._assertColumnMember(c, "insertSelectWhere"); _validateColumn(c); });
1900
+ this._columns = cols.slice();
1901
+ return this;
1902
+ }
1903
+
1904
+ // values(obj) - one row from a column->value map (sets _columns from the
1905
+ // keys when not already declared). values(array) - one positional row
1906
+ // aligned to a prior columns() call. A single row only: the SELECT
1907
+ // materialises exactly one candidate row gated by the WHERE.
1908
+ values(rowOrArray) {
1909
+ if (Array.isArray(rowOrArray)) {
1910
+ if (this._columns === null) {
1911
+ throw _err("values(array) requires a prior columns([...]) call", "sql-builder/no-columns");
1912
+ }
1913
+ if (rowOrArray.length !== this._columns.length) {
1914
+ throw _err("values(array): " + rowOrArray.length + " values but " +
1915
+ this._columns.length + " columns", "sql-builder/value-count");
1916
+ }
1917
+ this._values = rowOrArray.slice();
1918
+ return this;
1919
+ }
1920
+ if (rowOrArray && typeof rowOrArray === "object") {
1921
+ var keys = Object.keys(rowOrArray);
1922
+ if (keys.length === 0) throw _err("insertSelectWhere row object is empty", "sql-builder/empty-values");
1923
+ if (this._columns === null) this.columns(keys);
1924
+ var self = this;
1925
+ this._values = this._columns.map(function (c) {
1926
+ if (!Object.prototype.hasOwnProperty.call(rowOrArray, c)) {
1927
+ throw _err("insertSelectWhere row is missing column '" + c + "'", "sql-builder/missing-column");
1928
+ }
1929
+ return rowOrArray[c];
1930
+ });
1931
+ keys.forEach(function (k) {
1932
+ if (self._columns.indexOf(k) === -1) {
1933
+ throw _err("insertSelectWhere row has column '" + k + "' not in the column set",
1934
+ "sql-builder/extra-column");
1935
+ }
1936
+ });
1937
+ return this;
1938
+ }
1939
+ throw _err("insertSelectWhere values() requires a row object or a value array aligned to columns()",
1940
+ "sql-builder/bad-values");
1941
+ }
1942
+
1943
+ // A deliberate un-guarded conditional insert opts in here - same shape as
1944
+ // update / delete. Without it the verb refuses an empty WHERE.
1945
+ allowNoWhere() { this._allowNoWhere = true; return this; }
1946
+
1947
+ where() { this._where.where.apply(this._where, arguments); return this; }
1948
+ andWhere() { this._where.andWhere.apply(this._where, arguments); return this; }
1949
+ orWhere() { this._where.orWhere.apply(this._where, arguments); return this; }
1950
+ whereOp(col, op, value) { this._where.whereOp(col, op, value); return this; }
1951
+ orWhereOp(col, op, value) { this._where.orWhereOp(col, op, value); return this; }
1952
+ whereIn(col, values) { this._where.whereIn(col, values); return this; }
1953
+ whereNotIn(col, values) { this._where.whereNotIn(col, values); return this; }
1954
+ orWhereIn(col, values) { this._where.orWhereIn(col, values); return this; }
1955
+ whereInArray(col, values) { this._where.whereInArray(col, values); return this; }
1956
+ orWhereInArray(col, values) { this._where.orWhereInArray(col, values); return this; }
1957
+ whereInJsonEach(col, jsonArrayString) { this._where.whereInJsonEach(col, jsonArrayString); return this; }
1958
+ whereMatch(target, expr) { this._where.whereMatch(target, expr); return this; }
1959
+ whereNull(col) { this._where.whereNull(col); return this; }
1960
+ whereNotNull(col) { this._where.whereNotNull(col); return this; }
1961
+ orWhereNull(col) { this._where.orWhereNull(col); return this; }
1962
+ whereLike(col, term, mode) { this._where.whereLike(col, term, mode); return this; }
1963
+ orWhereLike(col, term, mode) { this._where.orWhereLike(col, term, mode); return this; }
1964
+ whereBetween(col, low, high) { this._where.whereBetween(col, low, high); return this; }
1965
+ whereSub(col, op, sub) { this._where.whereSub(col, op, sub); return this; }
1966
+ whereExists(sub) { this._where.whereExists(sub); return this; }
1967
+ whereNotExists(sub) { this._where.whereNotExists(sub); return this; }
1968
+ orWhereExists(sub) { this._where.orWhereExists(sub); return this; }
1969
+ whereGroup(closure) { this._where.whereGroup(closure); return this; }
1970
+ orWhereGroup(closure) { this._where.orWhereGroup(closure); return this; }
1971
+ whereRaw(sql, params, opts) { this._where.whereRaw(sql, params, opts); return this; }
1972
+ orWhereRaw(sql, params, opts) { this._where.orWhereRaw(sql, params, opts); return this; }
1973
+
1974
+ returning(cols) { this._returning = _normReturning(cols); return this; }
1975
+
1976
+ _render() {
1977
+ if (this._columns === null || this._values === null) {
1978
+ throw _err("insertSelectWhere requires columns + a values() row", "sql-builder/empty-values");
1979
+ }
1980
+ if (this._where.length === 0 && !this._allowNoWhere) {
1981
+ throw _err("refusing unconditional insertSelectWhere - call where(...) first or " +
1982
+ "allowNoWhere() (an un-guarded INSERT...SELECT is just INSERT...VALUES)",
1983
+ "sql-builder/no-where");
1984
+ }
1985
+ var dialect = this._dialect;
1986
+ var params = [];
1987
+ var quotedCols = this._columns.map(function (c) { return _quoteId(c, dialect); }).join(", ");
1988
+
1989
+ // Each SELECT cell renders through the same choke-point INSERT VALUES
1990
+ // uses: `?` (bound), `?::type` (cast), or an allowlisted server-function
1991
+ // token (NOW() / CURRENT_TIMESTAMP - no param).
1992
+ var cells = [];
1993
+ for (var v = 0; v < this._values.length; v += 1) {
1994
+ var rendered = _renderValueCell(this._values[v], dialect);
1995
+ cells.push(rendered.sql);
1996
+ for (var rp = 0; rp < rendered.params.length; rp += 1) params.push(rendered.params[rp]);
1997
+ }
1998
+
1999
+ var sql = "INSERT INTO " + this._table.ref(dialect) + " (" + quotedCols + ") SELECT " +
2000
+ cells.join(", ");
2001
+
2002
+ var w = this._where.build();
2003
+ if (w.sql) {
2004
+ sql += " WHERE " + w.sql;
2005
+ for (var wi = 0; wi < w.params.length; wi += 1) params.push(w.params[wi]);
2006
+ }
2007
+
2008
+ sql += _renderReturning(this._returning, dialect);
2009
+ return _emit(sql, params);
2010
+ }
2011
+ }
2012
+
1925
2013
  // ---- UPDATE ---------------------------------------------------------
1926
2014
 
1927
2015
  class UpdateBuilder extends Builder {
@@ -3699,6 +3787,64 @@ function select(tableNameOrRef, opts) { return new SelectBuilder(tableNameOrRef,
3699
3787
  */
3700
3788
  function insert(tableNameOrRef, opts) { return new InsertBuilder(tableNameOrRef, opts); }
3701
3789
 
3790
+ /**
3791
+ * @primitive b.sql.insertSelectWhere
3792
+ * @signature b.sql.insertSelectWhere(table, opts?)
3793
+ * @since 0.15.13
3794
+ * @status stable
3795
+ * @related b.sql.insert, b.sql.upsert, b.sql.update
3796
+ *
3797
+ * Start a conditional `INSERT ... SELECT ... WHERE` builder - a row written
3798
+ * ONLY when a guard derived from the table itself holds. Emits
3799
+ * `INSERT INTO t (cols) SELECT <cells> WHERE <guard>`: the value-less SELECT
3800
+ * is a single computed candidate row the WHERE either admits (one row
3801
+ * inserted) or rejects (zero rows). It is the race-free append-only-ledger
3802
+ * debit - a store-credit / gift-card / wallet / points / metered-quota /
3803
+ * seat-counter balance that lives only on the latest row, with no mutable
3804
+ * counter row to `increment()`. The guard's correlated subquery / `EXISTS`
3805
+ * is evaluated atomically inside the INSERT, so two concurrent debits cannot
3806
+ * both pass the same balance check.
3807
+ *
3808
+ * Supply the row via `columns([...])` + `values([...])` (positional),
3809
+ * `values({ ... })` (one row object, inferring the column list from its
3810
+ * keys), then the guard via the full `where` family (`whereExists` /
3811
+ * `whereSub` / `whereOp` / `whereGroup` / `whereRaw` all compose - the
3812
+ * balance fence is typically an `EXISTS` against the same table). Each SELECT
3813
+ * cell routes through the same choke-point INSERT `values()` uses, so a cell
3814
+ * may be a bound `?`, a `b.sql.cast(...)` (`?::type`), or a `b.sql.fn(...)`
3815
+ * allowlisted server function (`NOW()`, no param). Standard SQL across sqlite
3816
+ * / Postgres / MySQL; only `RETURNING` diverges (Postgres / SQLite - refused
3817
+ * on MySQL, run an explicit read).
3818
+ *
3819
+ * Safety default: an INSERT...SELECT with no WHERE is just an
3820
+ * INSERT...VALUES, so the verb THROWS without a `where()` unless
3821
+ * `allowNoWhere()` opts in - the same discipline `update` / `delete` apply.
3822
+ *
3823
+ * @opts
3824
+ * dialect: string, // postgres | sqlite | mysql (default sqlite)
3825
+ * schema: string, // schema qualifier
3826
+ * prefix: string, // operator app-table namespace prefix
3827
+ * allowedColumns: array, // column-membership gate set
3828
+ *
3829
+ * @example
3830
+ * // Append a -25 debit ONLY if the wallet's balance row still covers it -
3831
+ * // a race-free conditional insert with no read-modify-write. The guard is
3832
+ * // an EXISTS over a same-dialect sub-builder (no raw statement verb).
3833
+ * var covered = b.sql.select("wallet", { dialect: "postgres" })
3834
+ * .selectRaw("1")
3835
+ * .whereRaw('"id" = ? AND "balance" >= ?', ["w-1", 25]);
3836
+ * b.sql.insertSelectWhere("wallet_ledger", { dialect: "postgres" })
3837
+ * .values({ wallet_id: "w-1", amount: -25, at: b.sql.fn("NOW") })
3838
+ * .whereExists(covered)
3839
+ * .returning(["id"])
3840
+ * .toSql();
3841
+ * // -> { sql: 'INSERT INTO wallet_ledger ("wallet_id", "amount", "at") ' +
3842
+ * // 'SELECT ?, ?, NOW() WHERE EXISTS (SELECT 1 FROM wallet ' +
3843
+ * // 'WHERE ("id" = ? AND "balance" >= ?)) RETURNING "id"',
3844
+ * // params: ["w-1", -25, "w-1", 25] }
3845
+ */
3846
+ function insertSelectWhere(tableNameOrRef, opts) { return new InsertSelectWhereBuilder(tableNameOrRef, opts); }
3847
+
3702
3848
  /**
3703
3849
  * @primitive b.sql.update
3704
3850
  * @signature b.sql.update(table, opts?)
@@ -3806,6 +3952,7 @@ module.exports = {
3806
3952
  // Verbs
3807
3953
  select: select,
3808
3954
  insert: insert,
3955
+ insertSelectWhere: insertSelectWhere,
3809
3956
  update: update,
3810
3957
  delete: del,
3811
3958
  upsert: upsert,
package/lib/sse.js CHANGED
@@ -76,6 +76,8 @@
76
76
 
77
77
  var C = require("./constants");
78
78
  var audit = require("./audit");
79
+ var validateOpts = require("./validate-opts");
80
+ var numericBounds = require("./numeric-bounds");
79
81
  var { SseError } = require("./framework-error");
80
82
 
81
83
  // Per W3C SSE — the wire format uses LF as terminator. A single LF
@@ -228,10 +230,8 @@ function _readLastEventId(req) {
228
230
  function create(req, res, opts) {
229
231
  opts = opts || {};
230
232
  var errorClass = opts.errorClass || SseError;
231
- if (!res || typeof res.write !== "function" || typeof res.end !== "function") {
232
- throw errorClass.factory("sse/bad-res",
233
- "sse.create: res must be a writable response stream");
234
- }
233
+ validateOpts.requireMethods(res, ["write", "end"],
234
+ "sse.create: res (writable response stream)", errorClass, "sse/bad-res");
235
235
  var heartbeatMs = opts.heartbeatMs;
236
236
  if (heartbeatMs === undefined) heartbeatMs = C.TIME.seconds(15);
237
237
  if (typeof heartbeatMs !== "number" || !isFinite(heartbeatMs) ||
@@ -255,12 +255,8 @@ function create(req, res, opts) {
255
255
  // never affected. Config-time input → throw on a bad value.
256
256
  var maxBufferedBytes = opts.maxBufferedBytes;
257
257
  if (maxBufferedBytes === undefined) maxBufferedBytes = C.BYTES.mib(1);
258
- if (typeof maxBufferedBytes !== "number" || !isFinite(maxBufferedBytes) ||
259
- maxBufferedBytes <= 0 || Math.floor(maxBufferedBytes) !== maxBufferedBytes) {
260
- throw errorClass.factory("sse/bad-opts",
261
- "sse.create: maxBufferedBytes must be a positive integer byte count (got " +
262
- JSON.stringify(maxBufferedBytes) + ")");
263
- }
258
+ numericBounds.requirePositiveFiniteInt(maxBufferedBytes,
259
+ "sse.create: maxBufferedBytes", errorClass, "sse/bad-opts");
264
260
 
265
261
  var lastEventId = _readLastEventId(req);
266
262
 
package/lib/static.js CHANGED
@@ -489,84 +489,147 @@ function _httpDate(date) {
489
489
  }
490
490
 
491
491
  function _validateCreateOpts(opts) {
492
- validateOpts.requireObject(opts, "staticServe.create", StaticServeError);
493
- validateOpts.requireNonEmptyString(opts.root, "staticServe.create: root", StaticServeError, "BAD_OPT");
492
+ // Declarative per-field validation. shape() runs requireObject(opts)
493
+ // itself (same "opts must be an object" / BAD_OPT contract the inline
494
+ // requireObject produced), then dispatches each field. A field whose
495
+ // check is one of the rule tokens maps to the token directly; a field
496
+ // with a bespoke message (mountPath / hashedPathPattern), a
497
+ // duck-typed-handle shape (permissions / cache / fileType / retention /
498
+ // revokeStore — each carrying its own operator-facing description), a
499
+ // numeric bound, or an audit/observability shape uses the
500
+ // validator-function hatch so the exact thrown code + message + label
501
+ // are preserved. Cross-field business logic (root existence,
502
+ // allowedFileTypes↔fileType wiring, the contentSafety map, the
503
+ // mountType enum, the quota↔cache requirement) stays below shape.
504
+ validateOpts.shape(opts, {
505
+ root: "required-string",
506
+ mountPath: function (value, _label, errorClass, code) {
507
+ // empty string is operator-permissible: "no mount, root is request URL"
508
+ if (typeof value === "string" && value.length === 0) return;
509
+ if (value !== undefined && value !== null && typeof value !== "string") {
510
+ throw errorClass.factory(code, "staticServe.create: mountPath must be a string");
511
+ }
512
+ },
513
+ hashedPathPattern: function (value, _label, errorClass, code) {
514
+ if (value !== undefined && value !== null && !(value instanceof RegExp)) {
515
+ throw errorClass.factory(code, "staticServe.create: hashedPathPattern must be a RegExp");
516
+ }
517
+ },
518
+ // indexFile === null is the operator's "disable" sentinel; the helper
519
+ // returns null/undefined unchanged so we keep that semantic.
520
+ indexFile: "optional-string",
521
+ defaultMaxAge: function (value, label, errorClass, code) {
522
+ numericBounds.requireNonNegativeFiniteIntIfPresent(value, label, errorClass, code);
523
+ },
524
+ contentTypes: "optional-plain-object",
525
+ // contentSafety — extension-keyed gate map. undefined → the framework
526
+ // wires b.guardAll.byExtension({ profile: "strict" }) so every shipped
527
+ // guard is ON by default; null is the explicit opt-out (audited at
528
+ // create() time); a plain { ext: gate } object validates each value is a
529
+ // gate (a .check fn). The label / code / message match the prior inline
530
+ // check exactly so a test asserting them still holds.
531
+ contentSafety: function (value, _label, errorClass, code) {
532
+ if (value === undefined || value === null) return;
533
+ validateOpts.optionalPlainObject(value,
534
+ "staticServe.create: contentSafety", errorClass, code,
535
+ "must be a plain { ext: gate } object, null to opt out, or " +
536
+ "undefined for the default-on b.guardAll wiring");
537
+ var safetyKeys = Object.keys(value);
538
+ for (var sk = 0; sk < safetyKeys.length; sk++) {
539
+ var ext = safetyKeys[sk];
540
+ var g = value[ext];
541
+ if (!g || typeof g.check !== "function") {
542
+ throw errorClass.factory(code,
543
+ "staticServe.create: contentSafety[" + JSON.stringify(ext) +
544
+ "] must be a gate (b.guardCsv.gate / b.guardHtml.gate / etc.)");
545
+ }
546
+ }
547
+ },
548
+ permissions: function (value, label, errorClass, code) {
549
+ validateOpts.optionalObjectWithMethod(value, "check", label, errorClass, code,
550
+ "must be a b.permissions instance (check fn)");
551
+ },
552
+ cache: function (value, label, errorClass, code) {
553
+ validateOpts.optionalObjectWithMethod(value, "get", label, errorClass, code,
554
+ "must be a b.cache instance (used for cluster-shared bandwidth + concurrency tracking)");
555
+ },
556
+ fileType: function (value, label, errorClass, code) {
557
+ validateOpts.optionalObjectWithMethod(value, "detect", label, errorClass, code,
558
+ "must be a b.fileType instance (magic-byte MIME detection)");
559
+ },
560
+ retention: function (value, label, errorClass, code) {
561
+ validateOpts.optionalObjectWithMethod(value, "isServable", label, errorClass, code,
562
+ "must expose isServable(absPath, ctx) → boolean (compliance retention check)");
563
+ },
564
+ revokeStore: function (value, label, errorClass, code) {
565
+ validateOpts.optionalObjectWithMethod(value, "isRevoked", label, errorClass, code,
566
+ "must expose isRevoked(key) and revoke(key) for force-revoke support");
567
+ },
568
+ allowedFileTypes: "optional-string-array",
569
+ audit: function (value, _label, errorClass, _code) {
570
+ validateOpts.auditShape(value, "staticServe.create", errorClass);
571
+ },
572
+ observability: function (value, _label, errorClass, _code) {
573
+ validateOpts.observabilityShape(value, "staticServe.create", errorClass);
574
+ },
575
+ onServe: "optional-function",
576
+ onError: "optional-function",
577
+ acceptRanges: "optional-boolean",
578
+ auditSuccess: "optional-boolean",
579
+ auditFailures: "optional-boolean",
580
+ safeAttachmentForRiskyMimes: "optional-boolean",
581
+ forceAttachmentForNonText: "optional-boolean",
582
+ safeRenderSvg: "optional-boolean",
583
+ safeRenderPdf: "optional-boolean",
584
+ maxBytesPerActorPerWindowMs: function (value, label, errorClass, code) {
585
+ numericBounds.requireNonNegativeFiniteIntIfPresent(value, label, errorClass, code);
586
+ },
587
+ maxBytesAllActorsPerWindowMs: function (value, label, errorClass, code) {
588
+ numericBounds.requireNonNegativeFiniteIntIfPresent(value, label, errorClass, code);
589
+ },
590
+ bandwidthWindowMs: function (value, label, errorClass, code) {
591
+ numericBounds.requirePositiveFiniteIntIfPresent(value, label, errorClass, code);
592
+ },
593
+ maxConcurrentDownloadsPerActor: function (value, label, errorClass, code) {
594
+ numericBounds.requireNonNegativeFiniteIntIfPresent(value, label, errorClass, code);
595
+ },
596
+ maxIdleMs: function (value, label, errorClass, code) {
597
+ numericBounds.requirePositiveFiniteIntIfPresent(value, label, errorClass, code);
598
+ },
599
+ // maxRangeBytes — per-range byte cap (slowloris-range defense). A
600
+ // positive finite integer caps Range requests; Infinity is the
601
+ // documented opt-out, so the numericBounds finite-int helpers (which
602
+ // reject Infinity) can't be used directly here.
603
+ maxRangeBytes: function (value, label, errorClass, code) {
604
+ if (value === undefined || value === null || value === Infinity) return;
605
+ numericBounds.requirePositiveFiniteInt(value, label, errorClass, code);
606
+ },
607
+ }, "staticServe.create", StaticServeError, "BAD_OPT", {
608
+ // contentSafetyDisabledReason (audit-row string) and mountType (curated
609
+ // | user-content enum) carry dedicated cross-field validation below
610
+ // shape — the audit reason read and the enum throw whose exact message a
611
+ // test asserts. Listing them here keeps that bespoke logic the single
612
+ // validator rather than re-checking the same key inside shape.
613
+ allow: ["contentSafetyDisabledReason", "mountType"],
614
+ });
615
+
494
616
  if (!nodeFs.existsSync(opts.root)) {
495
617
  throw _err("BAD_OPT", "staticServe.create: root does not exist: " + opts.root);
496
618
  }
497
- if (typeof opts.mountPath === "string" && opts.mountPath.length === 0) {
498
- // empty string is operator-permissible: "no mount, root is request URL"
499
- } else if (opts.mountPath !== undefined && opts.mountPath !== null &&
500
- typeof opts.mountPath !== "string") {
501
- throw _err("BAD_OPT", "staticServe.create: mountPath must be a string");
502
- }
503
- if (opts.hashedPathPattern !== undefined && opts.hashedPathPattern !== null &&
504
- !(opts.hashedPathPattern instanceof RegExp)) {
505
- throw _err("BAD_OPT", "staticServe.create: hashedPathPattern must be a RegExp");
506
- }
507
- // indexFile === null is the operator's "disable" sentinel; the helper
508
- // returns null/undefined unchanged so we keep that semantic.
509
- validateOpts.optionalNonEmptyString(opts.indexFile,
510
- "staticServe.create: indexFile", StaticServeError, "BAD_OPT");
511
- numericBounds.requireNonNegativeFiniteIntIfPresent(opts.defaultMaxAge,
512
- "staticServe.create: defaultMaxAge", StaticServeError, "BAD_OPT");
513
- validateOpts.optionalPlainObject(opts.contentTypes,
514
- "staticServe.create: contentTypes", StaticServeError, "BAD_OPT");
515
- validateOpts.optionalObjectWithMethod(opts.permissions, "check",
516
- "staticServe.create: permissions", StaticServeError, "BAD_OPT",
517
- "must be a b.permissions instance (check fn)");
518
- validateOpts.optionalObjectWithMethod(opts.cache, "get",
519
- "staticServe.create: cache", StaticServeError, "BAD_OPT",
520
- "must be a b.cache instance (used for cluster-shared bandwidth + concurrency tracking)");
521
- validateOpts.optionalObjectWithMethod(opts.fileType, "detect",
522
- "staticServe.create: fileType", StaticServeError, "BAD_OPT",
523
- "must be a b.fileType instance (magic-byte MIME detection)");
524
- validateOpts.optionalObjectWithMethod(opts.retention, "isServable",
525
- "staticServe.create: retention", StaticServeError, "BAD_OPT",
526
- "must expose isServable(absPath, ctx) → boolean (compliance retention check)");
527
- validateOpts.optionalObjectWithMethod(opts.revokeStore, "isRevoked",
528
- "staticServe.create: revokeStore", StaticServeError, "BAD_OPT",
529
- "must expose isRevoked(key) and revoke(key) for force-revoke support");
530
- validateOpts.optionalNonEmptyStringArray(opts.allowedFileTypes,
531
- "staticServe.create: allowedFileTypes", StaticServeError, "BAD_OPT");
532
619
  if (Array.isArray(opts.allowedFileTypes) && opts.allowedFileTypes.length > 0 &&
533
620
  (!opts.fileType || typeof opts.fileType.detect !== "function")) {
534
621
  throw _err("BAD_OPT",
535
622
  "staticServe.create: allowedFileTypes is set but fileType primitive is not wired " +
536
623
  "(pass fileType: b.fileType so the framework can sniff magic bytes before serving)");
537
624
  }
538
- validateOpts.auditShape(opts.audit, "staticServe.create", StaticServeError);
539
- validateOpts.observabilityShape(opts.observability, "staticServe.create", StaticServeError);
540
- validateOpts.optionalFunction(opts.onServe, "staticServe.create: onServe", StaticServeError);
541
- validateOpts.optionalFunction(opts.onError, "staticServe.create: onError", StaticServeError);
542
- // contentSafety extension-keyed gate map. Default behaviour: when
543
- // undefined, the framework wires b.guardAll.byExtension({ profile:
544
- // "strict" }) automatically so every shipped guard is ON by default.
545
- // Explicit opt-out: contentSafety: null (audited at create() time so
546
- // a security review can reconstruct which deploys disabled the
547
- // default-on protection).
625
+ // contentSafety — extension-keyed gate map; validated in the shape above
626
+ // (plain { ext: gate } object / null opt-out / undefined default-on).
627
+ // When undefined, the framework wires b.guardAll.byExtension({ profile:
628
+ // "strict" }) automatically so every shipped guard is ON by default;
629
+ // contentSafety: null is the explicit opt-out, audited at create() time so
630
+ // a security review can reconstruct which deploys disabled the default-on
631
+ // protection.
548
632
  // Example: contentSafety: { ".csv": b.guardCsv.gate({ profile: "strict" }) }
549
- if (opts.contentSafety !== undefined && opts.contentSafety !== null) {
550
- validateOpts.optionalPlainObject(opts.contentSafety,
551
- "staticServe.create: contentSafety", StaticServeError, "BAD_OPT",
552
- "must be a plain { ext: gate } object, null to opt out, or " +
553
- "undefined for the default-on b.guardAll wiring");
554
- var safetyKeys = Object.keys(opts.contentSafety);
555
- for (var sk = 0; sk < safetyKeys.length; sk++) {
556
- var ext = safetyKeys[sk];
557
- var g = opts.contentSafety[ext];
558
- if (!g || typeof g.check !== "function") {
559
- throw _err("BAD_OPT",
560
- "staticServe.create: contentSafety[" + JSON.stringify(ext) +
561
- "] must be a gate (b.guardCsv.gate / b.guardHtml.gate / etc.)");
562
- }
563
- }
564
- }
565
- validateOpts.optionalBoolean(opts.acceptRanges, "staticServe.create: acceptRanges", StaticServeError);
566
- validateOpts.optionalBoolean(opts.auditSuccess, "staticServe.create: auditSuccess", StaticServeError);
567
- validateOpts.optionalBoolean(opts.auditFailures, "staticServe.create: auditFailures", StaticServeError);
568
- validateOpts.optionalBoolean(opts.safeAttachmentForRiskyMimes,
569
- "staticServe.create: safeAttachmentForRiskyMimes", StaticServeError);
570
633
  // mountType — config-time enum. A typo ("usercontent", "uploads")
571
634
  // would silently fall back to the curated default and serve untrusted
572
635
  // HTML inline, so THROW at boot rather than mis-type the mount.
@@ -576,22 +639,6 @@ function _validateCreateOpts(opts) {
576
639
  "staticServe.create: mountType must be 'curated' (default) or " +
577
640
  "'user-content'; got " + JSON.stringify(opts.mountType));
578
641
  }
579
- validateOpts.optionalBoolean(opts.forceAttachmentForNonText,
580
- "staticServe.create: forceAttachmentForNonText", StaticServeError);
581
- validateOpts.optionalBoolean(opts.safeRenderSvg,
582
- "staticServe.create: safeRenderSvg", StaticServeError);
583
- validateOpts.optionalBoolean(opts.safeRenderPdf,
584
- "staticServe.create: safeRenderPdf", StaticServeError);
585
- numericBounds.requireNonNegativeFiniteIntIfPresent(opts.maxBytesPerActorPerWindowMs,
586
- "staticServe.create: maxBytesPerActorPerWindowMs", StaticServeError, "BAD_OPT");
587
- numericBounds.requireNonNegativeFiniteIntIfPresent(opts.maxBytesAllActorsPerWindowMs,
588
- "staticServe.create: maxBytesAllActorsPerWindowMs", StaticServeError, "BAD_OPT");
589
- numericBounds.requirePositiveFiniteIntIfPresent(opts.bandwidthWindowMs,
590
- "staticServe.create: bandwidthWindowMs", StaticServeError, "BAD_OPT");
591
- numericBounds.requireNonNegativeFiniteIntIfPresent(opts.maxConcurrentDownloadsPerActor,
592
- "staticServe.create: maxConcurrentDownloadsPerActor", StaticServeError, "BAD_OPT");
593
- numericBounds.requirePositiveFiniteIntIfPresent(opts.maxIdleMs,
594
- "staticServe.create: maxIdleMs", StaticServeError, "BAD_OPT");
595
642
  // Quotas require a cache for cluster-shared coordination.
596
643
  if ((opts.maxBytesPerActorPerWindowMs > 0 ||
597
644
  opts.maxBytesAllActorsPerWindowMs > 0 ||
@@ -701,20 +748,11 @@ async function integrity(absPath) {
701
748
 
702
749
  function create(opts) {
703
750
  opts = opts || {};
704
- // The v0.6.x test surface called `validateOpts(opts, [...allowed], label)`
705
- // for the unknown-key check. Preserve that gate in addition to the
706
- // throw-at-config-time validation so tests catch typos.
707
- validateOpts(opts, [
708
- "root", "mountPath", "hashedPathPattern",
709
- "indexFile", "defaultMaxAge", "contentTypes",
710
- "permissions", "cache", "fileType", "retention", "revokeStore",
711
- "allowedFileTypes", "audit", "observability", "onServe", "onError",
712
- "acceptRanges", "auditSuccess", "auditFailures",
713
- "maxBytesPerActorPerWindowMs", "maxBytesAllActorsPerWindowMs",
714
- "bandwidthWindowMs", "maxConcurrentDownloadsPerActor", "maxIdleMs",
715
- "contentSafety", "contentSafetyDisabledReason",
716
- "mountType", "forceAttachmentForNonText", "safeRenderSvg", "safeRenderPdf",
717
- ], "staticServe.create");
751
+ // The exhaustive shape() in _validateCreateOpts is the authoritative
752
+ // unknown-key gate: any opt not declared in its schema (nor listed in
753
+ // its options.allow pass-through) is rejected as an unknown opt, so the
754
+ // typo-catching the v0.6.x `validateOpts(opts, [...allowed])` call gave
755
+ // is now subsumed by the per-field validation below.
718
756
  _validateCreateOpts(opts);
719
757
  var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
720
758
  var root = nodePath.resolve(opts.root);