nexus-ai-pro 2.2.0 → 2.4.0

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 (298) hide show
  1. package/API_STABILITY.md +225 -0
  2. package/CHANGELOG.md +325 -1
  3. package/README.md +20 -6
  4. package/SECURITY.md +140 -0
  5. package/dist/adapter-kit/contracts.d.ts +87 -0
  6. package/dist/adapter-kit/contracts.js +237 -0
  7. package/dist/adapter-kit/errors.d.ts +39 -0
  8. package/dist/adapter-kit/errors.js +52 -0
  9. package/dist/adapter-kit/index.d.ts +144 -0
  10. package/dist/adapter-kit/index.js +178 -0
  11. package/dist/adapter-kit/semver.d.ts +12 -0
  12. package/dist/adapter-kit/semver.js +55 -0
  13. package/dist/agent/capabilities.d.ts +39 -0
  14. package/dist/agent/capabilities.js +36 -0
  15. package/dist/agent/create-agent.d.ts +61 -11
  16. package/dist/agent/create-agent.js +136 -22
  17. package/dist/agent/index.d.ts +3 -2
  18. package/dist/agent/index.js +1 -1
  19. package/dist/agent/middleware/approval.d.ts +26 -0
  20. package/dist/agent/middleware/approval.js +23 -0
  21. package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  22. package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
  23. package/dist/agent/middleware/context-editor.d.ts +52 -0
  24. package/dist/agent/middleware/context-editor.js +66 -0
  25. package/dist/agent/middleware/filesystem.d.ts +44 -0
  26. package/dist/agent/middleware/filesystem.js +83 -0
  27. package/dist/agent/middleware/index.d.ts +19 -0
  28. package/dist/agent/middleware/index.js +9 -0
  29. package/dist/agent/middleware/limits.d.ts +42 -0
  30. package/dist/agent/middleware/limits.js +52 -0
  31. package/dist/agent/middleware/models.d.ts +48 -0
  32. package/dist/agent/middleware/models.js +44 -0
  33. package/dist/agent/middleware/pii.d.ts +67 -0
  34. package/dist/agent/middleware/pii.js +142 -0
  35. package/dist/agent/middleware/retry.d.ts +66 -0
  36. package/dist/agent/middleware/retry.js +59 -0
  37. package/dist/agent/middleware/shared.d.ts +41 -0
  38. package/dist/agent/middleware/shared.js +73 -0
  39. package/dist/agent/middleware/tool-selector.d.ts +64 -0
  40. package/dist/agent/middleware/tool-selector.js +106 -0
  41. package/dist/agent/permissions.d.ts +113 -0
  42. package/dist/agent/permissions.js +180 -0
  43. package/dist/agent/sandbox.d.ts +151 -0
  44. package/dist/agent/sandbox.js +257 -0
  45. package/dist/agent/tool.d.ts +8 -3
  46. package/dist/agent/tool.js +14 -3
  47. package/dist/core/secure-stream.js +4 -3
  48. package/dist/deep-agent/index.d.ts +109 -0
  49. package/dist/deep-agent/index.js +216 -0
  50. package/dist/embeddings/adapters.js +2 -1
  51. package/dist/evals/judge.js +2 -5
  52. package/dist/evaluate/cache.d.ts +0 -14
  53. package/dist/evaluate/cache.js +0 -30
  54. package/dist/evaluate/file-cache.d.ts +15 -0
  55. package/dist/evaluate/file-cache.js +30 -0
  56. package/dist/evaluate/index.d.ts +2 -1
  57. package/dist/evaluate/index.js +2 -1
  58. package/dist/evaluate/review.js +2 -2
  59. package/dist/evaluate/run.js +2 -2
  60. package/dist/evaluate/version.js +3 -3
  61. package/dist/graph/functional.d.ts +9 -0
  62. package/dist/graph/functional.js +20 -3
  63. package/dist/graph/graph.js +27 -6
  64. package/dist/graph/lint.d.ts +4 -1
  65. package/dist/graph/lint.js +35 -0
  66. package/dist/graph/node-cache.js +3 -3
  67. package/dist/hallucination/rag.d.ts +4 -1
  68. package/dist/hallucination/rag.js +6 -1
  69. package/dist/images/asset-stores.js +2 -3
  70. package/dist/images/asset-support.js +2 -2
  71. package/dist/images/assets.js +1 -2
  72. package/dist/images/evals.js +4 -4
  73. package/dist/insights/proposals.js +1 -2
  74. package/dist/insights/regressions.d.ts +26 -2
  75. package/dist/insights/regressions.js +129 -3
  76. package/dist/loaders/git.js +8 -1
  77. package/dist/loaders/web.js +7 -1
  78. package/dist/mcp/client.d.ts +14 -0
  79. package/dist/mcp/client.js +59 -1
  80. package/dist/mcp/registry.d.ts +6 -0
  81. package/dist/mcp/registry.js +7 -0
  82. package/dist/postgres/fulltext.d.ts +67 -0
  83. package/dist/postgres/fulltext.js +91 -0
  84. package/dist/prompts/file.js +28 -11
  85. package/dist/protocols/a2a.d.ts +235 -0
  86. package/dist/protocols/a2a.js +386 -0
  87. package/dist/protocols/acp.d.ts +87 -0
  88. package/dist/protocols/acp.js +322 -0
  89. package/dist/protocols/ag-ui.d.ts +120 -0
  90. package/dist/protocols/ag-ui.js +180 -0
  91. package/dist/protocols/shared.d.ts +80 -0
  92. package/dist/protocols/shared.js +49 -0
  93. package/dist/providers/anthropic.js +1 -0
  94. package/dist/providers/cohere.js +2 -2
  95. package/dist/providers/google.js +2 -11
  96. package/dist/providers/openai.js +1 -0
  97. package/dist/rag/elasticsearch.d.ts +71 -0
  98. package/dist/rag/elasticsearch.js +130 -0
  99. package/dist/rag/file-ingestion.js +15 -9
  100. package/dist/rag/pipeline.d.ts +140 -0
  101. package/dist/rag/pipeline.js +187 -0
  102. package/dist/rag/rerankers.d.ts +92 -0
  103. package/dist/rag/rerankers.js +97 -0
  104. package/dist/rag/retrievers.d.ts +12 -1
  105. package/dist/runtime/detect.d.ts +17 -0
  106. package/dist/runtime/detect.js +16 -0
  107. package/dist/runtime/index.d.ts +19 -0
  108. package/dist/runtime/index.js +7 -0
  109. package/dist/security/injection-detector.d.ts +2 -0
  110. package/dist/security/injection-detector.js +7 -1
  111. package/dist/security/input-guard.js +5 -2
  112. package/dist/security/output-guard.js +8 -4
  113. package/dist/security/pii-detector.js +22 -6
  114. package/dist/security/prompt-hardening.js +6 -2
  115. package/dist/security/semantic-injection-classifier.d.ts +2 -1
  116. package/dist/security/semantic-injection-classifier.js +31 -35
  117. package/dist/security/upload-scanner.d.ts +8 -0
  118. package/dist/security/upload-scanner.js +51 -9
  119. package/dist/server/assistant.d.ts +5 -1
  120. package/dist/server/assistant.js +4 -2
  121. package/dist/server/auth.d.ts +250 -0
  122. package/dist/server/auth.js +392 -0
  123. package/dist/server/deployments.d.ts +63 -1
  124. package/dist/server/deployments.js +75 -2
  125. package/dist/server/runs.d.ts +9 -0
  126. package/dist/server/runs.js +20 -0
  127. package/dist/server/server.js +5 -0
  128. package/dist/store/memory.d.ts +10 -0
  129. package/dist/store/memory.js +10 -0
  130. package/dist/testing/record.js +2 -2
  131. package/dist/tracing/stores.js +2 -1
  132. package/dist/tracing/tracer.js +2 -2
  133. package/dist/types/config.d.ts +20 -0
  134. package/dist/types/graph.d.ts +38 -0
  135. package/dist/types/messages.d.ts +36 -2
  136. package/dist/types/principal.d.ts +21 -0
  137. package/dist/types/principal.js +1 -0
  138. package/dist/types/server.d.ts +8 -9
  139. package/dist/utils/base64.d.ts +4 -0
  140. package/dist/utils/base64.js +21 -0
  141. package/dist/utils/checksums.d.ts +22 -0
  142. package/dist/utils/checksums.js +39 -0
  143. package/dist/utils/ids.d.ts +8 -0
  144. package/dist/utils/ids.js +26 -2
  145. package/dist/utils/sha256.d.ts +22 -0
  146. package/dist/utils/sha256.js +120 -0
  147. package/dist/utils/tenant.d.ts +11 -0
  148. package/dist/utils/tenant.js +14 -0
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
  152. package/dist-cjs/adapter-kit/contracts.js +243 -0
  153. package/dist-cjs/adapter-kit/errors.d.ts +39 -0
  154. package/dist-cjs/adapter-kit/errors.js +57 -0
  155. package/dist-cjs/adapter-kit/index.d.ts +144 -0
  156. package/dist-cjs/adapter-kit/index.js +194 -0
  157. package/dist-cjs/adapter-kit/semver.d.ts +12 -0
  158. package/dist-cjs/adapter-kit/semver.js +59 -0
  159. package/dist-cjs/agent/capabilities.d.ts +39 -0
  160. package/dist-cjs/agent/capabilities.js +41 -0
  161. package/dist-cjs/agent/create-agent.d.ts +61 -11
  162. package/dist-cjs/agent/create-agent.js +136 -22
  163. package/dist-cjs/agent/index.d.ts +3 -2
  164. package/dist-cjs/agent/index.js +4 -4
  165. package/dist-cjs/agent/middleware/approval.d.ts +26 -0
  166. package/dist-cjs/agent/middleware/approval.js +26 -0
  167. package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  168. package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
  169. package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
  170. package/dist-cjs/agent/middleware/context-editor.js +70 -0
  171. package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
  172. package/dist-cjs/agent/middleware/filesystem.js +86 -0
  173. package/dist-cjs/agent/middleware/index.d.ts +19 -0
  174. package/dist-cjs/agent/middleware/index.js +28 -0
  175. package/dist-cjs/agent/middleware/limits.d.ts +42 -0
  176. package/dist-cjs/agent/middleware/limits.js +57 -0
  177. package/dist-cjs/agent/middleware/models.d.ts +48 -0
  178. package/dist-cjs/agent/middleware/models.js +48 -0
  179. package/dist-cjs/agent/middleware/pii.d.ts +67 -0
  180. package/dist-cjs/agent/middleware/pii.js +147 -0
  181. package/dist-cjs/agent/middleware/retry.d.ts +66 -0
  182. package/dist-cjs/agent/middleware/retry.js +63 -0
  183. package/dist-cjs/agent/middleware/shared.d.ts +41 -0
  184. package/dist-cjs/agent/middleware/shared.js +83 -0
  185. package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
  186. package/dist-cjs/agent/middleware/tool-selector.js +109 -0
  187. package/dist-cjs/agent/permissions.d.ts +113 -0
  188. package/dist-cjs/agent/permissions.js +189 -0
  189. package/dist-cjs/agent/sandbox.d.ts +151 -0
  190. package/dist-cjs/agent/sandbox.js +297 -0
  191. package/dist-cjs/agent/tool.d.ts +8 -3
  192. package/dist-cjs/agent/tool.js +14 -3
  193. package/dist-cjs/core/secure-stream.js +4 -3
  194. package/dist-cjs/deep-agent/index.d.ts +109 -0
  195. package/dist-cjs/deep-agent/index.js +219 -0
  196. package/dist-cjs/embeddings/adapters.js +2 -1
  197. package/dist-cjs/evals/judge.js +2 -5
  198. package/dist-cjs/evaluate/cache.d.ts +0 -14
  199. package/dist-cjs/evaluate/cache.js +1 -65
  200. package/dist-cjs/evaluate/file-cache.d.ts +15 -0
  201. package/dist-cjs/evaluate/file-cache.js +67 -0
  202. package/dist-cjs/evaluate/index.d.ts +2 -1
  203. package/dist-cjs/evaluate/index.js +3 -2
  204. package/dist-cjs/evaluate/review.js +2 -2
  205. package/dist-cjs/evaluate/run.js +2 -2
  206. package/dist-cjs/evaluate/version.js +3 -3
  207. package/dist-cjs/graph/functional.d.ts +9 -0
  208. package/dist-cjs/graph/functional.js +20 -3
  209. package/dist-cjs/graph/graph.js +27 -6
  210. package/dist-cjs/graph/lint.d.ts +4 -1
  211. package/dist-cjs/graph/lint.js +35 -0
  212. package/dist-cjs/graph/node-cache.js +3 -3
  213. package/dist-cjs/hallucination/rag.d.ts +4 -1
  214. package/dist-cjs/hallucination/rag.js +6 -1
  215. package/dist-cjs/images/asset-stores.js +2 -3
  216. package/dist-cjs/images/asset-support.js +2 -2
  217. package/dist-cjs/images/assets.js +1 -2
  218. package/dist-cjs/images/evals.js +4 -4
  219. package/dist-cjs/insights/proposals.js +1 -2
  220. package/dist-cjs/insights/regressions.d.ts +26 -2
  221. package/dist-cjs/insights/regressions.js +129 -3
  222. package/dist-cjs/loaders/git.js +8 -1
  223. package/dist-cjs/loaders/web.js +7 -1
  224. package/dist-cjs/mcp/client.d.ts +14 -0
  225. package/dist-cjs/mcp/client.js +60 -1
  226. package/dist-cjs/mcp/registry.d.ts +6 -0
  227. package/dist-cjs/mcp/registry.js +7 -0
  228. package/dist-cjs/postgres/fulltext.d.ts +67 -0
  229. package/dist-cjs/postgres/fulltext.js +129 -0
  230. package/dist-cjs/prompts/file.js +28 -11
  231. package/dist-cjs/protocols/a2a.d.ts +235 -0
  232. package/dist-cjs/protocols/a2a.js +393 -0
  233. package/dist-cjs/protocols/acp.d.ts +87 -0
  234. package/dist-cjs/protocols/acp.js +326 -0
  235. package/dist-cjs/protocols/ag-ui.d.ts +120 -0
  236. package/dist-cjs/protocols/ag-ui.js +186 -0
  237. package/dist-cjs/protocols/shared.d.ts +80 -0
  238. package/dist-cjs/protocols/shared.js +59 -0
  239. package/dist-cjs/providers/anthropic.js +1 -0
  240. package/dist-cjs/providers/cohere.js +2 -2
  241. package/dist-cjs/providers/google.js +3 -12
  242. package/dist-cjs/providers/openai.js +1 -0
  243. package/dist-cjs/rag/elasticsearch.d.ts +71 -0
  244. package/dist-cjs/rag/elasticsearch.js +135 -0
  245. package/dist-cjs/rag/file-ingestion.js +14 -8
  246. package/dist-cjs/rag/pipeline.d.ts +140 -0
  247. package/dist-cjs/rag/pipeline.js +191 -0
  248. package/dist-cjs/rag/rerankers.d.ts +92 -0
  249. package/dist-cjs/rag/rerankers.js +107 -0
  250. package/dist-cjs/rag/retrievers.d.ts +12 -1
  251. package/dist-cjs/runtime/detect.d.ts +17 -0
  252. package/dist-cjs/runtime/detect.js +19 -0
  253. package/dist-cjs/runtime/index.d.ts +19 -0
  254. package/dist-cjs/runtime/index.js +31 -0
  255. package/dist-cjs/security/injection-detector.d.ts +2 -0
  256. package/dist-cjs/security/injection-detector.js +7 -1
  257. package/dist-cjs/security/input-guard.js +5 -2
  258. package/dist-cjs/security/output-guard.js +8 -4
  259. package/dist-cjs/security/pii-detector.js +22 -6
  260. package/dist-cjs/security/prompt-hardening.js +6 -2
  261. package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
  262. package/dist-cjs/security/semantic-injection-classifier.js +31 -35
  263. package/dist-cjs/security/upload-scanner.d.ts +8 -0
  264. package/dist-cjs/security/upload-scanner.js +53 -9
  265. package/dist-cjs/server/assistant.d.ts +5 -1
  266. package/dist-cjs/server/assistant.js +4 -2
  267. package/dist-cjs/server/auth.d.ts +250 -0
  268. package/dist-cjs/server/auth.js +402 -0
  269. package/dist-cjs/server/deployments.d.ts +63 -1
  270. package/dist-cjs/server/deployments.js +75 -2
  271. package/dist-cjs/server/runs.d.ts +9 -0
  272. package/dist-cjs/server/runs.js +20 -0
  273. package/dist-cjs/server/server.js +5 -0
  274. package/dist-cjs/store/memory.d.ts +10 -0
  275. package/dist-cjs/store/memory.js +10 -0
  276. package/dist-cjs/testing/record.js +2 -2
  277. package/dist-cjs/tracing/stores.js +2 -1
  278. package/dist-cjs/tracing/tracer.js +2 -2
  279. package/dist-cjs/types/config.d.ts +20 -0
  280. package/dist-cjs/types/graph.d.ts +38 -0
  281. package/dist-cjs/types/messages.d.ts +36 -2
  282. package/dist-cjs/types/principal.d.ts +21 -0
  283. package/dist-cjs/types/principal.js +2 -0
  284. package/dist-cjs/types/server.d.ts +8 -9
  285. package/dist-cjs/utils/base64.d.ts +4 -0
  286. package/dist-cjs/utils/base64.js +25 -0
  287. package/dist-cjs/utils/checksums.d.ts +22 -0
  288. package/dist-cjs/utils/checksums.js +46 -0
  289. package/dist-cjs/utils/ids.d.ts +8 -0
  290. package/dist-cjs/utils/ids.js +28 -2
  291. package/dist-cjs/utils/sha256.d.ts +22 -0
  292. package/dist-cjs/utils/sha256.js +125 -0
  293. package/dist-cjs/utils/tenant.d.ts +11 -0
  294. package/dist-cjs/utils/tenant.js +15 -0
  295. package/dist-cjs/version.d.ts +1 -1
  296. package/dist-cjs/version.js +1 -1
  297. package/llms.txt +7 -3
  298. package/package.json +193 -6
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Checks that tell a real identifier from a number that only looks like one. Every detector of card
3
+ * numbers and account numbers uses them, so an order id or a millisecond timestamp is never masked as
4
+ * a card.
5
+ */
6
+ /** Whether a card number passes the Luhn check, with 13 to 19 digits; spaces and dashes are ignored. */
7
+ export declare function luhn(value: string): boolean;
8
+ /** Whether an IBAN passes its mod-97 check, with 15 to 34 characters; spaces are ignored. */
9
+ export declare function ibanChecksum(value: string): boolean;
10
+ /** Whether a phone-like match has as many digits as a phone number can: 10 to 15. */
11
+ export declare function phoneDigits(value: string): boolean;
12
+ /**
13
+ * Whether a match reads as a phone number: 10 to 15 digits, written with a leading `+` or with
14
+ * separators. A bare run of digits is an id or a timestamp far more often than a phone number.
15
+ */
16
+ export declare function phoneNumber(value: string): boolean;
17
+ /**
18
+ * Text as a detector should read it: Unicode compatibility forms folded (full-width letters become
19
+ * ASCII), and the invisible characters an attacker uses to split a phrase removed, so
20
+ * `ignore` and `ig​nore` read as `ignore`.
21
+ */
22
+ export declare function normalizeForDetection(text: string): string;
@@ -0,0 +1,39 @@
1
+ export function luhn(value) {
2
+ const digits = value.replace(/[\s-]/g, '');
3
+ if (!/^\d{13,19}$/.test(digits))
4
+ return false;
5
+ let sum = 0;
6
+ for (let index = 0; index < digits.length; index += 1) {
7
+ let digit = Number(digits[digits.length - 1 - index]);
8
+ if (index % 2 === 1) {
9
+ digit *= 2;
10
+ if (digit > 9)
11
+ digit -= 9;
12
+ }
13
+ sum += digit;
14
+ }
15
+ return sum % 10 === 0;
16
+ }
17
+ export function ibanChecksum(value) {
18
+ const iban = value.replace(/ /g, '').toUpperCase();
19
+ if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(iban))
20
+ return false;
21
+ const rearranged = `${iban.slice(4)}${iban.slice(0, 4)}`;
22
+ let remainder = 0;
23
+ for (const char of rearranged) {
24
+ const code = /\d/.test(char) ? char : String(char.charCodeAt(0) - 55);
25
+ for (const digit of code)
26
+ remainder = (remainder * 10 + Number(digit)) % 97;
27
+ }
28
+ return remainder === 1;
29
+ }
30
+ export function phoneDigits(value) {
31
+ const digits = value.replace(/\D/g, '').length;
32
+ return digits >= 10 && digits <= 15;
33
+ }
34
+ export function phoneNumber(value) {
35
+ return phoneDigits(value) && (/^\s*\+/.test(value) || /\d[\s.()-]+\d/.test(value));
36
+ }
37
+ export function normalizeForDetection(text) {
38
+ return text.normalize('NFKC').replace(/[­᠎​-‏‪-‮⁠-⁤]/g, '');
39
+ }
@@ -1 +1,9 @@
1
+ /**
2
+ * Ids and text measures from Web Crypto and plain JavaScript, so the modules that use them run on
3
+ * every runtime: Node.js, Deno, Bun, edge runtimes, and browsers.
4
+ */
5
+ /** Random bytes as lowercase hex, two characters per byte. */
6
+ export declare function randomHex(bytes: number): string;
1
7
  export declare function generateRequestId(): string;
