@stainless-api/docs 0.1.0-beta.123 → 0.1.0-beta.125

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.125
4
+
5
+ ### Patch Changes
6
+
7
+ - 975ba51: cap prose indexing batches to 30MB
8
+ - Updated dependencies [005419c]
9
+ - @stainless-api/docs-ui@0.1.0-beta.89
10
+ - @stainless-api/docs-search@0.1.0-beta.42
11
+
12
+ ## 0.1.0-beta.124
13
+
14
+ ### Patch Changes
15
+
16
+ - 152c864: don’t try to load github themes unconditionally
17
+ - Updated dependencies [af5a5d7]
18
+ - @stainless-api/docs-ui@0.1.0-beta.88
19
+ - @stainless-api/docs-search@0.1.0-beta.41
20
+
3
21
  ## 0.1.0-beta.123
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.123",
3
+ "version": "0.1.0-beta.125",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -34,8 +34,8 @@
34
34
  "node": ">=22.12.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@astrojs/starlight": ">=0.37.5",
38
- "astro": ">=5.15.3",
37
+ "@astrojs/starlight": ">=0.38.0",
38
+ "astro": ">=6.0.0",
39
39
  "react": ">=19.0.0",
40
40
  "react-dom": ">=19.0.0",
41
41
  "vite": ">=7.3.1"
@@ -49,7 +49,7 @@
49
49
  "clsx": "^2.1.1",
50
50
  "dotenv": "17.3.1",
51
51
  "lucide-react": "^0.577.0",
52
- "marked": "^17.0.4",
52
+ "marked": "^17.0.5",
53
53
  "node-html-parser": "^7.1.0",
54
54
  "rehype-parse": "^9.0.1",
55
55
  "rehype-remark": "^10.0.1",
@@ -60,9 +60,9 @@
60
60
  "unified": "^11.0.5",
61
61
  "vite-plugin-prebundle-workers": "^0.2.0",
62
62
  "web-worker": "^1.5.0",
63
- "yaml": "^2.8.2",
64
- "@stainless-api/docs-search": "0.1.0-beta.40",
65
- "@stainless-api/docs-ui": "0.1.0-beta.87",
63
+ "yaml": "^2.8.3",
64
+ "@stainless-api/docs-search": "0.1.0-beta.42",
65
+ "@stainless-api/docs-ui": "0.1.0-beta.89",
66
66
  "@stainless-api/ui-primitives": "0.1.0-beta.50"
67
67
  },
68
68
  "devDependencies": {
@@ -76,6 +76,7 @@
76
76
  "tsx": "^4.21.0",
77
77
  "typescript": "5.9.3",
78
78
  "vite": "^7.3.1",
79
+ "vitest": "^4.1.2",
79
80
  "zod": "^4.3.6",
80
81
  "@stainless/eslint-config": "0.1.0-beta.1",
81
82
  "@stainless/sdk-json": "^0.1.0-beta.9"
@@ -84,6 +85,7 @@
84
85
  "vendor-deps": "tsx scripts/vendor_deps.ts",
85
86
  "lint": "eslint --flag unstable_native_nodejs_ts_config . --max-warnings 0",
86
87
  "sync": "astro sync",
87
- "check:types": "astro check"
88
+ "check:types": "astro check",
89
+ "test": "vitest run"
88
90
  }
89
91
  }
