vocs 2.7.2 → 2.8.1

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 (39) hide show
  1. package/dist/internal/embedding.d.ts.map +1 -1
  2. package/dist/internal/embedding.js +5 -1
  3. package/dist/internal/embedding.js.map +1 -1
  4. package/dist/internal/mdx.d.ts +15 -0
  5. package/dist/internal/mdx.d.ts.map +1 -1
  6. package/dist/internal/mdx.js +140 -0
  7. package/dist/internal/mdx.js.map +1 -1
  8. package/dist/internal/promise.d.ts +10 -0
  9. package/dist/internal/promise.d.ts.map +1 -0
  10. package/dist/internal/promise.js +30 -0
  11. package/dist/internal/promise.js.map +1 -0
  12. package/dist/internal/reranker.d.ts.map +1 -1
  13. package/dist/internal/reranker.js +5 -1
  14. package/dist/internal/reranker.js.map +1 -1
  15. package/dist/internal/vector-store.d.ts.map +1 -1
  16. package/dist/internal/vector-store.js +5 -1
  17. package/dist/internal/vector-store.js.map +1 -1
  18. package/dist/mdx.d.ts.map +1 -1
  19. package/dist/mdx.js +3 -0
  20. package/dist/mdx.js.map +1 -1
  21. package/dist/react/internal/Benchmarks.mdx.d.ts +9 -0
  22. package/dist/react/internal/Benchmarks.mdx.d.ts.map +1 -0
  23. package/dist/react/internal/Benchmarks.mdx.js +6 -0
  24. package/dist/react/internal/Benchmarks.mdx.js.map +1 -0
  25. package/dist/server/openapi/assets.generated.js +3 -3
  26. package/dist/server/openapi/assets.generated.js.map +1 -1
  27. package/dist/styles/markdown.css +93 -0
  28. package/package.json +1 -1
  29. package/src/internal/embedding.ts +21 -12
  30. package/src/internal/mdx.test.ts +195 -0
  31. package/src/internal/mdx.ts +150 -0
  32. package/src/internal/promise.test.ts +90 -0
  33. package/src/internal/promise.ts +41 -0
  34. package/src/internal/reranker.ts +22 -13
  35. package/src/internal/vector-store.ts +18 -9
  36. package/src/mdx.tsx +2 -0
  37. package/src/react/internal/Benchmarks.mdx.tsx +30 -0
  38. package/src/server/openapi/assets.generated.ts +3 -3
  39. package/src/styles/markdown.css +93 -0