8
+ /** The bytes UTF-8 takes for a string, as `Buffer.byteLength()` counts them, without encoding it. */
9
+ export declare function utf8Length(text: string): number;
package/dist/utils/ids.js CHANGED
@@ -1,4 +1,28 @@
1
- import { randomBytes } from 'node:crypto';
1
+ export function randomHex(bytes) {
2
+ const values = globalThis.crypto.getRandomValues(new Uint8Array(bytes));
3
+ let hex = '';
4
+ for (const value of values)
5
+ hex += HEX[value];
6
+ return hex;
7
+ }
2
8
  export function generateRequestId() {
3
- return `req_${randomBytes(12).toString('hex')}`;
9
+ return `req_${randomHex(12)}`;
10
+ }
11
+ export function utf8Length(text) {
12
+ let length = 0;
13
+ for (let index = 0; index < text.length; index += 1) {
14
+ const code = text.charCodeAt(index);
15
+ if (code < 0x80)
16
+ length += 1;
17
+ else if (code < 0x800)
18
+ length += 2;
19
+ else if (code >= 0xd800 && code <= 0xdbff && (text.charCodeAt(index + 1) & 0xfc00) === 0xdc00) {
20
+ length += 4;
21
+ index += 1;
22
+ }
23
+ else
24
+ length += 3;
25
+ }
26
+ return length;
4
27
  }
