@blamejs/core 0.8.43 → 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 +92 -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/safe-sql.js CHANGED
@@ -1,53 +1,44 @@
1
1
  "use strict";
2
2
  /**
3
- * SQL safety primitive — identifier validation and parameterized query
4
- * helpers.
5
- *
6
- * The framework's own SQL is parameterized everywhere — values bind
7
- * through node:sqlite or external-db drivers, never string-concatenated.
8
- * That covers value injection. The remaining attack surface is
9
- * IDENTIFIER injection: when a TABLE name or COLUMN name is interpolated
10
- * into a SQL string, a malicious or misconfigured caller can break out
11
- * of the intended structure. This is rare in framework-internal code
12
- * (we control the names) but easy to get wrong if operator-supplied
13
- * config flows in unchecked.
14
- *
15
- * This module provides the thin validator that any code interpolating
16
- * identifiers should call first. It does NOT execute SQL; it just
17
- * validates the inputs that get composed into SQL elsewhere.
18
- *
19
- * Public API:
20
- * safeSql.validateIdentifier(name, opts?) throws on bad shape
21
- * safeSql.quoteIdentifier(name, dialect?) returns "name" / `name`
22
- * safeSql.assertOneOf(name, allowlist) throws unless in list
23
- * safeSql.SafeSqlError error class
24
- *
25
- * Identifier rule (default): `^[A-Za-z_][A-Za-z0-9_]*$` with length
26
- * 1–63 chars (Postgres NAMEDATALEN default; SQLite has no hard
27
- * limit but matching the strictest dialect is the safe bound).
28
- *
29
- * Forbidden in identifiers regardless of regex:
30
- * - SQL reserved words (a small allowlist, not exhaustive operators
31
- * who need reserved-word identifiers should quote them and accept
32
- * the dialect-specific quoting)
33
- * - Leading underscore prefixed `sqlite_` (SQLite-internal)
34
- * - Embedded null byte
35
- *
36
- * Quoting:
37
- * sqlite "name" (double-quote per SQL standard; SQLite
38
- * accepts double quotes for identifiers
39
- * per its quirks settings)
40
- * postgres "name"
41
- * mysql `name` (MySQL's backtick convention)
42
- *
43
- * Allowlist usage (recommended):
44
- * var ALLOWED_TABLES = new Set(["audit_log", "consent_log", …]);
45
- * safeSql.assertOneOf(operatorTableName, ALLOWED_TABLES);
46
- * var sql = "INSERT INTO " + safeSql.quoteIdentifier(operatorTableName) + " …";
47
- *
48
- * The allowlist is the strongest guarantee. Operators with dynamic
49
- * identifier needs (rare) use validateIdentifier alone, accepting
3
+ * @module b.safeSql
4
+ * @nav Validation
5
+ * @title Safe Sql
6
+ *
7
+ * @intro
8
+ * SQL identifier validation + dialect-aware quoting + allowlist
9
+ * gating. Defends against IDENTIFIER injection the residual attack
10
+ * surface left over when a TABLE name or COLUMN name flows from
11
+ * operator-supplied config into a SQL string. Values bind through
12
+ * parameterized queries everywhere in the framework, but parameters
13
+ * can't carry identifiers; that interpolation is what this module
14
+ * guards.
15
+ *
16
+ * Default identifier shape: `^[A-Za-z_][A-Za-z0-9_]*$`, length 1–63
17
+ * (Postgres NAMEDATALEN the strictest of the supported dialects).
18
+ * Reserved words (SELECT / DROP / PRAGMA / ATTACH / …) and the
19
+ * SQLite-internal `sqlite_` prefix are refused unless the caller
20
+ * explicitly opts in. Quoting follows dialect convention: SQLite +
21
+ * Postgres double-quote, MySQL backtick. Multi-segment names
22
+ * (`schema.table`) validate + quote each segment independently so
23
+ * the dotted form `"schema"."table"` resolves correctly instead of
24
+ * collapsing into one literal identifier with a dot in it.
25
+ *
26
+ * Recommended pattern is the closed allowlist:
27
+ *
28
+ * var ALLOWED = new Set(["audit_log", "consent_log"]);
29
+ * b.safeSql.assertOneOf(name, ALLOWED);
30
+ * var sql = "INSERT INTO " + b.safeSql.quoteIdentifier(name) + " ...";
31
+ *
32
+ * The allowlist is the strongest guarantee. Operators with genuinely
33
+ * dynamic identifier needs use `validateIdentifier` alone, accepting
50
34
  * that any string passing the regex is allowed.
35
+ *
36
+ * Validation policy: every primitive throws `SafeSqlError` on bad
37
+ * input — these run at SQL-composition time, well before the query
38
+ * reaches the database. The throw IS the security signal.
39
+ *
40
+ * @card
41
+ * SQL identifier validation + dialect-aware quoting + allowlist gating.
51
42
  */