@@ -0,0 +1,90 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import * as promise from './promise.js'
3
+
4
+ afterEach(() => vi.useRealTimers())
5
+
6
+ describe('withRetry', () => {
7
+ it('retries failures', async () => {
8
+ const fn = vi
9
+ .fn<() => Promise<string>>()
10
+ .mockRejectedValueOnce(new Error('failed'))
11
+ .mockRejectedValueOnce(new Error('failed'))
12
+ .mockResolvedValueOnce('ok')
13
+
14
+ await expect(promise.withRetry(fn, { baseMs: 0 })).resolves.toBe('ok')
15
+ expect(fn).toHaveBeenCalledTimes(3)
16
+ })
17
+
18
+ it('throws after exhausting retries', async () => {
19
+ const error = new Error('failed')
20
+ const fn = vi.fn().mockRejectedValue(error)
21
+
22
+ await expect(promise.withRetry(fn, { baseMs: 0 })).rejects.toBe(error)
23
+ expect(fn).toHaveBeenCalledTimes(3)
24
+ })
25
+
26
+ it('only retries matching failures', async () => {
27
+ const error = new Error('invalid request')
28
+ const fn = vi.fn().mockRejectedValue(error)
29
+
30
+ await expect(
31
+ promise.withRetry(fn, {
32
+ baseMs: 0,
33
+ shouldRetry: (error) => error instanceof TypeError,
34
+ }),
35
+ ).rejects.toBe(error)
36
+ expect(fn).toHaveBeenCalledOnce()
37
+ })
38
+
39
+ it('stops retrying when aborted', async () => {
40
+ const controller = new AbortController()
41
+ const fn = vi.fn().mockImplementation(async () => {
42
+ controller.abort(new Error('aborted'))
43
+ throw new Error('failed')
44
+ })
45
+
46
+ await expect(
47
+ promise.withRetry(fn, {
48
+ baseMs: 0,
49
+ signal: controller.signal,
50
+ }),
51
+ ).rejects.toThrow('failed')
52
+ expect(fn).toHaveBeenCalledOnce()
53
+ })
54
+
55
+ it('cancels the retry delay when aborted', async () => {
56
+ const controller = new AbortController()
57
+ const error = new Error('aborted')
58
+ const fn = vi.fn().mockRejectedValue(new Error('failed'))
59
+ const retry = promise.withRetry(fn, {
60
+ baseMs: 100,
61
+ signal: controller.signal,
62
+ })
63
+
64
+ setTimeout(() => controller.abort(error), 0)
65
+
66
+ await expect(retry).rejects.toBe(error)
67
+ expect(fn).toHaveBeenCalledOnce()
68
+ })
69
+
70
+ it('backs off exponentially', async () => {
71
+ vi.useFakeTimers()
72
+ const fn = vi
73
+ .fn<() => Promise<string>>()
74
+ .mockRejectedValueOnce(new Error('failed'))
75
+ .mockRejectedValueOnce(new Error('failed'))
76
+ .mockResolvedValueOnce('ok')
77
+ const retry = promise.withRetry(fn, { baseMs: 100 })
78
+
79
+ await vi.advanceTimersByTimeAsync(99)
80
+ expect(fn).toHaveBeenCalledOnce()
81
+ await vi.advanceTimersByTimeAsync(1)
82
+ expect(fn).toHaveBeenCalledTimes(2)
83
+ await vi.advanceTimersByTimeAsync(199)
84
+ expect(fn).toHaveBeenCalledTimes(2)
85
+ await vi.advanceTimersByTimeAsync(1)
86
+
87
+ await expect(retry).resolves.toBe('ok')
88
+ expect(fn).toHaveBeenCalledTimes(3)
89
+ })
90
+ })
@@ -0,0 +1,41 @@
1
+ export async function withRetry<value>(
2
+ fn: () => Promise<value>,
3
+ options: withRetry.Options = {},
4
+ ): Promise<value> {
5
+ const { baseMs = 250, retries = 2, shouldRetry = () => true, signal } = options
6
+ for (let attempt = 0; ; attempt++) {
7
+ try {
8
+ return await fn()
9
+ } catch (error) {
10
+ if (signal?.aborted || attempt >= retries || !shouldRetry(error)) throw error
11
+ await wait(baseMs * 2 ** attempt, signal)
12
+ }
13
+ }
14
+ }
15
+
16
+ export declare namespace withRetry {
17
+ type Options = {
18
+ baseMs?: number | undefined
19
+ retries?: number | undefined
20
+ shouldRetry?: ((error: unknown) => boolean) | undefined
21
+ signal?: AbortSignal | null | undefined
22
+ }
23
+ }
24
+
25
+ async function wait(delay: number, signal?: AbortSignal | null): Promise<void> {
26
+ if (signal?.aborted) throw signal.reason
27
+ await new Promise<void>((resolve, reject) => {
28
+ const timeout = setTimeout(done, delay)
29
+ signal?.addEventListener('abort', aborted, { once: true })
30
+
31
+ function aborted() {
32
+ clearTimeout(timeout)
33
+ reject(signal?.reason)
34
+ }
35
+
36
+ function done() {
37
+ signal?.removeEventListener('abort', aborted)
38
+ resolve()
39
+ }
40
+ })
41
+ }
@@ -15,6 +15,8 @@
15
15
  * private `_localRetriever` config — never in serializable config sent to the browser.
16
16
  */
17
17
 