28
+ const HEX = Array.from({ length: 256 }, (_, value) => value.toString(16).padStart(2, '0'));
@@ -0,0 +1,22 @@
1
+ /**
2
+ * SHA-256 that can be fed in pieces, as `createHash('sha256')` is, with the same digest. Strings are
3
+ * hashed as UTF-8. Synchronous, unlike Web Crypto's `digest()`, so a cache key can be computed where
4
+ * it is needed.
5
+ */
6
+ export declare class Sha256 {
7
+ private readonly state;
8
+ private readonly block;
9
+ private readonly words;
10
+ private filled;
11
+ private total;
12
+ /** Adds data: a string, as UTF-8, or bytes. */
13
+ update(data: string | Uint8Array): this;
14
+ /** The digest as lowercase hex. The hash takes no more data afterwards. */
15
+ digest(): string;
16
+ private compress;
17
+ }
18
+ /**
19
+ * The SHA-256 digest of a string, as UTF-8, or of bytes, as lowercase hex. Native where the runtime
20
+ * has it, which is ten times faster on large inputs, and the same digest everywhere else.
21
+ */
22
+ export declare function sha256Hex(data: string | Uint8Array): string;
@@ -0,0 +1,120 @@
1
+ const K = new Uint32Array([
2
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
3
+ 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
4
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
5
+ 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
6
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
7
+ 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
8
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
9
+ 0xc67178f2,
10
+ ]);
11
+ const encoder = new TextEncoder();
12
+ export class Sha256 {
13
+ state = new Uint32Array([
14
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
15
+ ]);
16
+ block = new Uint8Array(64);
17
+ words = new Uint32Array(64);
18
+ filled = 0;
19
+ total = 0;
20
+ update(data) {
21
+ const bytes = typeof data === 'string' ? encoder.encode(data) : data;
22
+ this.total += bytes.length;
23
+ let offset = 0;
24
+ if (this.filled > 0) {
25
+ const take = Math.min(64 - this.filled, bytes.length);
26
+ this.block.set(bytes.subarray(0, take), this.filled);
27
+ this.filled += take;
28
+ offset = take;
29
+ if (this.filled < 64)
30
+ return this;
31
+ this.compress(this.block, 0);
32
+ this.filled = 0;
33
+ }
34
+ for (; offset + 64 <= bytes.length; offset += 64)
35
+ this.compress(bytes, offset);
36
+ if (offset < bytes.length) {
37
+ this.block.set(bytes.subarray(offset), 0);
38
+ this.filled = bytes.length - offset;
39
+ }
40
+ return this;
41
+ }
42
+ digest() {
43
+ const bits = this.total * 8;
44
+ const padding = new Uint8Array(this.filled < 56 ? 64 - this.filled : 128 - this.filled);
45
+ padding[0] = 0x80;
46
+ const view = new DataView(padding.buffer);
47
+ view.setUint32(padding.length - 8, Math.floor(bits / 0x100000000));
48
+ view.setUint32(padding.length - 4, bits >>> 0);
49
+ const total = this.total;
50
+ this.update(padding);
51
+ this.total = total;
52
+ let hex = '';
53
+ for (const word of this.state)
54
+ hex += word.toString(16).padStart(8, '0');
55
+ return hex;
56
+ }
57
+ compress(bytes, offset) {
58
+ const w = this.words;
59
+ for (let index = 0; index < 16; index += 1) {
60
+ const at = offset + index * 4;
61
+ w[index] =
62
+ (bytes[at] << 24) |
63
+ (bytes[at + 1] << 16) |
64
+ (bytes[at + 2] << 8) |
65
+ bytes[at + 3];
66
+ }
67
+ for (let index = 16; index < 64; index += 1) {
68
+ const a = w[index - 15];
69
+ const b = w[index - 2];
70
+ const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
71
+ const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
72
+ w[index] = (w[index - 16] + s0 + w[index - 7] + s1) | 0;
73
+ }
74
+ const h = this.state;
75
+ let a = h[0];
76
+ let b = h[1];
77
+ let c = h[2];
78
+ let d = h[3];
79
+ let e = h[4];
80
+ let f = h[5];
81
+ let g = h[6];
82
+ let k = h[7];
83
+ for (let index = 0; index < 64; index += 1) {
84
+ const s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
85
+ const choose = (e & f) ^ (~e & g);
86
+ const t1 = (k + s1 + choose + K[index] + w[index]) | 0;
87
+ const s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
88
+ const majority = (a & b) ^ (a & c) ^ (b & c);
89
+ const t2 = (s0 + majority) | 0;
90
+ k = g;
91
+ g = f;
92
+ f = e;
93
+ e = (d + t1) | 0;
94
+ d = c;
95
+ c = b;
96
+ b = a;
97
+ a = (t1 + t2) | 0;
98
+ }
99
+ h[0] = h[0] + a;
100
+ h[1] = h[1] + b;
101
+ h[2] = h[2] + c;
102
+ h[3] = h[3] + d;
103
+ h[4] = h[4] + e;
104
+ h[5] = h[5] + f;
105
+ h[6] = h[6] + g;
106
+ h[7] = h[7] + k;
107
+ }
108
+ }
109
+ const nativeHash = (() => {
110
+ try {
111
+ const host = globalThis.process;
112
+ return host?.getBuiltinModule?.('crypto')?.createHash;
113
+ }
114
+ catch {
115
+ return undefined;
116
+ }
117
+ })();
118
+ export function sha256Hex(data) {
119
+ return nativeHash ? nativeHash('sha256').update(data).digest('hex') : new Sha256().update(data).digest();
120
+ }
@@ -1,3 +1,4 @@
1
+ import type { Principal } from '../types/principal.js';
1
2
  /**
2
3
  * The tenant id rule every scope shares. An id is letters, digits, `_`, and `-`, so it can never hold
3
4
  * the `/` a scoped name or key is built with: no tenant's prefix is a prefix of another's.
@@ -12,3 +13,13 @@ export declare class TenantIdError extends RangeError {
12
13
  export declare function assertTenantId(tenantId: string): string;
13
14
  /** @internal The prefix a tenant's names and keys carry in a shared store. */