52
43
 
53
44
  // Reserved-word block list — the most dangerous to accept as a bare
@@ -73,6 +64,30 @@ var MAX_IDENTIFIER_LENGTH = 63;
73
64
 
74
65
  var { FrameworkError } = require("./framework-error");
75
66
 
67
+ /**
68
+ * @primitive b.safeSql.SafeSqlError
69
+ * @signature b.safeSql.SafeSqlError
70
+ * @since 0.1.0
71
+ * @status stable
72
+ * @related b.safeSql.validateIdentifier, b.safeSql.quoteIdentifier, b.safeSql.assertOneOf
73
+ *
74
+ * Error class thrown by every `b.safeSql` primitive on bad input.
75
+ * Extends `FrameworkError`. Carries a stable `.code` —
76
+ * `sql/bad-type` / `sql/empty` / `sql/too-long` / `sql/null-byte` /
77
+ * `sql/bad-shape` / `sql/reserved-word` / `sql/internal-prefix` /
78
+ * `sql/not-allowed` / `sql/bad-allowlist`. Operators catch these at
79
+ * SQL-composition boundaries; the throw fires before the query
80
+ * reaches the database driver.
81
+ *
82
+ * @example
83
+ * var b = require("blamejs");
84
+ * try {
85
+ * b.safeSql.validateIdentifier("drop");
86
+ * } catch (e) {
87
+ * e instanceof b.safeSql.SafeSqlError; // → true
88
+ * e.code; // → "sql/reserved-word"
89
+ * }
90
+ */
76
91
  class SafeSqlError extends FrameworkError {
77
92
  constructor(message, code) {
78
93
  super(message);
@@ -82,6 +97,42 @@ class SafeSqlError extends FrameworkError {
82
97
  }
83
98
  }
84
99
 
100
+ /**
101
+ * @primitive b.safeSql.validateIdentifier
102
+ * @signature b.safeSql.validateIdentifier(name, opts?)
103
+ * @since 0.1.0
104
+ * @status stable
105
+ * @related b.safeSql.quoteIdentifier, b.safeSql.assertOneOf, b.safeSql.SafeSqlError
106
+ *
107
+ * Throw-on-bad-shape validator for SQL table / column / index names.
108
+ * Enforces the default identifier regex (`[A-Za-z_][A-Za-z0-9_]*`),
109
+ * a 63-character cap (Postgres NAMEDATALEN — strictest supported
110
+ * dialect), no embedded null byte, no SQL reserved word, no
111
+ * SQLite-internal `sqlite_` prefix. Returns `name` on success so the
112
+ * call composes inside a SQL fragment without an extra temporary.
113
+ *
114
+ * @opts
115
+ * pattern: RegExp, // override the default shape regex
116
+ * allowReserved: boolean, // default false; permit reserved words like "select"/"drop"
117
+ * allowSqliteInternal: boolean, // default false; permit "sqlite_..." identifiers
118
+ *
119
+ * @example
120
+ * var b = require("blamejs");
121
+ * b.safeSql.validateIdentifier("audit_log");
122
+ * // → "audit_log"
123
+ *
124
+ * try { b.safeSql.validateIdentifier("drop"); }
125
+ * catch (e) { e.code; }
126
+ * // → "sql/reserved-word"
127
+ *
128
+ * try { b.safeSql.validateIdentifier("evil; DROP"); }
129
+ * catch (e) { e.code; }
130
+ * // → "sql/bad-shape"
131
+ *
132
+ * // Operator opts in to a custom shape (still ASCII-only, still capped).
133
+ * b.safeSql.validateIdentifier("col-1", { pattern: /^[A-Za-z][A-Za-z0-9_-]*$/ });
134
+ * // → "col-1"
135
+ */
85
136
  function validateIdentifier(name, opts) {
86
137
  opts = opts || {};
87
138
  if (typeof name !== "string") {
@@ -122,6 +173,29 @@ function validateIdentifier(name, opts) {
122
173
  return name;
123
174
  }
124
175
 
176
+ /**
177
+ * @primitive b.safeSql.quoteIdentifier
178
+ * @signature b.safeSql.quoteIdentifier(name, dialect?)
179
+ * @since 0.1.0
180
+ * @status stable
181
+ * @related b.safeSql.validateIdentifier, b.safeSql.quoteQualified
182
+ *
183
+ * Validate `name` then wrap it in dialect-appropriate quotes —
184
+ * double-quote for SQLite + Postgres (per SQL standard), backtick for
185
+ * MySQL. Default dialect is `"sqlite"`. Throws `SafeSqlError` if the
186
+ * identifier fails `validateIdentifier`.
187
+ *
188
+ * @example
189
+ * var b = require("blamejs");
190
+ * b.safeSql.quoteIdentifier("users");
191
+ * // → '"users"'
192
+ *
193
+ * b.safeSql.quoteIdentifier("Order", "postgres");
194
+ * // → '"Order"'
195
+ *
196
+ * b.safeSql.quoteIdentifier("users", "mysql");
197
+ * // → "`users`"
198
+ */
125
199
  function quoteIdentifier(name, dialect) {
126
200
  validateIdentifier(name);
127
201
  dialect = (dialect || "sqlite").toLowerCase();
@@ -130,18 +204,35 @@ function quoteIdentifier(name, dialect) {
130
204
  return '"' + name + '"';
131
205
  }
132
206
 
133
- // Quote a multi-part qualified name like `schema.table` or
134
- // `database.schema.table`. Each segment is validated + quoted
135
- // independently so the dotted form `"schema"."table"` resolves
136
- // correctly. Replaces the wrong shape `"schema.table"` (one literal
137
- // identifier with a dot in it). Accepts an array of parts OR a string
138
- // with `.` as the separator.
139
- //
140
- // quoteQualified(["public", "users"]) → '"public"."users"'
141
- // quoteQualified("public.users") → '"public"."users"'
142
- // quoteQualified(["public", "Order"], "postgres")
143
- // → '"public"."Order"' (case preserved)
144
- // quoteQualified("dbA.public.users") → '"dbA"."public"."users"'
207
+ /**
208
+ * @primitive b.safeSql.quoteQualified
209
+ * @signature b.safeSql.quoteQualified(parts, dialect?)
210
+ * @since 0.1.0
211
+ * @status stable
212
+ * @related b.safeSql.quoteIdentifier, b.safeSql.validateIdentifier
213
+ *
214
+ * Quote a multi-part qualified name like `schema.table` or
215
+ * `database.schema.table`. Each segment is validated and quoted
216
+ * independently so the resulting SQL is `"schema"."table"` (three
217
+ * lookups against the catalog) instead of `"schema.table"` (one
218
+ * literal identifier with a dot in its name — a different and
219
+ * usually-nonexistent object). Accepts an array of parts OR a
220
+ * dot-separated string.
221
+ *
222
+ * @example
223
+ * var b = require("blamejs");
224
+ * b.safeSql.quoteQualified(["public", "users"]);
225
+ * // → '"public"."users"'
226
+ *
227
+ * b.safeSql.quoteQualified("public.users");
228
+ * // → '"public"."users"'
229
+ *
230
+ * b.safeSql.quoteQualified("dbA.public.users");
231
+ * // → '"dbA"."public"."users"'
232
+ *
233
+ * b.safeSql.quoteQualified(["app", "orders"], "mysql");
234
+ * // → "`app`.`orders`"
235
+ */
145
236
  function quoteQualified(parts, dialect) {
146
237
  var arr;
147
238
  if (typeof parts === "string") {
@@ -167,6 +258,36 @@ function quoteQualified(parts, dialect) {
167
258
  return quoted.join(".");
168
259
  }
169
260
 
261
+ /**
262
+ * @primitive b.safeSql.assertOneOf
263
+ * @signature b.safeSql.assertOneOf(name, allowlist)
264
+ * @since 0.1.0
265
+ * @status stable
266
+ * @related b.safeSql.validateIdentifier, b.safeSql.quoteIdentifier
267
+ *
268
+ * Closed-allowlist gate — the strongest guarantee against identifier
269
+ * injection. `allowlist` is a `Set` or `Array` of permitted names;
270
+ * anything outside throws `SafeSqlError` with `.code = "sql/not-allowed"`.
271
+ * Returns `name` on success so the call composes inline with
272
+ * `quoteIdentifier`. Use this whenever the operator-supplied identifier
273
+ * is drawn from a known finite set (which is most cases — table names
274
+ * are config, not user input).
275
+ *
276
+ * @example
277
+ * var b = require("blamejs");
278
+ * var ALLOWED = new Set(["audit_log", "consent_log", "session"]);
279
+ *
280
+ * b.safeSql.assertOneOf("audit_log", ALLOWED);
281
+ * // → "audit_log"
282
+ *
283
+ * try { b.safeSql.assertOneOf("users", ALLOWED); }
284
+ * catch (e) { e.code; }
285
+ * // → "sql/not-allowed"
286
+ *
287
+ * // Array form works too.
288
+ * b.safeSql.assertOneOf("audit_log", ["audit_log", "consent_log"]);
289
+ * // → "audit_log"
290
+ */
170
291
  function assertOneOf(name, allowlist) {
171
292
  if (typeof name !== "string") {
172
293
  throw new SafeSqlError("name must be a string", "sql/bad-type");
@@ -189,6 +310,47 @@ function assertOneOf(name, allowlist) {
189
310
  return name;
190
311
  }
191
312
 
313
+ /**
314
+ * @primitive b.safeSql.DEFAULT_IDENTIFIER_RE
315
+ * @signature b.safeSql.DEFAULT_IDENTIFIER_RE
316
+ * @since 0.1.0
317
+ * @status stable
318
+ * @related b.safeSql.validateIdentifier, b.safeSql.MAX_IDENTIFIER_LENGTH
319
+ *
320
+ * The default identifier shape regex — `/^[A-Za-z_][A-Za-z0-9_]*$/`.
321
+ * Exposed so operator code that needs a slightly-wider or
322
+ * slightly-narrower shape can compose against it instead of
323
+ * re-deriving the pattern. ASCII-only by design — Unicode
324
+ * identifiers are dialect-specific and surface in mismatched-encoding
325
+ * footguns we don't want to default into.
326
+ *
327
+ * @example
328
+ * var b = require("blamejs");
329
+ * b.safeSql.DEFAULT_IDENTIFIER_RE.test("audit_log");
330
+ * // → true
331
+ *
332
+ * b.safeSql.DEFAULT_IDENTIFIER_RE.test("1starts_with_digit");
333
+ * // → false
334
+ */
335
+
336
+ /**
337
+ * @primitive b.safeSql.MAX_IDENTIFIER_LENGTH
338
+ * @signature b.safeSql.MAX_IDENTIFIER_LENGTH
339
+ * @since 0.1.0
340
+ * @status stable
341
+ * @related b.safeSql.validateIdentifier, b.safeSql.DEFAULT_IDENTIFIER_RE
342
+ *
343
+ * Hard cap on identifier length — 63 characters. Matches Postgres'
344
+ * NAMEDATALEN default; SQLite and MySQL accept longer names but
345
+ * defaulting to the strictest dialect keeps cross-dialect SQL
346
+ * portable.
347
+ *
348
+ * @example
349
+ * var b = require("blamejs");
350
+ * b.safeSql.MAX_IDENTIFIER_LENGTH;
351
+ * // → 63
352
+ */
353
+
192
354
  module.exports = {
193
355
  validateIdentifier: validateIdentifier,
194
356
  quoteIdentifier: quoteIdentifier,