@@ -425,8 +425,8 @@ async function astroHighlight() {
425
425
 
426
426
  astroShikiHighlighter = createHighlighter({
427
427
  themes: [
428
- 'github-light',
429
- 'github-dark',
428
+ HIGHLIGHT_THEMES?.dark ?? 'github-dark',
429
+ HIGHLIGHT_THEMES?.light ?? 'github-light',
430
430
  {
431
431
  name: 'stainless-docs-json',
432
432
  colors: {
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { batchBySize, MAX_BATCH_BYTES, MAX_BATCH_DOCS } from './proseDocSync';
3
+
4
+ function makeDoc(docId: string, sizeBytes: number) {
5
+ return {
6
+ docId,
7
+ content: Buffer.alloc(sizeBytes, 'x'),
8
+ sha256: 'fake',
9
+ source: `/${docId}`,
10
+ };
11
+ }
12
+
13
+ describe('batchBySize', () => {
14
+ it('returns empty array for no docs', () => {
15
+ expect(batchBySize([])).toEqual([]);
16
+ });
17
+
18
+ it('puts all docs in one batch when under both limits', () => {
19
+ const docs = Array.from({ length: 5 }, (_, i) => makeDoc(`doc-${i}`, 100));
20
+ const batches = batchBySize(docs);
21
+ expect(batches).toHaveLength(1);
22
+ expect(batches[0]).toHaveLength(5);
23
+ });
24
+
25
+ it('splits at MAX_BATCH_DOCS', () => {
26
+ const docs = Array.from({ length: MAX_BATCH_DOCS + 10 }, (_, i) => makeDoc(`doc-${i}`, 10));
27
+ const batches = batchBySize(docs);
28
+ expect(batches).toHaveLength(2);
29
+ expect(batches[0]).toHaveLength(MAX_BATCH_DOCS);
30
+ expect(batches[1]).toHaveLength(10);
31
+ });
32
+
33
+ it('splits when cumulative size exceeds MAX_BATCH_BYTES', () => {
34
+ const docSize = 10 * 1024 * 1024; // 10MB each
35
+ // 4 docs = 40MB > 30MB limit, so should split into [3, 1]
36
+ const docs = Array.from({ length: 4 }, (_, i) => makeDoc(`doc-${i}`, docSize));
37
+ const batches = batchBySize(docs);
38
+ expect(batches).toHaveLength(2);
39
+ expect(batches[0]).toHaveLength(3);
40
+ expect(batches[1]).toHaveLength(1);
41
+ });
42
+
43
+ it('handles a single doc larger than MAX_BATCH_BYTES', () => {
44
+ const docs = [makeDoc('huge', MAX_BATCH_BYTES + 1)];
45
+ const batches = batchBySize(docs);
46
+ // Still goes into a batch on its own — we can't split a single doc
47
+ expect(batches).toHaveLength(1);
48
+ expect(batches[0]).toHaveLength(1);
49
+ });
50
+
51
+ it('byte limit takes precedence over doc count when hit first', () => {
52
+ // 2 docs of 20MB each = 40MB > 30MB limit, well under 100 doc limit
53
+ const docs = [makeDoc('a', 20 * 1024 * 1024), makeDoc('b', 20 * 1024 * 1024)];
54
+ const batches = batchBySize(docs);
55
+ expect(batches).toHaveLength(2);
56
+ expect(batches[0]).toHaveLength(1);
57
+ expect(batches[1]).toHaveLength(1);
58
+ });
59
+
60
+ it('creates multiple batches for many large docs', () => {
61
+ const docSize = 8 * 1024 * 1024; // 8MB each
62
+ // 10 docs * 8MB = 80MB, should split into batches of ~3 (24MB each)
63
+ const docs = Array.from({ length: 10 }, (_, i) => makeDoc(`doc-${i}`, docSize));
64
+ const batches = batchBySize(docs);
65
+ // Every batch should be <= MAX_BATCH_BYTES
66
+ for (const batch of batches) {
67
+ const totalBytes = batch.reduce((sum, d) => sum + d.content.byteLength, 0);
68
+ expect(totalBytes).toBeLessThanOrEqual(MAX_BATCH_BYTES);
69
+ }
70
+ // All docs should be accounted for
71
+ const totalDocs = batches.reduce((sum, b) => sum + b.length, 0);
72
+ expect(totalDocs).toBe(10);
73
+ });
74
+ });
@@ -9,6 +9,10 @@ import { NormalizedStainlessDocsConfig } from './loadStlDocsConfig';
9
9
 
10
10
  const DOCS_API_BASE_URL = 'https://api.stainlessapi.com';
11
11
 
12
+ export const MAX_BATCH_DOCS = 100;
13
+ // Cloud Run has a 32MB request body limit; leave headroom for JSON envelope overhead
14
+ export const MAX_BATCH_BYTES = 30 * 1024 * 1024;
15
+
12
16
  // ─── API client ──────────────────────────────────────────────────────
13
17
 
14
18
  async function docsApiRequest(
@@ -96,6 +100,33 @@ function diffManifests(
96
100
  return { toPut, toDelete };
97
101
  }
98
102
 
103
+ // ─── Batching ────────────────────────────────────────────────────────
104
+
105
+ export function batchBySize(docs: (LocalDoc & { docId: string })[]): (LocalDoc & { docId: string })[][] {
106
+ const batches: (LocalDoc & { docId: string })[][] = [];
107
+ let current: (LocalDoc & { docId: string })[] = [];
108
+ let currentBytes = 0;
109
+
110
+ for (const doc of docs) {
111
+ const docBytes = doc.content.byteLength;
112
+
113
+ if (
114
+ current.length > 0 &&
115
+ (current.length >= MAX_BATCH_DOCS || currentBytes + docBytes > MAX_BATCH_BYTES)
116
+ ) {
117
+ batches.push(current);
118
+ current = [];
119
+ currentBytes = 0;
120
+ }
121
+
122
+ current.push(doc);
123
+ currentBytes += docBytes;
124
+ }
125
+
126
+ if (current.length > 0) batches.push(current);
127
+ return batches;
128
+ }
129
+
99
130
  // ─── Import & delete ────────────────────────────────────────────────
100
131
 
101
132
  type ImportJobResult = { succeeded: number; failed: number; errors: string[] };
@@ -108,10 +139,9 @@ async function importDocuments(
108
139
  logger: AstroIntegrationLogger,
109
140
  ): Promise<ImportJobResult> {
110
141
  const totals: ImportJobResult = { succeeded: 0, failed: 0, errors: [] };
142
+ const batches = batchBySize(docs);
111
143
 
112
- for (let i = 0; i < docs.length; i += 100) {
113
- const batch = docs.slice(i, i + 100);
114
-
144
+ for (const batch of batches) {
115
145
  let response: Response;
116
146
  try {
117
147
  response = await docsApiRequest('POST', `/api/docs-sites/${docsSiteId}/documents/import`, apiKey, {