14
15
  export declare function tenantPrefix(tenantId: string): string;
16
+ /**
17
+ * Run options with the principal's tenant applied: the principal's `tenantId` becomes the run's when
18
+ * it has none, a run whose two tenants differ is refused, and the principal's `userId` is recorded
19
+ * in the run's metadata.
20
+ */
21
+ export declare function withPrincipal<O extends {
22
+ tenantId?: string;
23
+ principal?: Principal;
24
+ metadata?: Record<string, unknown>;
25
+ }>(runOptions: O): O;
@@ -14,3 +14,17 @@ export function assertTenantId(tenantId) {
14
14
  export function tenantPrefix(tenantId) {
15
15
  return `tenant/${assertTenantId(tenantId)}/`;
16
16
  }
17
+ export function withPrincipal(runOptions) {
18
+ const principal = runOptions.principal;
19
+ if (!principal)
20
+ return runOptions;
21
+ const tenantId = principal.tenantId;
22
+ if (tenantId !== undefined && runOptions.tenantId !== undefined && tenantId !== runOptions.tenantId) {
23
+ throw new TypeError("The run's tenantId and its principal's tenant differ");
24
+ }
25
+ return {
26
+ ...runOptions,
27
+ ...(tenantId !== undefined && runOptions.tenantId === undefined ? { tenantId } : {}),
28
+ ...(principal.userId !== undefined ? { metadata: { ...runOptions.metadata, userId: principal.userId } } : {}),
29
+ };
30
+ }
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * This package's version, recorded where provenance matters, such as on every experiment. A test
3
3
  * keeps it equal to `package.json`.
4
4
  */
5
- export declare const NEXUS_VERSION = "2.2.0";
5
+ export declare const NEXUS_VERSION = "2.4.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const NEXUS_VERSION = '2.2.0';
1
+ export const NEXUS_VERSION = '2.4.0';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The contract suites the adapter kit ships: what every vector store, retriever, and long-term store
3
+ * must do, as checks that need no test framework. Each check is named for the behavior it proves,
4
+ * and a capability the adapter does not declare is reported as skipped, never as a failure.
5
+ */
6
+ import type { RagChunk } from '../hallucination/rag.js';
7
+ import type { EmbeddingProvider, VectorStore } from '../hallucination/retrieval.js';
8
+ import type { Retriever } from '../rag/retrievers.js';
9
+ import type { Store } from '../types/store.js';
10
+ /** One check of a contract suite. */
11
+ export interface AdapterCheck {
12
+ /** What was checked, as a sentence. */
13
+ name: string;
14
+ /** Whether it held. */
15
+ ok: boolean;
16
+ /** Why it did not hold, or why it was skipped. */
17
+ detail?: string;
18
+ /** True when the adapter does not declare the capability the check needs. */
19
+ skipped?: boolean;
20
+ }
21
+ /** The passages every retrieval contract runs on, and that benchmarks reuse. */
22
+ export declare const ADAPTER_FIXTURE_PASSAGES: readonly RagChunk[];
23
+ /** What a vector store declares it supports, for its contract. */
24
+ export interface VectorStoreCapabilities {
25
+ /** `searchVector()` with a vector computed elsewhere. Defaults to true. */
26
+ searchVector?: boolean;
27
+ /** `minScore` drops weak matches. Defaults to true. */
28
+ minScore?: boolean;
29
+ /** Metadata filters: `exact` matches values, `typed` also tells `3` from `'3'`, `none` has no filters. Defaults to `exact`. */
30
+ filters?: 'none' | 'exact' | 'typed';
31
+ }
32
+ /** Options for `runVectorStoreContract()`. */
33
+ export interface VectorStoreContractOptions {
34
+ /** The embedding function the store was given, to compute a query vector for `searchVector()`. */
35
+ embed: EmbeddingProvider;
36
+ /** What the store supports. */
37
+ capabilities?: VectorStoreCapabilities;
38
+ /** Waits for writes to become searchable, for a store whose index is eventually consistent. */
39
+ settle?: () => Promise<void>;
40
+ }
41
+ /**
42
+ * The `VectorStore` contract: ranking, `topK`, sources and metadata round-tripped, filters,
43
+ * `minScore`, vector search, replacement by id, deletes, and empty writes. The store should be empty
44
+ * when it starts.
45
+ */
46
+ export declare function runVectorStoreContract(store: VectorStore, options: VectorStoreContractOptions): Promise<AdapterCheck[]>;
47
+ /** What a retriever declares it supports, for its contract. */
48
+ export interface RetrieverCapabilities {
49
+ /** It holds its own passages, with `add()` and `delete()`: a `SparseRetriever`. Defaults to whether it has them. */
50
+ writes?: boolean;
51
+ /** Metadata filters. Defaults to true. */
52
+ filters?: boolean;
53
+ }
54
+ /** Options for `runRetrieverContract()`. */
55
+ export interface RetrieverContractOptions {
56
+ /** What the retriever supports. */
57
+ capabilities?: RetrieverCapabilities;
58
+ /**
59
+ * Loads the fixture passages, for a retriever that does not hold its own: into the vector store
60
+ * behind it, say. A retriever with `add()` is loaded through that.
61
+ */
62
+ load?: (passages: RagChunk[]) => Promise<void>;
63
+ /** Waits for writes to become searchable. */
64
+ settle?: () => Promise<void>;
65
+ }
66
+ /**
67
+ * The `Retriever` contract, and the `SparseRetriever` contract when the retriever takes writes:
68
+ * an exact term found, ranking, `topK`, filters, replacement, deletes, and an empty query.
69
+ */
70
+ export declare function runRetrieverContract(retriever: Retriever, options?: RetrieverContractOptions): Promise<AdapterCheck[]>;
71
+ /** What a long-term store declares it supports, for its contract. */
72
+ export interface StoreCapabilities {
73
+ /** `search()` lists a namespace prefix, newest first. Defaults to true. */
74
+ search?: boolean;
75
+ /** `search()` filters on value fields. Defaults to true. */
76
+ filters?: boolean;
77
+ /** `listNamespaces()`. Defaults to true. */
78
+ namespaces?: boolean;
79
+ }
80
+ /**
81
+ * The long-term `Store` contract: values round-trip, a replaced item keeps its creation time, a
82
+ * missing key reads as nothing, deletes, prefix search with filters and paging, and namespace
83
+ * listing. The store should be empty when it starts.
84
+ */
85
+ export declare function runStoreContract(store: Store, options?: {
86
+ capabilities?: StoreCapabilities;
87
+ }): Promise<AdapterCheck[]>;
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ADAPTER_FIXTURE_PASSAGES = void 0;
4
+ exports.runVectorStoreContract = runVectorStoreContract;
5
+ exports.runRetrieverContract = runRetrieverContract;
6
+ exports.runStoreContract = runStoreContract;
7
+ exports.ADAPTER_FIXTURE_PASSAGES = [
8
+ {
9
+ id: 'refunds#0',
10
+ content: 'Refunds are issued within fourteen days of a return',
11
+ source: 'policy.md',
12
+ metadata: { tenant: 'acme', page: 3 },
13
+ },
14
+ {
15
+ id: 'shipping#0',
16
+ content: 'Orders ship from the warehouse within two business days',
17
+ source: 'policy.md',
18
+ metadata: { tenant: 'acme' },
19
+ },
20
+ { id: 'refunds#1', content: 'A refund goes back to the original payment method', metadata: { tenant: 'globex' } },
21
+ { id: 'careers#0', content: 'We are hiring engineers in three offices' },
22
+ ];
23
+ function fixturePassages() {
24
+ return exports.ADAPTER_FIXTURE_PASSAGES.map((passage) => ({
25
+ ...passage,
26
+ ...(passage.metadata ? { metadata: { ...passage.metadata } } : {}),
27
+ }));
28
+ }
29
+ class Suite {
30
+ checks = [];
31
+ async check(name, run) {
32
+ try {
33
+ const result = await run();
34
+ this.checks.push(result === true ? { name, ok: true } : { name, ok: false, detail: result === false ? 'did not hold' : result });
35
+ }
36
+ catch (error) {
37
+ this.checks.push({ name, ok: false, detail: error instanceof Error ? error.message : String(error) });
38
+ }
39
+ }
40
+ skip(name, reason) {
41
+ this.checks.push({ name, ok: true, skipped: true, detail: reason });
42
+ }
43
+ }
44
+ async function runVectorStoreContract(store, options) {
45
+ const suite = new Suite();
46
+ const settle = options.settle ?? (async () => undefined);
47
+ const capabilities = options.capabilities ?? {};
48
+ const filters = capabilities.filters ?? 'exact';
49
+ await suite.check('adds passages', async () => {
50
+ await store.add(fixturePassages());
51
+ await settle();
52
+ return true;
53
+ });
54
+ await suite.check('ranks the closest passage first, best first, within topK', async () => {
55
+ const results = await store.search('how long do refunds take', { topK: 2 });
56
+ if (results.length !== 2)
57
+ return `expected 2 results for topK 2, got ${results.length}`;
58
+ if (results[0]?.id !== 'refunds#0')
59
+ return `expected refunds#0 first, got ${results[0]?.id}`;
60
+ return (results[0]?.score ?? 0) > (results[1]?.score ?? 0) || 'results are not ordered by score';
61
+ });
62
+ await suite.check('returns a passage source and its metadata whole', async () => {
63
+ const [first] = await store.search('how long do refunds take', { topK: 1 });
64
+ if (first?.source !== 'policy.md')
65
+ return `expected source policy.md, got ${first?.source}`;
66
+ return (JSON.stringify(first?.metadata) === JSON.stringify({ tenant: 'acme', page: 3 }) ||
67
+ `metadata came back as ${JSON.stringify(first?.metadata)}`);
68
+ });
69
+ if (filters === 'none')
70
+ suite.skip('narrows a search by metadata', 'the store declares no filters');
71
+ else {
72
+ await suite.check('narrows a search by metadata', async () => {
73
+ const results = await store.search('refund', { filter: { tenant: 'globex' } });
74
+ return ((results.length === 1 && results[0]?.id === 'refunds#1') ||
75
+ `got ${results.map((result) => result.id).join(', ')}`);
76
+ });
77
+ }
78
+ if (filters === 'typed') {
79
+ await suite.check('tells a number from the same digits as text in a filter', async () => {
80
+ const numeric = await store.search('refunds', { filter: { page: 3 }, topK: 10 });
81
+ const text = await store.search('refunds', { filter: { page: '3' }, topK: 10 });
82
+ return ((numeric.length === 1 && text.length === 0) || `number matched ${numeric.length}, text matched ${text.length}`);
83
+ });
84
+ }
85
+ else
86
+ suite.skip('tells a number from the same digits as text in a filter', 'the store declares untyped filters');
87
+ if (capabilities.minScore === false)
88
+ suite.skip('drops matches under minScore', 'the store declares no minScore');
89
+ else {
90
+ await suite.check('drops matches under minScore', async () => {
91
+ const results = await store.search('how long do refunds take', { minScore: 0.99 });
92
+ return results.length === 0 || `${results.length} results above 0.99`;
93
+ });
94
+ }
95
+ if (capabilities.searchVector === false)
96
+ suite.skip('searches by a precomputed vector', 'the store declares no searchVector');
97
+ else {
98
+ await suite.check('searches by a precomputed vector', async () => {
99
+ const [vector] = await options.embed(['orders ship from the warehouse']);
100
+ const [first] = await store.searchVector(vector, { topK: 1 });
101
+ return first?.id === 'shipping#0' || `expected shipping#0, got ${first?.id}`;
102
+ });
103
+ }
104
+ await suite.check('replaces a passage added again under its id', async () => {
105
+ await store.add([
106
+ { id: 'careers#0', content: 'Refunds for event tickets are not available', metadata: { tenant: 'acme' } },
107
+ ]);
108
+ await settle();
109
+ const results = await store.search('event tickets refunds', { topK: 10 });
110
+ const copies = results.filter((result) => result.id === 'careers#0');
111
+ if (copies.length !== 1)
112
+ return `found ${copies.length} copies`;
113
+ return /event tickets/.test(copies[0]?.content ?? '') || 'the old content is still there';
114
+ });
115
+ await suite.check('deletes by id, ignoring ids it does not hold', async () => {
116
+ await store.delete(['refunds#0', 'not-stored']);
117
+ await settle();
118
+ const results = await store.search('how long do refunds take', { topK: 10 });
119
+ return !results.some((result) => result.id === 'refunds#0') || 'the deleted passage is still found';
120
+ });
121
+ await suite.check('accepts empty writes', async () => {
122
+ await store.add([]);
123
+ await store.delete([]);
124
+ return true;
125
+ });
126
+ return suite.checks;
127
+ }
128
+ async function runRetrieverContract(retriever, options = {}) {
129
+ const suite = new Suite();
130
+ const settle = options.settle ?? (async () => undefined);
131
+ const sparse = retriever;
132
+ const writes = options.capabilities?.writes ?? (typeof sparse.add === 'function' && typeof sparse.delete === 'function');
133
+ const passages = fixturePassages();
134
+ await suite.check('loads the fixture passages', async () => {
135
+ if (writes)
136
+ await sparse.add?.(passages);
137
+ else if (options.load)
138
+ await options.load(passages);
139
+ else
140
+ return 'a retriever without add() needs a load() to fill what it reads from';
141
+ await settle();
142
+ return true;
143
+ });
144
+ await suite.check('finds the passage holding the query terms first', async () => {
145
+ const [first] = await retriever.retrieve('warehouse business days', { topK: 3 });
146
+ return first?.id === 'shipping#0' || `expected shipping#0, got ${first?.id}`;
147
+ });
148
+ await suite.check('returns at most topK results, best first', async () => {
149
+ const results = await retriever.retrieve('refund refunds return', { topK: 1 });
150
+ return results.length <= 1 || `${results.length} results for topK 1`;
151
+ });
152
+ if (options.capabilities?.filters === false)
153
+ suite.skip('narrows by metadata', 'the retriever declares no filters');
154
+ else {
155
+ await suite.check('narrows by metadata', async () => {
156
+ const results = await retriever.retrieve('refund', { filter: { tenant: 'globex' }, topK: 10 });
157
+ return ((results.every((result) => result.metadata?.tenant === 'globex') && results.length > 0) ||
158
+ `got ${results.map((result) => result.id).join(', ')}`);
159
+ });
160
+ }
161
+ if (!writes) {
162
+ suite.skip('replaces a passage added again under its id', 'the retriever does not take writes');
163
+ suite.skip('deletes by id, ignoring ids it does not hold', 'the retriever does not take writes');
164
+ }
165
+ else {
166
+ await suite.check('replaces a passage added again under its id', async () => {
167
+ await sparse.add?.([{ id: 'careers#0', content: 'Engineers relocate with a stipend' }]);
168
+ await settle();
169
+ const results = await retriever.retrieve('stipend relocate', { topK: 10 });
170
+ return (results.filter((result) => result.id === 'careers#0').length === 1 || 'the passage is missing or duplicated');
171
+ });
172
+ await suite.check('deletes by id, ignoring ids it does not hold', async () => {
173
+ await sparse.delete?.(['shipping#0', 'not-stored']);
174
+ await settle();
175
+ const results = await retriever.retrieve('warehouse business days', { topK: 10 });
176
+ return !results.some((result) => result.id === 'shipping#0') || 'the deleted passage is still found';
177
+ });
178
+ }
179
+ await suite.check('answers an empty query without failing', async () => {
180
+ await retriever.retrieve('', { topK: 3 });
181
+ return true;
182
+ });
183
+ return suite.checks;
184
+ }
185
+ async function runStoreContract(store, options = {}) {
186
+ const suite = new Suite();
187
+ const capabilities = options.capabilities ?? {};
188
+ const namespace = ['contract', 'users'];
189
+ await suite.check('round-trips a value', async () => {
190
+ await store.put(namespace, 'ann', { name: 'Ann', plan: 'pro' });
191
+ const item = await store.get(namespace, 'ann');
192
+ return (item?.value.name === 'Ann' && item.key === 'ann') || `read ${JSON.stringify(item)}`;
193
+ });
194
+ await suite.check('keeps the creation time when an item is replaced', async () => {
195
+ const before = await store.get(namespace, 'ann');
196
+ await new Promise((resolve) => setTimeout(resolve, 5));
197
+ await store.put(namespace, 'ann', { name: 'Ann', plan: 'team' });
198
+ const after = await store.get(namespace, 'ann');
199
+ if (after?.value.plan !== 'team')
200
+ return 'the replacement was not stored';
201
+ return after.createdAt === before?.createdAt || `createdAt moved from ${before?.createdAt} to ${after.createdAt}`;
202
+ });
203
+ await suite.check('reads a missing key as nothing', async () => (await store.get(namespace, 'nobody')) === undefined);
204
+ await suite.check('deletes an item', async () => {
205
+ await store.put(namespace, 'temp', { name: 'Temp' });
206
+ await store.delete(namespace, 'temp');
207
+ return (await store.get(namespace, 'temp')) === undefined;
208
+ });
209
+ if (capabilities.search === false)
210
+ suite.skip('lists a namespace prefix, with paging', 'the store declares no search');
211
+ else {
212
+ await suite.check('lists a namespace prefix, with paging', async () => {
213
+ await store.put([...namespace, 'archived'], 'bob', { name: 'Bob', plan: 'free' });
214
+ await store.put(namespace, 'cy', { name: 'Cy', plan: 'free' });
215
+ const all = await store.search(['contract'], { limit: 10 });
216
+ if (all.length !== 3)
217
+ return `expected 3 items under the prefix, got ${all.length}`;
218
+ const page = await store.search(['contract'], { limit: 2, offset: 2 });
219
+ return page.length === 1 || `expected 1 item on the second page, got ${page.length}`;
220
+ });
221
+ }
222
+ if (capabilities.search === false || capabilities.filters === false)
223
+ suite.skip('filters by a value field', 'the store declares no filters');
224
+ else {
225
+ await suite.check('filters by a value field', async () => {
226
+ const free = await store.search(['contract'], { filter: { plan: 'free' }, limit: 10 });
227
+ const names = free
228
+ .map((item) => item.value.name)
229
+ .sort()
230
+ .join(',');
231
+ return names === 'Bob,Cy' || `got ${names}`;
232
+ });
233
+ }
234
+ if (capabilities.namespaces === false)
235
+ suite.skip('lists namespaces under a prefix', 'the store declares no namespace listing');
236
+ else {
237
+ await suite.check('lists namespaces under a prefix', async () => {
238
+ const listed = (await store.listNamespaces({ prefix: ['contract'] })).map((parts) => parts.join('/')).sort();
239
+ return listed.join(',') === 'contract/users,contract/users/archived' || `got ${listed.join(', ')}`;
240
+ });
241
+ }
242
+ return suite.checks;
243
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * One vocabulary for adapter failures, so code above an adapter retries, alerts, and reports the same
3
+ * way whichever database, provider, or service the adapter talks to.
4
+ */
5
+ /** What kind of failure an adapter call met. */
6
+ export type AdapterErrorCode = 'AUTH' | 'RATE_LIMIT' | 'NOT_FOUND' | 'INVALID' | 'CONFLICT' | 'TIMEOUT' | 'UNAVAILABLE' | 'CANCELLED' | 'UNKNOWN';
7
+ /** A failure's code, and whether another attempt could succeed. */
8
+ export interface AdapterErrorClass {
9
+ /** The kind of failure. */
10
+ code: AdapterErrorCode;
11
+ /** True for a failure a retry could get past: a rate limit, a timeout, an unavailable service. */
12
+ retryable: boolean;
13
+ /** The HTTP status, when the failure carried one. */
14
+ status?: number;
15
+ }
16
+ /** A failed adapter call, normalized. The original error is its `cause`. */
17
+ export declare class AdapterError extends Error {
18
+ /** The adapter that failed. */
19
+ readonly adapter: string;
20
+ /** The method that failed, such as `search`. */
21
+ readonly operation: string;
22
+ /** The kind of failure. */
23
+ readonly code: AdapterErrorCode;
24
+ /** Whether another attempt could succeed. */
25
+ readonly retryable: boolean;
26
+ /** The HTTP status, when the failure carried one. */
27
+ readonly status?: number;
28
+ constructor(
29
+ /** The adapter that failed. */
30
+ adapter: string,
31
+ /** The method that failed, such as `search`. */
32
+ operation: string, failure: AdapterErrorClass, cause: unknown);
33
+ }
34
+ /**
35
+ * Classifies any error by what it carries: an HTTP status (`status`, `statusCode`, or
36
+ * `response.status`), a Node network code such as `ECONNREFUSED`, or an `AbortError` or
37
+ * `TimeoutError` name. An adapter's own `normalizeError` runs first and may override it.
38
+ */
39
+ export declare function classifyAdapterError(error: unknown): AdapterErrorClass;