18
+ import * as promise from './promise.js'
19
+
18
20
  export type RerankContext = {
19
21
  /** Optional abort signal to cancel the request. */
20
22
  signal?: AbortSignal | undefined
@@ -111,20 +113,27 @@ export function cloudflare(options: cloudflare.Options = {}): Adapter {
111
113
  if (!apiToken)
112
114
  throw new Error('[vocs] Reranker.cloudflare: missing `apiToken` (or CLOUDFLARE_API_TOKEN).')
113
115
  const url = `${baseUrl.replace(/\/$/, '')}/accounts/${accountId}/ai/run/${model}`
114
- const response = await fetch(url, {
115
- method: 'POST',
116
- headers: {
117
- Authorization: `Bearer ${apiToken}`,
118
- 'Content-Type': 'application/json',
119
- ...headers,
116
+ const response = await promise.withRetry(
117
+ () =>
118
+ fetch(url, {
119
+ method: 'POST',
120
+ headers: {
121
+ Authorization: `Bearer ${apiToken}`,
122
+ 'Content-Type': 'application/json',
123
+ ...headers,
124
+ },
125
+ body: JSON.stringify({
126
+ query,
127
+ contexts: documents.map((text) => ({ text })),
128
+ ...(context.topK ? { top_k: context.topK } : {}),
129
+ }),
130
+ signal: context.signal ?? null,
131
+ }),
132
+ {
133
+ shouldRetry: (error) => error instanceof TypeError,
134
+ signal: context.signal,
120
135
  },
121
- body: JSON.stringify({
122
- query,
123
- contexts: documents.map((text) => ({ text })),
124
- ...(context.topK ? { top_k: context.topK } : {}),
125
- }),
126
- signal: context.signal ?? null,
127
- })
136
+ )
128
137
  if (!response.ok)
129
138
  throw new Error(
130
139
  `[vocs] cloudflare rerank failed (${response.status}): ${await safeText(response)}`,
@@ -14,6 +14,8 @@
14
14
  * runtime, so no index artifact ships with the server bundle.
15
15
  */
16
16
 
17
+ import * as promise from './promise.js'
18
+
17
19
  export type Format = 'float32' | 'int8'
18
20
 
19
21
  export type Adapter = StaticAdapter | RemoteAdapter
@@ -170,16 +172,23 @@ export function cloudflare(options: cloudflare.Options = {}): RemoteAdapter {
170
172
  '[vocs] VectorStore.cloudflare: missing `apiToken` (or CLOUDFLARE_API_TOKEN).',
171
173
  )
172
174
  const url = `${baseUrl.replace(/\/$/, '')}/accounts/${accountId}/vectorize/v2/indexes${path}`
173
- return await fetch(url, {
174
- method: init.method ?? 'GET',
175
- headers: {
176
- Authorization: `Bearer ${apiToken}`,
177
- ...(init.contentType ? { 'Content-Type': init.contentType } : {}),
178
- ...headers,
175
+ return await promise.withRetry(
176
+ () =>
177
+ fetch(url, {
178
+ method: init.method ?? 'GET',
179
+ headers: {
180
+ Authorization: `Bearer ${apiToken}`,
181
+ ...(init.contentType ? { 'Content-Type': init.contentType } : {}),
182
+ ...headers,
183
+ },
184
+ ...(init.body !== undefined ? { body: init.body } : {}),
185
+ signal: init.signal ?? null,
186
+ }),
187
+ {
188
+ shouldRetry: (error) => error instanceof TypeError,
189
+ signal: init.signal,
179
190
  },
180
- ...(init.body !== undefined ? { body: init.body } : {}),
181
- signal: init.signal ?? null,
182
- })
191
+ )
183
192
  }
184
193
 
185
194
  /** Creates the index when missing; validates dimensions when present. */
package/src/mdx.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { MDXComponents } from 'mdx/types.js'
2
2
 
3
3
  import { Callout } from './react/Callout.js'
4
+ import { Benchmarks } from './react/internal/Benchmarks.mdx.js'
4
5
  import { Changelog } from './react/internal/Changelog.mdx.js'
5
6
  import { CodeBlock } from './react/internal/CodeBlock.mdx.js'
6
7
  import { CodeGroup } from './react/internal/CodeGroup.mdx.js'
@@ -36,6 +37,7 @@ export const components: MDXComponents = {
36
37
  }
37
38
  >,
38
39
  ) {
40
+ if ('data-v-benchmarks' in props) return <Benchmarks {...props} />
39
41
  if ('data-v-changelog' in props) return <Changelog {...props} />
40
42
  if ('data-v-code-group' in props) return <CodeGroup {...props} />
41
43
  if ('data-v-file-tree' in props) return <FileTree {...props} />
@@ -0,0 +1,30 @@
1
+ import type * as React from 'react'
2
+
3
+ export function Benchmarks(props: Benchmarks.Props) {
4
+ const { children, legend, scalar: _scalar, ...rest } = props
5
+ return (
6
+ <div {...rest} data-v data-v-benchmarks>
7
+ {children}
8
+ {legend !== 'false' && (
9
+ <footer data-v-benchmarks-legend>
10
+ <span>Faster</span>
11
+ <span data-v-benchmarks-legend-scale />
12
+ <span>Slower</span>
13
+ <span data-v-benchmarks-legend-baseline>
14
+ <span data-v-benchmarks-legend-swatch />
15
+ baseline
16
+ </span>
17
+ </footer>
18
+ )}
19
+ </div>
20
+ )
21
+ }
22
+
23
+ export declare namespace Benchmarks {
24
+ type Props = React.PropsWithChildren<
25
+ React.ComponentProps<'div'> & {
26
+ legend?: string | undefined
27
+ scalar?: string | undefined
28
+ }
29
+ >
30
+ }