vector-framework 1.2.2 → 1.2.3

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 (189) hide show
  1. package/README.md +18 -6
  2. package/dist/auth/protected.d.ts +4 -4
  3. package/dist/auth/protected.d.ts.map +1 -1
  4. package/dist/auth/protected.js +10 -7
  5. package/dist/auth/protected.js.map +1 -1
  6. package/dist/cache/manager.d.ts +2 -0
  7. package/dist/cache/manager.d.ts.map +1 -1
  8. package/dist/cache/manager.js +21 -4
  9. package/dist/cache/manager.js.map +1 -1
  10. package/dist/checkpoint/artifacts/compressor.d.ts +5 -0
  11. package/dist/checkpoint/artifacts/compressor.d.ts.map +1 -0
  12. package/dist/checkpoint/artifacts/compressor.js +24 -0
  13. package/dist/checkpoint/artifacts/compressor.js.map +1 -0
  14. package/dist/checkpoint/artifacts/decompress-worker.d.ts +2 -0
  15. package/dist/checkpoint/artifacts/decompress-worker.d.ts.map +1 -0
  16. package/dist/checkpoint/artifacts/decompress-worker.js +31 -0
  17. package/dist/checkpoint/artifacts/decompress-worker.js.map +1 -0
  18. package/dist/checkpoint/artifacts/hasher.d.ts +2 -0
  19. package/dist/checkpoint/artifacts/hasher.d.ts.map +1 -0
  20. package/dist/checkpoint/artifacts/hasher.js +7 -0
  21. package/dist/checkpoint/artifacts/hasher.js.map +1 -0
  22. package/dist/checkpoint/artifacts/manifest.d.ts +6 -0
  23. package/dist/checkpoint/artifacts/manifest.d.ts.map +1 -0
  24. package/dist/checkpoint/artifacts/manifest.js +55 -0
  25. package/dist/checkpoint/artifacts/manifest.js.map +1 -0
  26. package/dist/checkpoint/artifacts/materializer.d.ts +16 -0
  27. package/dist/checkpoint/artifacts/materializer.d.ts.map +1 -0
  28. package/dist/checkpoint/artifacts/materializer.js +168 -0
  29. package/dist/checkpoint/artifacts/materializer.js.map +1 -0
  30. package/dist/checkpoint/artifacts/packager.d.ts +12 -0
  31. package/dist/checkpoint/artifacts/packager.d.ts.map +1 -0
  32. package/dist/checkpoint/artifacts/packager.js +82 -0
  33. package/dist/checkpoint/artifacts/packager.js.map +1 -0
  34. package/dist/checkpoint/artifacts/repository.d.ts +11 -0
  35. package/dist/checkpoint/artifacts/repository.d.ts.map +1 -0
  36. package/dist/checkpoint/artifacts/repository.js +29 -0
  37. package/dist/checkpoint/artifacts/repository.js.map +1 -0
  38. package/dist/checkpoint/artifacts/store.d.ts +13 -0
  39. package/dist/checkpoint/artifacts/store.d.ts.map +1 -0
  40. package/dist/checkpoint/artifacts/store.js +85 -0
  41. package/dist/checkpoint/artifacts/store.js.map +1 -0
  42. package/dist/checkpoint/artifacts/types.d.ts +21 -0
  43. package/dist/checkpoint/artifacts/types.d.ts.map +1 -0
  44. package/dist/checkpoint/artifacts/types.js +2 -0
  45. package/dist/checkpoint/artifacts/types.js.map +1 -0
  46. package/dist/checkpoint/artifacts/worker-decompressor.d.ts +17 -0
  47. package/dist/checkpoint/artifacts/worker-decompressor.d.ts.map +1 -0
  48. package/dist/checkpoint/artifacts/worker-decompressor.js +148 -0
  49. package/dist/checkpoint/artifacts/worker-decompressor.js.map +1 -0
  50. package/dist/checkpoint/asset-store.d.ts +10 -0
  51. package/dist/checkpoint/asset-store.d.ts.map +1 -0
  52. package/dist/checkpoint/asset-store.js +46 -0
  53. package/dist/checkpoint/asset-store.js.map +1 -0
  54. package/dist/checkpoint/bundler.d.ts +15 -0
  55. package/dist/checkpoint/bundler.d.ts.map +1 -0
  56. package/dist/checkpoint/bundler.js +45 -0
  57. package/dist/checkpoint/bundler.js.map +1 -0
  58. package/dist/checkpoint/cli.d.ts +2 -0
  59. package/dist/checkpoint/cli.d.ts.map +1 -0
  60. package/dist/checkpoint/cli.js +157 -0
  61. package/dist/checkpoint/cli.js.map +1 -0
  62. package/dist/checkpoint/entrypoint-generator.d.ts +17 -0
  63. package/dist/checkpoint/entrypoint-generator.d.ts.map +1 -0
  64. package/dist/checkpoint/entrypoint-generator.js +251 -0
  65. package/dist/checkpoint/entrypoint-generator.js.map +1 -0
  66. package/dist/checkpoint/forwarder.d.ts +6 -0
  67. package/dist/checkpoint/forwarder.d.ts.map +1 -0
  68. package/dist/checkpoint/forwarder.js +74 -0
  69. package/dist/checkpoint/forwarder.js.map +1 -0
  70. package/dist/checkpoint/gateway.d.ts +11 -0
  71. package/dist/checkpoint/gateway.d.ts.map +1 -0
  72. package/dist/checkpoint/gateway.js +30 -0
  73. package/dist/checkpoint/gateway.js.map +1 -0
  74. package/dist/checkpoint/ipc.d.ts +12 -0
  75. package/dist/checkpoint/ipc.d.ts.map +1 -0
  76. package/dist/checkpoint/ipc.js +96 -0
  77. package/dist/checkpoint/ipc.js.map +1 -0
  78. package/dist/checkpoint/manager.d.ts +20 -0
  79. package/dist/checkpoint/manager.d.ts.map +1 -0
  80. package/dist/checkpoint/manager.js +214 -0
  81. package/dist/checkpoint/manager.js.map +1 -0
  82. package/dist/checkpoint/process-manager.d.ts +35 -0
  83. package/dist/checkpoint/process-manager.d.ts.map +1 -0
  84. package/dist/checkpoint/process-manager.js +203 -0
  85. package/dist/checkpoint/process-manager.js.map +1 -0
  86. package/dist/checkpoint/resolver.d.ts +25 -0
  87. package/dist/checkpoint/resolver.d.ts.map +1 -0
  88. package/dist/checkpoint/resolver.js +95 -0
  89. package/dist/checkpoint/resolver.js.map +1 -0
  90. package/dist/checkpoint/socket-path.d.ts +2 -0
  91. package/dist/checkpoint/socket-path.d.ts.map +1 -0
  92. package/dist/checkpoint/socket-path.js +51 -0
  93. package/dist/checkpoint/socket-path.js.map +1 -0
  94. package/dist/checkpoint/types.d.ts +54 -0
  95. package/dist/checkpoint/types.d.ts.map +1 -0
  96. package/dist/checkpoint/types.js +2 -0
  97. package/dist/checkpoint/types.js.map +1 -0
  98. package/dist/cli/index.js +10 -2
  99. package/dist/cli/index.js.map +1 -1
  100. package/dist/cli/option-resolution.d.ts +1 -1
  101. package/dist/cli/option-resolution.d.ts.map +1 -1
  102. package/dist/cli/option-resolution.js.map +1 -1
  103. package/dist/cli.js +3709 -328
  104. package/dist/core/config-loader.d.ts +1 -0
  105. package/dist/core/config-loader.d.ts.map +1 -1
  106. package/dist/core/config-loader.js +10 -2
  107. package/dist/core/config-loader.js.map +1 -1
  108. package/dist/core/router.d.ts +24 -3
  109. package/dist/core/router.d.ts.map +1 -1
  110. package/dist/core/router.js +398 -249
  111. package/dist/core/router.js.map +1 -1
  112. package/dist/core/server.d.ts +2 -0
  113. package/dist/core/server.d.ts.map +1 -1
  114. package/dist/core/server.js +22 -8
  115. package/dist/core/server.js.map +1 -1
  116. package/dist/core/vector.d.ts +3 -0
  117. package/dist/core/vector.d.ts.map +1 -1
  118. package/dist/core/vector.js +51 -1
  119. package/dist/core/vector.js.map +1 -1
  120. package/dist/dev/route-scanner.d.ts.map +1 -1
  121. package/dist/dev/route-scanner.js +2 -1
  122. package/dist/dev/route-scanner.js.map +1 -1
  123. package/dist/http.d.ts +32 -7
  124. package/dist/http.d.ts.map +1 -1
  125. package/dist/http.js +144 -13
  126. package/dist/http.js.map +1 -1
  127. package/dist/index.cjs +1297 -74
  128. package/dist/index.d.ts +3 -2
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +2 -2
  131. package/dist/index.js.map +1 -1
  132. package/dist/index.mjs +1296 -73
  133. package/dist/middleware/manager.d.ts +3 -3
  134. package/dist/middleware/manager.d.ts.map +1 -1
  135. package/dist/middleware/manager.js +9 -8
  136. package/dist/middleware/manager.js.map +1 -1
  137. package/dist/openapi/docs-ui.d.ts.map +1 -1
  138. package/dist/openapi/docs-ui.js +1097 -61
  139. package/dist/openapi/docs-ui.js.map +1 -1
  140. package/dist/openapi/generator.d.ts +2 -1
  141. package/dist/openapi/generator.d.ts.map +1 -1
  142. package/dist/openapi/generator.js +240 -7
  143. package/dist/openapi/generator.js.map +1 -1
  144. package/dist/types/index.d.ts +71 -28
  145. package/dist/types/index.d.ts.map +1 -1
  146. package/dist/types/index.js +24 -1
  147. package/dist/types/index.js.map +1 -1
  148. package/dist/utils/validation.d.ts.map +1 -1
  149. package/dist/utils/validation.js +3 -2
  150. package/dist/utils/validation.js.map +1 -1
  151. package/package.json +2 -1
  152. package/src/auth/protected.ts +11 -8
  153. package/src/cache/manager.ts +23 -4
  154. package/src/checkpoint/artifacts/compressor.ts +30 -0
  155. package/src/checkpoint/artifacts/decompress-worker.ts +49 -0
  156. package/src/checkpoint/artifacts/hasher.ts +6 -0
  157. package/src/checkpoint/artifacts/manifest.ts +72 -0
  158. package/src/checkpoint/artifacts/materializer.ts +211 -0
  159. package/src/checkpoint/artifacts/packager.ts +100 -0
  160. package/src/checkpoint/artifacts/repository.ts +36 -0
  161. package/src/checkpoint/artifacts/store.ts +102 -0
  162. package/src/checkpoint/artifacts/types.ts +24 -0
  163. package/src/checkpoint/artifacts/worker-decompressor.ts +192 -0
  164. package/src/checkpoint/asset-store.ts +61 -0
  165. package/src/checkpoint/bundler.ts +64 -0
  166. package/src/checkpoint/cli.ts +177 -0
  167. package/src/checkpoint/entrypoint-generator.ts +275 -0
  168. package/src/checkpoint/forwarder.ts +84 -0
  169. package/src/checkpoint/gateway.ts +40 -0
  170. package/src/checkpoint/ipc.ts +107 -0
  171. package/src/checkpoint/manager.ts +254 -0
  172. package/src/checkpoint/process-manager.ts +250 -0
  173. package/src/checkpoint/resolver.ts +124 -0
  174. package/src/checkpoint/socket-path.ts +61 -0
  175. package/src/checkpoint/types.ts +63 -0
  176. package/src/cli/index.ts +11 -2
  177. package/src/cli/option-resolution.ts +5 -1
  178. package/src/core/config-loader.ts +11 -2
  179. package/src/core/router.ts +505 -264
  180. package/src/core/server.ts +36 -9
  181. package/src/core/vector.ts +60 -1
  182. package/src/dev/route-scanner.ts +2 -1
  183. package/src/http.ts +219 -19
  184. package/src/index.ts +3 -2
  185. package/src/middleware/manager.ts +10 -10
  186. package/src/openapi/docs-ui.ts +1097 -61
  187. package/src/openapi/generator.ts +265 -6
  188. package/src/types/index.ts +83 -30
  189. package/src/utils/validation.ts +5 -3
package/dist/cli.js CHANGED
@@ -1,9 +1,1807 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
13
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
+
15
+ // src/checkpoint/types.ts
16
+ var CHECKPOINT_FORMAT_VERSION = 1;
17
+
18
+ // src/checkpoint/artifacts/compressor.ts
19
+ function compressBytes(input, codec = DEFAULT_ASSET_CODEC) {
20
+ const normalized = new Uint8Array(input);
21
+ switch (codec) {
22
+ case "none":
23
+ return normalized;
24
+ case "gzip":
25
+ return Bun.gzipSync(normalized);
26
+ default:
27
+ throw new Error(`Unsupported compression codec: ${codec}`);
28
+ }
29
+ }
30
+ var DEFAULT_ASSET_CODEC = "gzip";
31
+
32
+ // src/checkpoint/artifacts/hasher.ts
33
+ function sha256Hex(content) {
34
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
35
+ const hashBuffer = Bun.SHA256.hash(bytes);
36
+ const hashBytes = new Uint8Array(hashBuffer.buffer, hashBuffer.byteOffset, hashBuffer.byteLength);
37
+ return Buffer.from(hashBytes).toString("hex");
38
+ }
39
+
40
+ // src/checkpoint/artifacts/manifest.ts
41
+ import { basename, isAbsolute as isAbsolute2, relative as relative3 } from "path";
42
+ function normalizeLogicalPath(input) {
43
+ const normalizedInput = normalizeSlashes(input).trim();
44
+ const rel = isAbsolutePathPortable(normalizedInput) ? toPortableRelativePath(normalizedInput) : normalizedInput;
45
+ const cleaned = rel.split("/").filter((segment) => segment.length > 0 && segment !== "." && segment !== "..").join("/");
46
+ const sanitized = cleaned.replace(UNSAFE_SEGMENT_PATTERN, "_").replace(/^\/+/, "");
47
+ if (sanitized.length > 0) {
48
+ return sanitized;
49
+ }
50
+ const fallback = basename(normalizedInput).replace(UNSAFE_SEGMENT_PATTERN, "_");
51
+ return fallback.length > 0 ? `external/${fallback}` : "external/asset.bin";
52
+ }
53
+ function normalizeRelativePath(input) {
54
+ return normalizeSlashes(input).replace(/^\/+/, "");
55
+ }
56
+ function isAbsolutePathPortable(input) {
57
+ const normalized = normalizeSlashes(input).trim();
58
+ return isAbsolute2(normalized) || WINDOWS_DRIVE_ABS_PATTERN.test(normalized) || WINDOWS_UNC_ABS_PATTERN.test(input);
59
+ }
60
+ function computeAssetFingerprint(assets) {
61
+ const stable = assets.map((asset) => ({
62
+ type: asset.type,
63
+ logicalPath: asset.logicalPath,
64
+ contentHash: asset.contentHash ?? asset.hash,
65
+ blobHash: asset.blobHash ?? "",
66
+ blobPath: asset.blobPath ?? asset.storedPath,
67
+ codec: asset.codec ?? "none",
68
+ size: asset.contentSize ?? asset.size
69
+ })).sort((a, b) => `${a.type}:${a.logicalPath}:${a.contentHash}:${a.blobHash}`.localeCompare(`${b.type}:${b.logicalPath}:${b.contentHash}:${b.blobHash}`));
70
+ return JSON.stringify(stable);
71
+ }
72
+ function normalizeSlashes(value) {
73
+ return value.replace(/\\/g, "/");
74
+ }
75
+ function toPortableRelativePath(input) {
76
+ const normalized = normalizeSlashes(input);
77
+ if (WINDOWS_DRIVE_ABS_PATTERN.test(normalized)) {
78
+ return normalized.replace(/^[a-zA-Z]:/, "").replace(/^\/+/, "");
79
+ }
80
+ if (WINDOWS_UNC_ABS_PATTERN.test(input)) {
81
+ return normalizeSlashes(input).replace(/^\\\\[^\\]+\\[^\\]+/, "").replace(/^\/+/, "");
82
+ }
83
+ return normalizeSlashes(relative3(process.cwd(), normalized));
84
+ }
85
+ var UNSAFE_SEGMENT_PATTERN, WINDOWS_DRIVE_ABS_PATTERN, WINDOWS_UNC_ABS_PATTERN;
86
+ var init_manifest = __esm(() => {
87
+ UNSAFE_SEGMENT_PATTERN = /[^a-zA-Z0-9._/-]/g;
88
+ WINDOWS_DRIVE_ABS_PATTERN = /^[a-zA-Z]:[\\/]/;
89
+ WINDOWS_UNC_ABS_PATTERN = /^\\\\[^\\]+\\[^\\]+/;
90
+ });
91
+
92
+ // src/checkpoint/artifacts/store.ts
93
+ import { existsSync as existsSync4, promises as fs3 } from "fs";
94
+ import { join as join3 } from "path";
95
+
96
+ class CheckpointArtifactStore {
97
+ storageDir;
98
+ codec;
99
+ constructor(storageDir, options = {}) {
100
+ this.storageDir = storageDir;
101
+ this.codec = options.assetCodec ?? DEFAULT_ASSET_CODEC;
102
+ }
103
+ async addEmbedded(logicalPath, sourcePath) {
104
+ return this.addAsset("embedded", logicalPath, sourcePath);
105
+ }
106
+ async addSidecar(logicalPath, sourcePath) {
107
+ return this.addAsset("sidecar", logicalPath, sourcePath);
108
+ }
109
+ async collect(embeddedPaths, sidecarPaths) {
110
+ const records = [];
111
+ for (const sourcePath of embeddedPaths) {
112
+ records.push(await this.addEmbedded(sourcePath, sourcePath));
113
+ }
114
+ for (const sourcePath of sidecarPaths) {
115
+ records.push(await this.addSidecar(sourcePath, sourcePath));
116
+ }
117
+ return records;
118
+ }
119
+ async addAsset(type, logicalPath, sourcePath) {
120
+ const content = await fs3.readFile(sourcePath);
121
+ const contentBytes = new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
122
+ const contentHash = sha256Hex(contentBytes);
123
+ const compressed = compressBytes(contentBytes, this.codec);
124
+ const blobHash = sha256Hex(compressed);
125
+ const blobPath = normalizeRelativePath(join3(BLOB_DIR, `${blobHash}${this.codec === "gzip" ? ".gz" : ""}`));
126
+ const storedPath = join3(this.storageDir, blobPath);
127
+ await fs3.mkdir(join3(this.storageDir, BLOB_DIR), { recursive: true });
128
+ if (!existsSync4(storedPath)) {
129
+ await this.writeAtomically(storedPath, compressed);
130
+ } else {
131
+ const existing = await fs3.readFile(storedPath);
132
+ const existingBytes = new Uint8Array(existing.buffer, existing.byteOffset, existing.byteLength);
133
+ if (sha256Hex(existingBytes) !== blobHash) {
134
+ await this.writeAtomically(storedPath, compressed);
135
+ }
136
+ }
137
+ return {
138
+ type,
139
+ logicalPath: normalizeLogicalPath(logicalPath),
140
+ storedPath,
141
+ hash: contentHash,
142
+ size: content.byteLength,
143
+ contentHash,
144
+ contentSize: content.byteLength,
145
+ blobHash,
146
+ blobSize: compressed.byteLength,
147
+ blobPath,
148
+ codec: this.codec
149
+ };
150
+ }
151
+ async writeAtomically(path, bytes) {
152
+ const tempPath = `${path}.tmp.${process.pid}.${Date.now()}`;
153
+ await fs3.writeFile(tempPath, bytes);
154
+ try {
155
+ await fs3.rename(tempPath, path);
156
+ } catch (error) {
157
+ if (!isAlreadyExists(error)) {
158
+ throw error;
159
+ }
160
+ await fs3.rm(path, { force: true });
161
+ await fs3.rename(tempPath, path);
162
+ }
163
+ }
164
+ }
165
+ function isAlreadyExists(error) {
166
+ if (typeof error !== "object" || error === null || !("code" in error)) {
167
+ return false;
168
+ }
169
+ const code = error.code;
170
+ return code === "EEXIST" || code === "EPERM";
171
+ }
172
+ var BLOB_DIR = "_assets/blobs";
173
+ var init_store = __esm(() => {
174
+ init_manifest();
175
+ });
176
+
177
+ // src/checkpoint/asset-store.ts
178
+ import { promises as fs4 } from "fs";
179
+
180
+ class AssetStore {
181
+ artifactStore;
182
+ constructor(storageDir) {
183
+ this.artifactStore = new CheckpointArtifactStore(storageDir);
184
+ }
185
+ async addEmbedded(logicalPath, sourcePath) {
186
+ const content = await fs4.readFile(sourcePath);
187
+ if (content.byteLength > EMBEDDED_PER_FILE_BUDGET) {
188
+ throw new Error(`Embedded asset "${logicalPath}" is ${formatBytes(content.byteLength)} \u2014 exceeds ${formatBytes(EMBEDDED_PER_FILE_BUDGET)} per-file budget. Use sidecar instead.`);
189
+ }
190
+ return await this.artifactStore.addEmbedded(logicalPath, sourcePath);
191
+ }
192
+ async addSidecar(logicalPath, sourcePath) {
193
+ return await this.artifactStore.addSidecar(logicalPath, sourcePath);
194
+ }
195
+ async collect(embeddedPaths, sidecarPaths) {
196
+ const records = [];
197
+ for (const p of embeddedPaths) {
198
+ records.push(await this.addEmbedded(p, p));
199
+ }
200
+ for (const p of sidecarPaths) {
201
+ records.push(await this.addSidecar(p, p));
202
+ }
203
+ return records;
204
+ }
205
+ validateBudgets(records) {
206
+ const embeddedTotal = records.filter((r) => r.type === "embedded").reduce((acc, r) => acc + (r.contentSize ?? r.size), 0);
207
+ if (embeddedTotal > EMBEDDED_TOTAL_BUDGET) {
208
+ throw new Error(`Total embedded asset size ${formatBytes(embeddedTotal)} exceeds ${formatBytes(EMBEDDED_TOTAL_BUDGET)} budget.`);
209
+ }
210
+ }
211
+ }
212
+ function formatBytes(bytes) {
213
+ if (bytes < 1024)
214
+ return `${bytes} B`;
215
+ if (bytes < 1024 * 1024)
216
+ return `${(bytes / 1024).toFixed(1)} KB`;
217
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
218
+ }
219
+ var EMBEDDED_PER_FILE_BUDGET, EMBEDDED_TOTAL_BUDGET;
220
+ var init_asset_store = __esm(() => {
221
+ init_store();
222
+ EMBEDDED_PER_FILE_BUDGET = 64 * 1024;
223
+ EMBEDDED_TOTAL_BUDGET = 512 * 1024;
224
+ });
225
+
226
+ // src/checkpoint/bundler.ts
227
+ import { existsSync as existsSync5 } from "fs";
228
+ import { join as join4 } from "path";
229
+
230
+ class CheckpointBundler {
231
+ async bundle(options) {
232
+ const outfile = options.outputFile ?? "checkpoint.js";
233
+ const result = await Bun.build({
234
+ entrypoints: [options.entrypointPath],
235
+ outdir: options.outputDir,
236
+ target: "bun",
237
+ format: "esm",
238
+ minify: true,
239
+ naming: { entry: outfile }
240
+ });
241
+ if (!result.success) {
242
+ const messages = result.logs.map((l) => l.message ?? String(l)).join(`
243
+ `);
244
+ throw new Error(`Checkpoint bundle failed:
245
+ ${messages}`);
246
+ }
247
+ const outputPath = join4(options.outputDir, outfile);
248
+ if (!existsSync5(outputPath)) {
249
+ const actualOutput = result.outputs.find((o) => o.kind === "entry-point");
250
+ if (actualOutput) {
251
+ const actualPath = actualOutput.path;
252
+ if (existsSync5(actualPath)) {
253
+ return this.hashFile(actualPath);
254
+ }
255
+ }
256
+ throw new Error(`Bundle output not found at expected path: ${outputPath}`);
257
+ }
258
+ return this.hashFile(outputPath);
259
+ }
260
+ async hashFile(path) {
261
+ const file = Bun.file(path);
262
+ const content = await file.arrayBuffer();
263
+ const hashBuffer = Bun.SHA256.hash(new Uint8Array(content));
264
+ const hashBytes = new Uint8Array(hashBuffer.buffer, hashBuffer.byteOffset, hashBuffer.byteLength);
265
+ const hash = Buffer.from(hashBytes).toString("hex");
266
+ return {
267
+ outputPath: path,
268
+ hash,
269
+ size: content.byteLength
270
+ };
271
+ }
272
+ }
273
+ var init_bundler = () => {};
274
+
275
+ // src/checkpoint/entrypoint-generator.ts
276
+ import { existsSync as existsSync6, promises as fs5 } from "fs";
277
+ import { join as join5, relative as relative4, sep as sep2 } from "path";
278
+
279
+ class CheckpointEntrypointGenerator {
280
+ discoveredRoutes = [];
281
+ async generate(options) {
282
+ const routeFiles = await this.scanRouteFiles(options.routesDir);
283
+ const source = this.buildSource(routeFiles, options);
284
+ const outputPath = join5(options.outputDir, "entrypoint.ts");
285
+ await fs5.writeFile(outputPath, source, "utf-8");
286
+ return outputPath;
287
+ }
288
+ getDiscoveredRoutes() {
289
+ return this.discoveredRoutes;
290
+ }
291
+ async scanRouteFiles(routesDir) {
292
+ if (!existsSync6(routesDir)) {
293
+ return [];
294
+ }
295
+ const files = [];
296
+ await this.walkDir(routesDir, files);
297
+ return files;
298
+ }
299
+ async walkDir(dir, files) {
300
+ const entries = await fs5.readdir(dir);
301
+ for (const entry of entries) {
302
+ const fullPath = join5(dir, entry);
303
+ const stats = await fs5.stat(fullPath);
304
+ if (stats.isDirectory()) {
305
+ await this.walkDir(fullPath, files);
306
+ } else if ((entry.endsWith(".ts") || entry.endsWith(".js")) && !entry.endsWith(".test.ts") && !entry.endsWith(".test.js") && !entry.endsWith(".spec.ts") && !entry.endsWith(".spec.js") && !entry.endsWith(".d.ts")) {
307
+ files.push(fullPath);
308
+ }
309
+ }
310
+ }
311
+ buildSource(routeFiles, options) {
312
+ this.discoveredRoutes = [];
313
+ const imports = [];
314
+ const registrations = [];
315
+ for (const [i, file] of routeFiles.entries()) {
316
+ const varName = `routeModule_${i}`;
317
+ const importSpecifier = this.toImportSpecifier(file, options.outputDir);
318
+ imports.push(`import * as ${varName} from ${JSON.stringify(importSpecifier)};`);
319
+ registrations.push(` registerModule(${varName});`);
320
+ const routePath = relative4(options.routesDir, file).replace(/\.(ts|js)$/, "").split(sep2).join("/");
321
+ this.discoveredRoutes.push({
322
+ method: "*",
323
+ path: `/${routePath}`
324
+ });
325
+ }
326
+ return `// Checkpoint entrypoint \u2014 auto-generated for version ${options.version}
327
+ // DO NOT EDIT: This file is generated by Vector checkpoint publish.
328
+
329
+ ${imports.join(`
330
+ `)}
331
+
332
+ const socketPath = process.env.VECTOR_CHECKPOINT_SOCKET ?? '${options.socketPath}';
333
+ const checkpointContextHeader = 'x-vector-checkpoint-context';
334
+
335
+ // Route registration helper
336
+ type RouteDefinition = {
337
+ entry: { method: string; path: string };
338
+ options: { method: string; path: string; [key: string]: any };
339
+ handler: (ctx: any) => any;
340
+ };
341
+
342
+ function isRouteDefinition(value: unknown): value is RouteDefinition {
343
+ return (
344
+ value !== null &&
345
+ typeof value === 'object' &&
346
+ 'entry' in value &&
347
+ 'options' in value &&
348
+ 'handler' in value &&
349
+ typeof (value as any).handler === 'function'
350
+ );
351
+ }
352
+
353
+ const routeTable: Record<string, Record<string, (req: Request) => Response | Promise<Response>>> = {};
354
+
355
+ function parseCheckpointContext(req: Request): Record<string, unknown> {
356
+ const encoded = req.headers.get(checkpointContextHeader);
357
+ if (!encoded) {
358
+ return {};
359
+ }
360
+
361
+ try {
362
+ const json = Buffer.from(encoded, 'base64url').toString('utf-8');
363
+ const parsed = JSON.parse(json);
364
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
365
+ return {};
366
+ }
367
+ return parsed as Record<string, unknown>;
368
+ } catch {
369
+ return {};
370
+ }
371
+ }
372
+
373
+ function parseQuery(url: URL): Record<string, string | string[]> {
374
+ const query: Record<string, string | string[]> = {};
375
+ for (const [key, value] of url.searchParams) {
376
+ if (key in query) {
377
+ const existing = query[key];
378
+ if (Array.isArray(existing)) {
379
+ existing.push(value);
380
+ } else {
381
+ query[key] = [existing as string, value];
382
+ }
383
+ } else {
384
+ query[key] = value;
385
+ }
386
+ }
387
+ return query;
388
+ }
389
+
390
+ function parseCookies(cookieHeader: string | null): Record<string, string> {
391
+ const cookies: Record<string, string> = {};
392
+ if (!cookieHeader) {
393
+ return cookies;
394
+ }
395
+ for (const pair of cookieHeader.split(';')) {
396
+ const idx = pair.indexOf('=');
397
+ if (idx > 0) {
398
+ cookies[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
399
+ }
400
+ }
401
+ return cookies;
402
+ }
403
+
404
+ function extractRouteParams(req: Request): Record<string, string> {
405
+ const nativeParams = (req as Request & { params?: Record<string, string> }).params;
406
+ if (nativeParams && typeof nativeParams === 'object' && !Array.isArray(nativeParams)) {
407
+ return nativeParams;
408
+ }
409
+ return {};
410
+ }
411
+
412
+ function createContext(req: Request): Record<string, unknown> {
413
+ const ctx: Record<string, unknown> = Object.create(null);
414
+ setContextField(ctx, 'request', req);
415
+
416
+ // Initialize always-present VectorContext fields with defaults derived from the request
417
+ setContextField(ctx, 'params', extractRouteParams(req));
418
+
419
+ try {
420
+ setContextField(ctx, 'query', parseQuery(new URL(req.url)));
421
+ } catch {
422
+ setContextField(ctx, 'query', {});
423
+ }
424
+
425
+ setContextField(ctx, 'cookies', parseCookies(req.headers.get('cookie')));
426
+ setContextField(ctx, 'metadata', {});
427
+
428
+ const checkpointContext = parseCheckpointContext(req);
429
+ const allowedCheckpointKeys = ['metadata', 'content', 'validatedInput', 'authUser'] as const;
430
+ for (const key of allowedCheckpointKeys) {
431
+ if (!Object.prototype.hasOwnProperty.call(checkpointContext, key)) {
432
+ continue;
433
+ }
434
+ const value = checkpointContext[key];
435
+ setContextField(ctx, key, value);
436
+ }
437
+
438
+ return ctx;
439
+ }
440
+
441
+ function setContextField(target: Record<string, unknown>, key: string, value: unknown) {
442
+ const ownDescriptor = Object.getOwnPropertyDescriptor(target as object, key);
443
+ if (ownDescriptor && ownDescriptor.writable === false && typeof ownDescriptor.set !== 'function') {
444
+ return;
445
+ }
446
+
447
+ try {
448
+ target[key] = value;
449
+ return;
450
+ } catch {
451
+ // Fall back to own-property define when inherited Request field is readonly.
452
+ }
453
+
454
+ try {
455
+ Object.defineProperty(target, key, {
456
+ value,
457
+ writable: true,
458
+ configurable: true,
459
+ enumerable: true,
460
+ });
461
+ } catch {
462
+ // Ignore non-extensible context edge cases.
463
+ }
464
+ }
465
+
466
+ function addToRouteTable(method: string, path: string, handler: (ctx: any) => any) {
467
+ if (!routeTable[path]) {
468
+ routeTable[path] = Object.create(null);
469
+ }
470
+ routeTable[path][method.toUpperCase()] = async (req: Request) => {
471
+ const ctx = createContext(req);
472
+ const result = await handler(ctx);
473
+ if (result instanceof Response) return result;
474
+ return Response.json(result);
475
+ };
476
+ }
477
+
478
+ function registerModule(mod: Record<string, unknown>) {
479
+ for (const [name, value] of Object.entries(mod)) {
480
+ if (isRouteDefinition(value)) {
481
+ addToRouteTable(value.options.method, value.options.path, value.handler);
482
+ } else if (name === 'default' && typeof value === 'function') {
483
+ // Default export functions need path context \u2014 skip for now
484
+ }
485
+ }
486
+ }
487
+
488
+ ${registrations.join(`
489
+ `)}
490
+
491
+ // Health check endpoint for parent process
492
+ routeTable['/_vector/health'] = {
493
+ GET: () => Response.json({ status: 'ok', version: '${options.version}' }),
494
+ };
495
+
496
+ const server = Bun.serve({
497
+ unix: socketPath,
498
+ routes: routeTable,
499
+ fetch(req: Request) {
500
+ return Response.json(
501
+ { error: true, message: 'Not Found', statusCode: 404 },
502
+ { status: 404 }
503
+ );
504
+ },
505
+ });
506
+
507
+ // Signal readiness to parent process
508
+ process.stdout.write('READY\\n');
509
+ `;
510
+ }
511
+ toImportSpecifier(filePath, outputDir) {
512
+ const normalized = relative4(outputDir, filePath).split(sep2).join("/");
513
+ if (normalized.startsWith(".") || normalized.startsWith("/")) {
514
+ return normalized;
515
+ }
516
+ return `./${normalized}`;
517
+ }
518
+ }
519
+ var init_entrypoint_generator = () => {};
520
+
521
+ // src/checkpoint/artifacts/packager.ts
522
+ import { promises as fs6 } from "fs";
523
+ import { join as join6, relative as relative5 } from "path";
524
+
525
+ class CheckpointPackager {
526
+ storageDir;
527
+ codec;
528
+ constructor(storageDir, codec = "gzip") {
529
+ this.storageDir = storageDir;
530
+ this.codec = codec;
531
+ }
532
+ async packageVersion(version) {
533
+ const versionDir = join6(this.storageDir, version);
534
+ const archiveRelPath = join6(ARCHIVE_DIR, `${version}${this.archiveSuffix()}`).replace(/\\/g, "/");
535
+ const archivePath = join6(this.storageDir, archiveRelPath);
536
+ await fs6.mkdir(join6(this.storageDir, ARCHIVE_DIR), { recursive: true });
537
+ const files = await collectFiles(versionDir);
538
+ const archiveBytes = await this.buildArchiveBytes(versionDir, archivePath, files);
539
+ return {
540
+ archivePath: archiveRelPath,
541
+ archiveHash: sha256Hex(archiveBytes),
542
+ archiveSize: archiveBytes.byteLength,
543
+ codec: this.codec
544
+ };
545
+ }
546
+ async buildArchiveBytes(versionDir, archivePath, files) {
547
+ const ArchiveCtor = Bun.Archive;
548
+ if (typeof ArchiveCtor === "function") {
549
+ const archiveEntries = Object.fromEntries(files.map((filePath) => {
550
+ const rel = relative5(versionDir, filePath).replace(/\\/g, "/");
551
+ return [rel, Bun.file(filePath)];
552
+ }));
553
+ const archive = new ArchiveCtor(archiveEntries);
554
+ const tarBytes = new Uint8Array(await archive.bytes());
555
+ const archiveBytes = this.codec === "gzip" ? Bun.gzipSync(tarBytes) : tarBytes;
556
+ await Bun.write(archivePath, archiveBytes);
557
+ return archiveBytes;
558
+ }
559
+ await this.buildArchiveWithTar(versionDir, archivePath, files);
560
+ const bytes = await fs6.readFile(archivePath);
561
+ return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
562
+ }
563
+ async buildArchiveWithTar(versionDir, archivePath, files) {
564
+ const relFiles = files.map((filePath) => relative5(versionDir, filePath));
565
+ if (relFiles.length === 0) {
566
+ throw new Error(`Cannot package checkpoint: no files found in "${versionDir}"`);
567
+ }
568
+ const tarArgs = this.codec === "gzip" ? ["-czf", archivePath] : ["-cf", archivePath];
569
+ const proc = Bun.spawn(["tar", ...tarArgs, "-C", versionDir, ...relFiles], {
570
+ stdout: "pipe",
571
+ stderr: "pipe"
572
+ });
573
+ const exitCode = await proc.exited;
574
+ if (exitCode === 0) {
575
+ return;
576
+ }
577
+ const stderr = await new Response(proc.stderr).text();
578
+ throw new Error(`Failed to package checkpoint archive with tar (exit ${exitCode}): ${stderr.trim()}`);
579
+ }
580
+ archiveSuffix() {
581
+ return this.codec === "gzip" ? ".tar.gz" : ".tar";
582
+ }
583
+ }
584
+ async function collectFiles(root) {
585
+ const files = [];
586
+ await walk(root, files);
587
+ return files.filter((filePath) => relative5(root, filePath).replace(/\\/g, "/") !== "manifest.json");
588
+ }
589
+ async function walk(dir, files) {
590
+ const entries = await fs6.readdir(dir, { withFileTypes: true });
591
+ for (const entry of entries) {
592
+ const fullPath = join6(dir, entry.name);
593
+ if (entry.isDirectory()) {
594
+ await walk(fullPath, files);
595
+ continue;
596
+ }
597
+ if (entry.isFile()) {
598
+ files.push(fullPath);
599
+ }
600
+ }
601
+ }
602
+ var ARCHIVE_DIR = "_archives";
603
+ var init_packager = () => {};
604
+
605
+ // src/checkpoint/socket-path.ts
606
+ import { createHash } from "crypto";
607
+ import { tmpdir } from "os";
608
+ import { isAbsolute as isAbsolute3, join as join7, resolve as resolve3 } from "path";
609
+ function toAbsolute(dir) {
610
+ return isAbsolute3(dir) ? dir : resolve3(dir);
611
+ }
612
+ function unixSocketWithinLimit(path) {
613
+ return Buffer.byteLength(path, "utf8") <= UNIX_SOCKET_PATH_MAX_BYTES;
614
+ }
615
+ function socketFileName(storageDir, version) {
616
+ const versionLabel = version.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-+|-+$)/g, "").slice(0, 12);
617
+ const digest = createHash("sha256").update(storageDir).update("\x00").update(version).digest("hex").slice(0, 16);
618
+ const label = versionLabel.length > 0 ? `${versionLabel}-` : "";
619
+ return `vector-${label}${digest}.sock`;
620
+ }
621
+ function candidateSocketRoots() {
622
+ const roots = new Set;
623
+ const override = process.env.VECTOR_CHECKPOINT_SOCKET_DIR?.trim();
624
+ if (override) {
625
+ roots.add(toAbsolute(override));
626
+ }
627
+ if (process.platform !== "win32") {
628
+ roots.add("/tmp");
629
+ roots.add("/private/tmp");
630
+ }
631
+ roots.add(toAbsolute(tmpdir()));
632
+ return [...roots];
633
+ }
634
+ function resolveCheckpointSocketPath(storageDir, version) {
635
+ const defaultPath = join7(storageDir, version, CHECKPOINT_SOCKET_FILE);
636
+ if (process.platform === "win32" || unixSocketWithinLimit(defaultPath)) {
637
+ return defaultPath;
638
+ }
639
+ const fileName = socketFileName(storageDir, version);
640
+ for (const root of candidateSocketRoots()) {
641
+ const candidate = join7(root, fileName);
642
+ if (unixSocketWithinLimit(candidate)) {
643
+ return candidate;
644
+ }
645
+ }
646
+ return join7("/tmp", fileName);
647
+ }
648
+ var CHECKPOINT_SOCKET_FILE = "run.sock", UNIX_SOCKET_PATH_MAX_BYTES = 103;
649
+ var init_socket_path = () => {};
650
+
651
+ // src/checkpoint/manager.ts
652
+ var exports_manager = {};
653
+ __export(exports_manager, {
654
+ CheckpointManager: () => CheckpointManager
655
+ });
656
+ import { existsSync as existsSync7, promises as fs7 } from "fs";
657
+ import { join as join8, resolve as resolve4 } from "path";
658
+ function inferLegacyAssetCodec(asset) {
659
+ if (asset.codec) {
660
+ return asset.codec;
661
+ }
662
+ const rawPath = (asset.blobPath ?? asset.storedPath ?? "").trim().toLowerCase();
663
+ return rawPath.endsWith(".gz") ? "gzip" : "none";
664
+ }
665
+
666
+ class CheckpointManager {
667
+ storageDir;
668
+ maxCheckpoints;
669
+ constructor(config = {}) {
670
+ this.storageDir = resolve4(process.cwd(), config.storageDir ?? DEFAULT_STORAGE_DIR);
671
+ this.maxCheckpoints = config.maxCheckpoints ?? 10;
672
+ }
673
+ getStorageDir() {
674
+ return this.storageDir;
675
+ }
676
+ versionDir(version) {
677
+ return join8(this.storageDir, version);
678
+ }
679
+ socketPath(version) {
680
+ return resolveCheckpointSocketPath(this.storageDir, version);
681
+ }
682
+ async ensureStorageDir() {
683
+ await fs7.mkdir(this.storageDir, { recursive: true });
684
+ }
685
+ async publish(options) {
686
+ await this.ensureStorageDir();
687
+ const versionDir = this.versionDir(options.version);
688
+ await fs7.mkdir(versionDir, { recursive: true });
689
+ const generator = new CheckpointEntrypointGenerator;
690
+ const entrypointPath = await generator.generate({
691
+ version: options.version,
692
+ outputDir: versionDir,
693
+ routesDir: resolve4(process.cwd(), options.routesDir),
694
+ socketPath: this.socketPath(options.version)
695
+ });
696
+ const bundler = new CheckpointBundler;
697
+ const bundleResult = await bundler.bundle({
698
+ entrypointPath,
699
+ outputDir: versionDir
700
+ });
701
+ const assetStore = new AssetStore(this.storageDir);
702
+ const assets = await assetStore.collect(options.embeddedAssetPaths ?? [], options.sidecarAssetPaths ?? []);
703
+ assetStore.validateBudgets(assets);
704
+ const manifest = {
705
+ formatVersion: CHECKPOINT_FORMAT_VERSION,
706
+ version: options.version,
707
+ createdAt: new Date().toISOString(),
708
+ entrypoint: "checkpoint.js",
709
+ routes: generator.getDiscoveredRoutes(),
710
+ assets,
711
+ bundleHash: bundleResult.hash,
712
+ bundleSize: bundleResult.size,
713
+ checkpointArchivePath: undefined,
714
+ checkpointArchiveHash: undefined,
715
+ checkpointArchiveSize: undefined,
716
+ checkpointArchiveCodec: undefined
717
+ };
718
+ await this.writeManifest(options.version, manifest);
719
+ try {
720
+ await fs7.unlink(entrypointPath);
721
+ } catch {}
722
+ const packager = new CheckpointPackager(this.storageDir);
723
+ const archive = await packager.packageVersion(options.version);
724
+ manifest.checkpointArchivePath = archive.archivePath;
725
+ manifest.checkpointArchiveHash = archive.archiveHash;
726
+ manifest.checkpointArchiveSize = archive.archiveSize;
727
+ manifest.checkpointArchiveCodec = archive.codec;
728
+ await this.writeManifest(options.version, manifest);
729
+ await this.pruneOld();
730
+ return manifest;
731
+ }
732
+ async readManifest(version) {
733
+ const manifestPath = join8(this.versionDir(version), "manifest.json");
734
+ const content = await fs7.readFile(manifestPath, "utf-8");
735
+ return this.normalizeManifest(JSON.parse(content));
736
+ }
737
+ async writeManifest(version, manifest) {
738
+ const manifestPath = join8(this.versionDir(version), "manifest.json");
739
+ await fs7.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
740
+ }
741
+ async listVersions() {
742
+ if (!existsSync7(this.storageDir)) {
743
+ return [];
744
+ }
745
+ const entries = await fs7.readdir(this.storageDir);
746
+ const manifests = [];
747
+ for (const entry of entries) {
748
+ const manifestPath = join8(this.storageDir, entry, "manifest.json");
749
+ if (existsSync7(manifestPath)) {
750
+ try {
751
+ const content = await fs7.readFile(manifestPath, "utf-8");
752
+ manifests.push(this.normalizeManifest(JSON.parse(content)));
753
+ } catch {}
754
+ }
755
+ }
756
+ manifests.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
757
+ return manifests;
758
+ }
759
+ async getActive() {
760
+ const pointerPath = join8(this.storageDir, ACTIVE_POINTER_FILE);
761
+ if (!existsSync7(pointerPath)) {
762
+ return null;
763
+ }
764
+ try {
765
+ const content = await fs7.readFile(pointerPath, "utf-8");
766
+ return JSON.parse(content);
767
+ } catch {
768
+ return null;
769
+ }
770
+ }
771
+ async setActive(version) {
772
+ const versionDir = this.versionDir(version);
773
+ if (!existsSync7(versionDir)) {
774
+ throw new Error(`Checkpoint version ${version} does not exist`);
775
+ }
776
+ const manifestPath = join8(versionDir, "manifest.json");
777
+ if (!existsSync7(manifestPath)) {
778
+ throw new Error(`Checkpoint version ${version} has no manifest`);
779
+ }
780
+ await this.ensureStorageDir();
781
+ const pointer = {
782
+ version,
783
+ activatedAt: new Date().toISOString()
784
+ };
785
+ const pointerPath = join8(this.storageDir, ACTIVE_POINTER_FILE);
786
+ await fs7.writeFile(pointerPath, JSON.stringify(pointer, null, 2), "utf-8");
787
+ }
788
+ async remove(version) {
789
+ const active = await this.getActive();
790
+ if (active?.version === version) {
791
+ throw new Error(`Cannot remove active checkpoint version ${version}. Rollback to a different version first.`);
792
+ }
793
+ const versionDir = this.versionDir(version);
794
+ if (!existsSync7(versionDir)) {
795
+ throw new Error(`Checkpoint version ${version} does not exist`);
796
+ }
797
+ await fs7.rm(versionDir, { recursive: true, force: true });
798
+ }
799
+ async pruneOld() {
800
+ if (this.maxCheckpoints <= 0)
801
+ return;
802
+ const manifests = await this.listVersions();
803
+ if (manifests.length <= this.maxCheckpoints)
804
+ return;
805
+ const active = await this.getActive();
806
+ const toRemove = manifests.slice(this.maxCheckpoints);
807
+ for (const manifest of toRemove) {
808
+ if (active?.version === manifest.version)
809
+ continue;
810
+ try {
811
+ const versionDir = this.versionDir(manifest.version);
812
+ await fs7.rm(versionDir, { recursive: true, force: true });
813
+ } catch {}
814
+ }
815
+ }
816
+ normalizeManifest(manifest) {
817
+ const normalizedAssets = (manifest.assets ?? []).map((asset) => ({
818
+ ...asset,
819
+ contentHash: asset.contentHash ?? asset.hash,
820
+ contentSize: asset.contentSize ?? asset.size,
821
+ blobPath: asset.blobPath ?? asset.storedPath,
822
+ blobHash: asset.blobHash,
823
+ blobSize: asset.blobSize,
824
+ codec: inferLegacyAssetCodec(asset)
825
+ }));
826
+ return {
827
+ formatVersion: manifest.formatVersion ?? CHECKPOINT_FORMAT_VERSION,
828
+ version: manifest.version ?? "unknown",
829
+ createdAt: manifest.createdAt ?? new Date(0).toISOString(),
830
+ entrypoint: manifest.entrypoint ?? "checkpoint.js",
831
+ routes: manifest.routes ?? [],
832
+ assets: normalizedAssets,
833
+ bundleHash: manifest.bundleHash ?? "",
834
+ bundleSize: manifest.bundleSize ?? 0,
835
+ checkpointArchivePath: manifest.checkpointArchivePath,
836
+ checkpointArchiveHash: manifest.checkpointArchiveHash,
837
+ checkpointArchiveSize: manifest.checkpointArchiveSize,
838
+ checkpointArchiveCodec: manifest.checkpointArchiveCodec
839
+ };
840
+ }
841
+ }
842
+ var DEFAULT_STORAGE_DIR = ".vector/checkpoints", ACTIVE_POINTER_FILE = "active.json";
843
+ var init_manager = __esm(() => {
844
+ init_asset_store();
845
+ init_bundler();
846
+ init_entrypoint_generator();
847
+ init_packager();
848
+ init_socket_path();
849
+ });
850
+
851
+ // src/checkpoint/ipc.ts
852
+ function parseIpcLine(line) {
853
+ const trimmed = line.trim();
854
+ if (!trimmed)
855
+ return null;
856
+ if (trimmed === "READY")
857
+ return { type: "ready" };
858
+ try {
859
+ return JSON.parse(trimmed);
860
+ } catch {
861
+ return null;
862
+ }
863
+ }
864
+ async function waitForReady(stdout, timeoutMs = 1e4) {
865
+ return new Promise((resolve5, reject) => {
866
+ let settled = false;
867
+ const reader = stdout.getReader();
868
+ const decoder = new TextDecoder;
869
+ let buffer = "";
870
+ function settle(fn) {
871
+ if (settled)
872
+ return;
873
+ settled = true;
874
+ clearTimeout(timer);
875
+ try {
876
+ reader.releaseLock();
877
+ } catch {}
878
+ fn();
879
+ }
880
+ const timer = setTimeout(() => {
881
+ settle(() => reject(new Error(`Checkpoint process did not become ready within ${timeoutMs}ms`)));
882
+ }, timeoutMs);
883
+ const processLine = (line) => {
884
+ const msg = parseIpcLine(line);
885
+ if (msg?.type === "ready") {
886
+ settle(() => resolve5());
887
+ return "ready";
888
+ }
889
+ if (msg?.type === "error") {
890
+ settle(() => reject(new Error(`Checkpoint process error: ${msg.message}`)));
891
+ return "error";
892
+ }
893
+ return "continue";
894
+ };
895
+ const processBufferLines = () => {
896
+ let lineEnd = buffer.indexOf(`
897
+ `);
898
+ while (lineEnd !== -1) {
899
+ const line = buffer.slice(0, lineEnd);
900
+ buffer = buffer.slice(lineEnd + 1);
901
+ const result = processLine(line);
902
+ if (result !== "continue") {
903
+ return result;
904
+ }
905
+ lineEnd = buffer.indexOf(`
906
+ `);
907
+ }
908
+ return "continue";
909
+ };
910
+ (async () => {
911
+ try {
912
+ while (true) {
913
+ const { done, value } = await reader.read();
914
+ if (done) {
915
+ buffer += decoder.decode();
916
+ const lastLine = buffer.trim();
917
+ if (lastLine.length > 0) {
918
+ const result2 = processLine(lastLine);
919
+ if (result2 !== "continue") {
920
+ return;
921
+ }
922
+ }
923
+ settle(() => reject(new Error("Checkpoint process stdout closed before READY signal")));
924
+ return;
925
+ }
926
+ buffer += decoder.decode(value, { stream: true });
927
+ if (buffer.length > MAX_PENDING_STDOUT_CHARS) {
928
+ settle(() => reject(new Error(`Checkpoint process stdout exceeded ${MAX_PENDING_STDOUT_CHARS} chars before READY`)));
929
+ return;
930
+ }
931
+ const result = processBufferLines();
932
+ if (result !== "continue") {
933
+ return;
934
+ }
935
+ }
936
+ } catch (err) {
937
+ settle(() => reject(err));
938
+ }
939
+ })();
940
+ });
941
+ }
942
+ var MAX_PENDING_STDOUT_CHARS = 1048576;
943
+
944
+ // src/checkpoint/artifacts/worker-decompressor.ts
945
+ import { availableParallelism, cpus } from "os";
946
+
947
+ class CheckpointWorkerDecompressor {
948
+ workers = [];
949
+ idleWorkers = [];
950
+ queue = [];
951
+ activeJobsByWorker = new Map;
952
+ nextJobId = 1;
953
+ disposed = false;
954
+ constructor(workerCount = resolveDefaultWorkerCount()) {
955
+ const normalizedCount = normalizeWorkerCount(workerCount);
956
+ const workerUrl = resolveWorkerModuleUrl();
957
+ for (let i = 0;i < normalizedCount; i++) {
958
+ const worker = new Worker(workerUrl.href);
959
+ worker.onmessage = (event) => this.handleWorkerMessage(worker, event);
960
+ worker.onerror = (event) => this.handleWorkerError(worker, event);
961
+ this.workers.push(worker);
962
+ this.idleWorkers.push(worker);
963
+ }
964
+ }
965
+ async decompress(input, codec) {
966
+ if (codec === "none") {
967
+ return new Uint8Array(input);
968
+ }
969
+ if (this.disposed) {
970
+ throw new Error("Checkpoint worker decompressor is disposed");
971
+ }
972
+ const copied = new Uint8Array(input);
973
+ return await new Promise((resolve5, reject) => {
974
+ const id = this.nextJobId++;
975
+ this.queue.push({
976
+ id,
977
+ request: {
978
+ id,
979
+ codec,
980
+ input: copied.buffer
981
+ },
982
+ resolve: resolve5,
983
+ reject
984
+ });
985
+ this.pump();
986
+ });
987
+ }
988
+ async dispose() {
989
+ if (this.disposed) {
990
+ return;
991
+ }
992
+ this.disposed = true;
993
+ const error = new Error("Checkpoint worker decompressor disposed");
994
+ this.failAll(error);
995
+ for (const worker of this.workers) {
996
+ try {
997
+ worker.terminate();
998
+ } catch {}
999
+ }
1000
+ this.workers = [];
1001
+ this.idleWorkers = [];
1002
+ this.activeJobsByWorker.clear();
1003
+ }
1004
+ pump() {
1005
+ while (this.idleWorkers.length > 0 && this.queue.length > 0) {
1006
+ const worker = this.idleWorkers.pop();
1007
+ const job = this.queue.shift();
1008
+ this.activeJobsByWorker.set(worker, job);
1009
+ worker.postMessage(job.request, [job.request.input]);
1010
+ }
1011
+ }
1012
+ handleWorkerMessage(worker, event) {
1013
+ const job = this.activeJobsByWorker.get(worker);
1014
+ this.activeJobsByWorker.delete(worker);
1015
+ if (!this.disposed) {
1016
+ this.idleWorkers.push(worker);
1017
+ }
1018
+ if (!job) {
1019
+ this.pump();
1020
+ return;
1021
+ }
1022
+ const message = event.data;
1023
+ if (message.error) {
1024
+ job.reject(new Error(message.error));
1025
+ } else if (message.output instanceof ArrayBuffer) {
1026
+ job.resolve(new Uint8Array(message.output));
1027
+ } else {
1028
+ job.reject(new Error("Worker returned no output"));
1029
+ }
1030
+ this.pump();
1031
+ }
1032
+ handleWorkerError(worker, event) {
1033
+ const job = this.activeJobsByWorker.get(worker);
1034
+ this.activeJobsByWorker.delete(worker);
1035
+ this.idleWorkers = this.idleWorkers.filter((candidate) => candidate !== worker);
1036
+ const message = event.message?.trim() || "Checkpoint decompression worker crashed";
1037
+ const error = new Error(message);
1038
+ if (job) {
1039
+ job.reject(error);
1040
+ }
1041
+ this.failAll(error);
1042
+ this.dispose().catch(() => {});
1043
+ }
1044
+ failAll(error) {
1045
+ const queued = this.queue.splice(0, this.queue.length);
1046
+ for (const job of queued) {
1047
+ job.reject(error);
1048
+ }
1049
+ for (const job of this.activeJobsByWorker.values()) {
1050
+ job.reject(error);
1051
+ }
1052
+ this.activeJobsByWorker.clear();
1053
+ }
1054
+ }
1055
+ function resolveDefaultWorkerCount() {
1056
+ const cores = resolveCoreCount();
1057
+ const reserveForMainThread = Math.max(1, cores - 1);
1058
+ return Math.max(1, Math.min(DEFAULT_MAX_WORKERS, reserveForMainThread));
1059
+ }
1060
+ function resolveCoreCount() {
1061
+ try {
1062
+ const parallelism = availableParallelism();
1063
+ if (Number.isFinite(parallelism) && parallelism > 0) {
1064
+ return parallelism;
1065
+ }
1066
+ } catch {}
1067
+ const cpuCount = cpus().length;
1068
+ return Number.isFinite(cpuCount) && cpuCount > 0 ? cpuCount : 1;
1069
+ }
1070
+ function normalizeWorkerCount(value) {
1071
+ if (!Number.isFinite(value) || value <= 0) {
1072
+ return 1;
1073
+ }
1074
+ return Math.max(1, Math.floor(value));
1075
+ }
1076
+ function resolveWorkerModuleUrl() {
1077
+ if (import.meta.url.endsWith(".ts")) {
1078
+ return new URL("./decompress-worker.ts", import.meta.url);
1079
+ }
1080
+ return new URL("./decompress-worker.js", import.meta.url);
1081
+ }
1082
+ var DEFAULT_MAX_WORKERS = 4;
1083
+ var init_worker_decompressor = () => {};
1084
+
1085
+ // src/checkpoint/artifacts/materializer.ts
1086
+ import { existsSync as existsSync8, promises as fs8 } from "fs";
1087
+ import { dirname as dirname2, extname, join as join9, relative as relative6 } from "path";
1088
+
1089
+ class CheckpointArtifactMaterializer {
1090
+ verifyChecksums;
1091
+ materializedDirName;
1092
+ lockTimeoutMs;
1093
+ constructor(options = {}) {
1094
+ this.verifyChecksums = options.verifyChecksums ?? true;
1095
+ this.materializedDirName = options.materializedDirName ?? DEFAULT_MATERIALIZED_DIR;
1096
+ this.lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
1097
+ }
1098
+ async materialize(manifest, storageDir) {
1099
+ const versionDir = join9(storageDir, manifest.version);
1100
+ const markerPath = join9(versionDir, ".assets.ready.json");
1101
+ const lockPath = join9(versionDir, ".assets.lock");
1102
+ const fingerprint = computeAssetFingerprint(manifest.assets);
1103
+ if (await this.isReady(markerPath, fingerprint, manifest.assets, join9(versionDir, this.materializedDirName))) {
1104
+ return;
1105
+ }
1106
+ await this.acquireLock(lockPath);
1107
+ try {
1108
+ if (await this.isReady(markerPath, fingerprint, manifest.assets, join9(versionDir, this.materializedDirName))) {
1109
+ return;
1110
+ }
1111
+ const materializedRoot = join9(versionDir, this.materializedDirName);
1112
+ await fs8.rm(materializedRoot, { recursive: true, force: true });
1113
+ await fs8.mkdir(materializedRoot, { recursive: true });
1114
+ const decompressor = new CheckpointWorkerDecompressor;
1115
+ try {
1116
+ for (const asset of manifest.assets) {
1117
+ const result = await this.materializeAsset(asset, storageDir, versionDir, materializedRoot, decompressor);
1118
+ asset.materializedPath = result;
1119
+ }
1120
+ } finally {
1121
+ await decompressor.dispose();
1122
+ }
1123
+ const marker = {
1124
+ fingerprint,
1125
+ createdAt: new Date().toISOString()
1126
+ };
1127
+ await fs8.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf-8");
1128
+ } finally {
1129
+ await fs8.rm(lockPath, { recursive: true, force: true });
1130
+ }
1131
+ }
1132
+ async materializeAsset(asset, storageDir, versionDir, root, decompressor) {
1133
+ const sourcePath = this.resolveSourcePath(asset, storageDir);
1134
+ if (!existsSync8(sourcePath)) {
1135
+ throw new Error(`Checkpoint asset blob not found: ${sourcePath}`);
1136
+ }
1137
+ const blob = await fs8.readFile(sourcePath);
1138
+ const blobBytes = new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength);
1139
+ const expectedBlobHash = asset.blobHash;
1140
+ if (this.verifyChecksums && expectedBlobHash && sha256Hex(blobBytes) !== expectedBlobHash) {
1141
+ throw new Error(`Checkpoint asset blob checksum mismatch for ${asset.logicalPath}`);
1142
+ }
1143
+ const codec = asset.codec ?? (asset.blobHash ? "gzip" : "none");
1144
+ const contentBytes = await decompressor.decompress(blobBytes, codec);
1145
+ const expectedContentHash = asset.contentHash ?? asset.hash;
1146
+ if (this.verifyChecksums && expectedContentHash && sha256Hex(contentBytes) !== expectedContentHash) {
1147
+ throw new Error(`Checkpoint asset content checksum mismatch for ${asset.logicalPath}`);
1148
+ }
1149
+ const cachedFile = await this.writeDecompressedCache(asset, storageDir, contentBytes);
1150
+ const safeLogicalPath = normalizeLogicalPath(asset.logicalPath);
1151
+ const destinationPath = join9(root, safeLogicalPath);
1152
+ await fs8.mkdir(dirname2(destinationPath), { recursive: true });
1153
+ await fs8.rm(destinationPath, { force: true });
1154
+ await this.linkWithFallback(cachedFile, destinationPath);
1155
+ return normalizePath2(relative6(versionDir, destinationPath));
1156
+ }
1157
+ async writeDecompressedCache(asset, storageDir, bytes) {
1158
+ const hash = asset.contentHash ?? asset.hash;
1159
+ const extension = extname(asset.logicalPath) || ".bin";
1160
+ const cacheFile = join9(storageDir, "_assets/cache", `${hash}${extension}`);
1161
+ await fs8.mkdir(dirname2(cacheFile), { recursive: true });
1162
+ if (existsSync8(cacheFile)) {
1163
+ if (!this.verifyChecksums) {
1164
+ return cacheFile;
1165
+ }
1166
+ const existing = await fs8.readFile(cacheFile);
1167
+ const existingBytes = new Uint8Array(existing.buffer, existing.byteOffset, existing.byteLength);
1168
+ if (sha256Hex(existingBytes) === hash) {
1169
+ return cacheFile;
1170
+ }
1171
+ }
1172
+ await fs8.writeFile(cacheFile, bytes);
1173
+ return cacheFile;
1174
+ }
1175
+ resolveSourcePath(asset, storageDir) {
1176
+ const rawPath = asset.blobPath ?? asset.storedPath;
1177
+ if (isAbsolutePathPortable(rawPath)) {
1178
+ return rawPath;
1179
+ }
1180
+ return join9(storageDir, rawPath);
1181
+ }
1182
+ async linkWithFallback(sourcePath, destinationPath) {
1183
+ try {
1184
+ await fs8.link(sourcePath, destinationPath);
1185
+ return;
1186
+ } catch {}
1187
+ try {
1188
+ await fs8.symlink(sourcePath, destinationPath);
1189
+ return;
1190
+ } catch {}
1191
+ await fs8.copyFile(sourcePath, destinationPath);
1192
+ }
1193
+ async acquireLock(lockPath) {
1194
+ const deadline = Date.now() + this.lockTimeoutMs;
1195
+ while (Date.now() < deadline) {
1196
+ try {
1197
+ await fs8.mkdir(lockPath);
1198
+ return;
1199
+ } catch (error) {
1200
+ if (!isAlreadyExists2(error)) {
1201
+ throw error;
1202
+ }
1203
+ }
1204
+ await sleep(DEFAULT_LOCK_POLL_MS);
1205
+ }
1206
+ throw new Error(`Timed out waiting for checkpoint asset lock: ${lockPath}`);
1207
+ }
1208
+ async isReady(markerPath, fingerprint, assets, materializedRoot) {
1209
+ if (!existsSync8(markerPath)) {
1210
+ return false;
1211
+ }
1212
+ try {
1213
+ const marker = JSON.parse(await fs8.readFile(markerPath, "utf-8"));
1214
+ if (marker.fingerprint !== fingerprint) {
1215
+ return false;
1216
+ }
1217
+ for (const asset of assets) {
1218
+ const expectedPath = join9(materializedRoot, normalizeLogicalPath(asset.logicalPath));
1219
+ if (!existsSync8(expectedPath)) {
1220
+ return false;
1221
+ }
1222
+ }
1223
+ return true;
1224
+ } catch {
1225
+ return false;
1226
+ }
1227
+ }
1228
+ }
1229
+ function normalizePath2(path) {
1230
+ return path.replace(/\\/g, "/");
1231
+ }
1232
+ function isAlreadyExists2(error) {
1233
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
1234
+ }
1235
+ function sleep(ms) {
1236
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
1237
+ }
1238
+ var DEFAULT_MATERIALIZED_DIR = "_materialized", DEFAULT_LOCK_TIMEOUT_MS = 15000, DEFAULT_LOCK_POLL_MS = 50;
1239
+ var init_materializer = __esm(() => {
1240
+ init_manifest();
1241
+ init_worker_decompressor();
1242
+ });
1243
+
1244
+ // src/checkpoint/process-manager.ts
1245
+ var exports_process_manager = {};
1246
+ __export(exports_process_manager, {
1247
+ CheckpointProcessManager: () => CheckpointProcessManager
1248
+ });
1249
+ import { existsSync as existsSync9, promises as fs9, unlinkSync } from "fs";
1250
+ import { dirname as dirname3, join as join10 } from "path";
1251
+
1252
+ class CheckpointProcessManager {
1253
+ running = new Map;
1254
+ pending = new Map;
1255
+ readyTimeoutMs;
1256
+ idleTimeoutMs;
1257
+ idleTimers = new Map;
1258
+ lastUsedAt = new Map;
1259
+ materializer;
1260
+ constructor(options = DEFAULT_READY_TIMEOUT_MS) {
1261
+ if (typeof options === "number") {
1262
+ this.readyTimeoutMs = options;
1263
+ this.idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS;
1264
+ } else {
1265
+ this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
1266
+ this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
1267
+ }
1268
+ this.materializer = new CheckpointArtifactMaterializer;
1269
+ }
1270
+ async spawn(manifest, storageDir) {
1271
+ if (this.running.has(manifest.version)) {
1272
+ return this.running.get(manifest.version);
1273
+ }
1274
+ if (this.pending.has(manifest.version)) {
1275
+ return this.pending.get(manifest.version);
1276
+ }
1277
+ const promise = this.doSpawn(manifest, storageDir);
1278
+ this.pending.set(manifest.version, promise);
1279
+ try {
1280
+ const result = await promise;
1281
+ return result;
1282
+ } finally {
1283
+ this.pending.delete(manifest.version);
1284
+ }
1285
+ }
1286
+ async doSpawn(manifest, storageDir) {
1287
+ const versionDir = join10(storageDir, manifest.version);
1288
+ const bundlePath = join10(versionDir, manifest.entrypoint);
1289
+ const socketPath = resolveCheckpointSocketPath(storageDir, manifest.version);
1290
+ if (!existsSync9(bundlePath)) {
1291
+ throw new Error(`Checkpoint bundle not found: ${bundlePath}`);
1292
+ }
1293
+ await this.materializer.materialize(manifest, storageDir);
1294
+ await fs9.mkdir(dirname3(socketPath), { recursive: true });
1295
+ this.tryUnlinkSocket(socketPath);
1296
+ const proc = Bun.spawn(["bun", "run", bundlePath], {
1297
+ env: {
1298
+ ...process.env,
1299
+ VECTOR_CHECKPOINT_SOCKET: socketPath,
1300
+ VECTOR_CHECKPOINT_VERSION: manifest.version
1301
+ },
1302
+ stdout: "pipe",
1303
+ stderr: "inherit"
1304
+ });
1305
+ if (!proc.stdout) {
1306
+ proc.kill("SIGTERM");
1307
+ throw new Error(`Checkpoint process for ${manifest.version} did not provide stdout`);
1308
+ }
1309
+ try {
1310
+ await waitForReady(proc.stdout, this.readyTimeoutMs);
1311
+ } catch (err) {
1312
+ proc.kill("SIGTERM");
1313
+ throw err;
1314
+ }
1315
+ const spawned = {
1316
+ version: manifest.version,
1317
+ socketPath,
1318
+ process: proc,
1319
+ pid: proc.pid
1320
+ };
1321
+ this.running.set(manifest.version, spawned);
1322
+ this.lastUsedAt.set(manifest.version, Date.now());
1323
+ this.scheduleIdleCheck(manifest.version);
1324
+ return spawned;
1325
+ }
1326
+ markUsed(version) {
1327
+ if (!this.running.has(version)) {
1328
+ return;
1329
+ }
1330
+ this.lastUsedAt.set(version, Date.now());
1331
+ }
1332
+ async stop(version) {
1333
+ const snap = this.running.get(version);
1334
+ if (!snap)
1335
+ return;
1336
+ this.running.delete(version);
1337
+ this.clearIdleTimer(version);
1338
+ this.lastUsedAt.delete(version);
1339
+ snap.process.kill("SIGTERM");
1340
+ const exited = await Promise.race([
1341
+ snap.process.exited.then(() => true),
1342
+ new Promise((resolve5) => setTimeout(() => resolve5(false), STOP_TIMEOUT_MS))
1343
+ ]);
1344
+ if (!exited) {
1345
+ try {
1346
+ snap.process.kill("SIGKILL");
1347
+ await snap.process.exited;
1348
+ } catch {}
1349
+ }
1350
+ this.tryUnlinkSocket(snap.socketPath);
1351
+ }
1352
+ async stopAll() {
1353
+ const versions = [...this.running.keys()];
1354
+ for (const version of versions) {
1355
+ await this.stop(version);
1356
+ }
1357
+ }
1358
+ isRunning(version) {
1359
+ return this.running.has(version);
1360
+ }
1361
+ getRunning(version) {
1362
+ return this.running.get(version);
1363
+ }
1364
+ async health(version) {
1365
+ const snap = this.running.get(version);
1366
+ if (!snap)
1367
+ return false;
1368
+ try {
1369
+ const response = await fetch("http://localhost/_vector/health", {
1370
+ unix: snap.socketPath,
1371
+ signal: AbortSignal.timeout(2000)
1372
+ });
1373
+ return response.ok;
1374
+ } catch {
1375
+ return false;
1376
+ }
1377
+ }
1378
+ getRunningVersions() {
1379
+ return [...this.running.keys()];
1380
+ }
1381
+ scheduleIdleCheck(version, delayMs = this.idleTimeoutMs) {
1382
+ this.clearIdleTimer(version);
1383
+ if (this.idleTimeoutMs <= 0) {
1384
+ return;
1385
+ }
1386
+ const timer = setTimeout(() => {
1387
+ this.handleIdleCheck(version);
1388
+ }, Math.max(1, delayMs));
1389
+ if (typeof timer.unref === "function") {
1390
+ timer.unref();
1391
+ }
1392
+ this.idleTimers.set(version, timer);
1393
+ }
1394
+ async handleIdleCheck(version) {
1395
+ if (!this.running.has(version)) {
1396
+ return;
1397
+ }
1398
+ const lastUsedAt = this.lastUsedAt.get(version) ?? 0;
1399
+ const idleForMs = Date.now() - lastUsedAt;
1400
+ const remainingMs = this.idleTimeoutMs - idleForMs;
1401
+ if (remainingMs > 0) {
1402
+ this.scheduleIdleCheck(version, remainingMs);
1403
+ return;
1404
+ }
1405
+ try {
1406
+ await this.stop(version);
1407
+ } catch (error) {
1408
+ console.error(`[CheckpointProcessManager] Failed to stop idle checkpoint ${version}:`, error);
1409
+ }
1410
+ }
1411
+ clearIdleTimer(version) {
1412
+ const timer = this.idleTimers.get(version);
1413
+ if (!timer) {
1414
+ return;
1415
+ }
1416
+ clearTimeout(timer);
1417
+ this.idleTimers.delete(version);
1418
+ }
1419
+ tryUnlinkSocket(socketPath) {
1420
+ try {
1421
+ if (existsSync9(socketPath)) {
1422
+ unlinkSync(socketPath);
1423
+ }
1424
+ } catch {}
1425
+ }
1426
+ }
1427
+ var DEFAULT_READY_TIMEOUT_MS = 1e4, DEFAULT_IDLE_TIMEOUT_MS, STOP_TIMEOUT_MS = 5000;
1428
+ var init_process_manager = __esm(() => {
1429
+ init_materializer();
1430
+ init_socket_path();
1431
+ DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
1432
+ });
1433
+
1434
+ // src/checkpoint/resolver.ts
1435
+ var exports_resolver = {};
1436
+ __export(exports_resolver, {
1437
+ CheckpointResolver: () => CheckpointResolver
1438
+ });
1439
+
1440
+ class CheckpointResolver {
1441
+ manager;
1442
+ processManager;
1443
+ versionHeader;
1444
+ cacheKeyOverride;
1445
+ allowFallbackVersionHeader;
1446
+ pendingVersionResolves = new Map;
1447
+ constructor(manager, processManager, options = {}) {
1448
+ this.manager = manager;
1449
+ this.processManager = processManager;
1450
+ this.versionHeader = normalizeHeaderName(options.versionHeader ?? DEFAULT_VERSION_HEADER);
1451
+ this.cacheKeyOverride = options.cacheKeyOverride === true;
1452
+ this.allowFallbackVersionHeader = options.versionHeader === undefined;
1453
+ }
1454
+ async resolve(request) {
1455
+ const requestedVersion = this.getRequestedVersion(request);
1456
+ if (!requestedVersion) {
1457
+ return null;
1458
+ }
1459
+ return await this.resolveVersion(requestedVersion);
1460
+ }
1461
+ getRequestedVersion(request) {
1462
+ return this.getRequestedHeader(request)?.value ?? null;
1463
+ }
1464
+ getCacheKeyOverrideValue(request) {
1465
+ if (!this.cacheKeyOverride) {
1466
+ return null;
1467
+ }
1468
+ const requestedHeader = this.getRequestedHeader(request);
1469
+ if (!requestedHeader) {
1470
+ return null;
1471
+ }
1472
+ return `${requestedHeader.name}:${requestedHeader.value}`;
1473
+ }
1474
+ getRequestedHeader(request) {
1475
+ const primary = request.headers.get(this.versionHeader);
1476
+ if (primary && primary.trim().length > 0) {
1477
+ return { name: this.versionHeader, value: primary.trim() };
1478
+ }
1479
+ if (this.allowFallbackVersionHeader && this.versionHeader !== FALLBACK_VERSION_HEADER) {
1480
+ const fallback = request.headers.get(FALLBACK_VERSION_HEADER);
1481
+ if (fallback && fallback.trim().length > 0) {
1482
+ return { name: FALLBACK_VERSION_HEADER, value: fallback.trim() };
1483
+ }
1484
+ }
1485
+ return null;
1486
+ }
1487
+ async resolveVersion(version) {
1488
+ let running = this.processManager.getRunning(version);
1489
+ if (!running) {
1490
+ const pending = this.pendingVersionResolves.get(version);
1491
+ if (pending) {
1492
+ const socketPath2 = await pending;
1493
+ if (!socketPath2) {
1494
+ return null;
1495
+ }
1496
+ this.processManager.markUsed(version);
1497
+ return socketPath2;
1498
+ }
1499
+ const pendingResolve = (async () => {
1500
+ try {
1501
+ const manifest = await this.manager.readManifest(version);
1502
+ const spawned = await this.processManager.spawn(manifest, this.manager.getStorageDir());
1503
+ return spawned.socketPath;
1504
+ } catch {
1505
+ return null;
1506
+ }
1507
+ })();
1508
+ this.pendingVersionResolves.set(version, pendingResolve);
1509
+ const socketPath = await pendingResolve;
1510
+ this.pendingVersionResolves.delete(version);
1511
+ if (!socketPath) {
1512
+ return null;
1513
+ }
1514
+ this.processManager.markUsed(version);
1515
+ return socketPath;
1516
+ }
1517
+ this.processManager.markUsed(version);
1518
+ return running.socketPath;
1519
+ }
1520
+ invalidateCache() {}
1521
+ }
1522
+ function normalizeHeaderName(value) {
1523
+ const normalized = value.trim().toLowerCase();
1524
+ return normalized.length > 0 ? normalized : DEFAULT_VERSION_HEADER;
1525
+ }
1526
+ var DEFAULT_VERSION_HEADER = "x-vector-checkpoint-version", FALLBACK_VERSION_HEADER = "x-vector-checkpoint";
1527
+
1528
+ // src/checkpoint/forwarder.ts
1529
+ var exports_forwarder = {};
1530
+ __export(exports_forwarder, {
1531
+ CheckpointForwarder: () => CheckpointForwarder,
1532
+ CHECKPOINT_CONTEXT_HEADER: () => CHECKPOINT_CONTEXT_HEADER
1533
+ });
1534
+
1535
+ class CheckpointForwarder {
1536
+ async forward(request, socketPath, contextPayload) {
1537
+ try {
1538
+ const encodedContext = encodeCheckpointContext(contextPayload);
1539
+ const headers = buildForwardHeaders(request.headers, encodedContext);
1540
+ const response = await fetch(request.url, {
1541
+ method: request.method,
1542
+ headers,
1543
+ body: request.body,
1544
+ unix: socketPath,
1545
+ duplex: request.body ? "half" : undefined
1546
+ });
1547
+ return new Response(response.body, {
1548
+ status: response.status,
1549
+ statusText: response.statusText,
1550
+ headers: new Headers(response.headers)
1551
+ });
1552
+ } catch (error) {
1553
+ console.error("[CheckpointForwarder] Forward failed:", error);
1554
+ return new Response(JSON.stringify({ error: true, message: "Checkpoint unavailable", statusCode: 503 }), {
1555
+ status: 503,
1556
+ headers: { "content-type": "application/json" }
1557
+ });
1558
+ }
1559
+ }
1560
+ }
1561
+ function buildForwardHeaders(source, encodedContext) {
1562
+ const headers = new Headers(source);
1563
+ stripHopByHopHeaders(headers);
1564
+ if (encodedContext) {
1565
+ headers.set(CHECKPOINT_CONTEXT_HEADER, encodedContext);
1566
+ }
1567
+ return headers;
1568
+ }
1569
+ function stripHopByHopHeaders(headers) {
1570
+ const connectionValue = headers.get("connection");
1571
+ if (connectionValue) {
1572
+ for (const token of connectionValue.split(",")) {
1573
+ const normalized = token.trim().toLowerCase();
1574
+ if (normalized) {
1575
+ headers.delete(normalized);
1576
+ }
1577
+ }
1578
+ }
1579
+ headers.delete("connection");
1580
+ headers.delete("keep-alive");
1581
+ headers.delete("proxy-authenticate");
1582
+ headers.delete("proxy-authorization");
1583
+ headers.delete("te");
1584
+ headers.delete("trailer");
1585
+ headers.delete("transfer-encoding");
1586
+ headers.delete("upgrade");
1587
+ }
1588
+ function encodeCheckpointContext(contextPayload) {
1589
+ if (!contextPayload) {
1590
+ return null;
1591
+ }
1592
+ const keys = Object.keys(contextPayload);
1593
+ if (keys.length === 0) {
1594
+ return null;
1595
+ }
1596
+ try {
1597
+ return Buffer.from(JSON.stringify(contextPayload), "utf-8").toString("base64url");
1598
+ } catch {
1599
+ return null;
1600
+ }
1601
+ }
1602
+ var CHECKPOINT_CONTEXT_HEADER = "x-vector-checkpoint-context";
1603
+
1604
+ // src/checkpoint/gateway.ts
1605
+ var exports_gateway = {};
1606
+ __export(exports_gateway, {
1607
+ CheckpointGateway: () => CheckpointGateway
1608
+ });
1609
+
1610
+ class CheckpointGateway {
1611
+ resolver;
1612
+ forwarder;
1613
+ constructor(resolver, forwarder) {
1614
+ this.resolver = resolver;
1615
+ this.forwarder = forwarder;
1616
+ }
1617
+ getRequestedVersion(request) {
1618
+ return this.resolver.getRequestedVersion(request);
1619
+ }
1620
+ getCacheKeyOverrideValue(request) {
1621
+ return this.resolver.getCacheKeyOverrideValue(request);
1622
+ }
1623
+ async handle(request, contextPayload) {
1624
+ const requestedVersion = this.getRequestedVersion(request);
1625
+ const socketPath = await this.resolver.resolve(request);
1626
+ if (!socketPath) {
1627
+ if (requestedVersion) {
1628
+ return new Response(JSON.stringify({
1629
+ error: true,
1630
+ message: `Requested checkpoint version "${requestedVersion}" is unavailable`,
1631
+ statusCode: 503
1632
+ }), { status: 503, headers: { "content-type": "application/json" } });
1633
+ }
1634
+ return null;
1635
+ }
1636
+ return await this.forwarder.forward(request, socketPath, contextPayload);
1637
+ }
1638
+ }
1639
+
1640
+ // src/checkpoint/cli.ts
1641
+ var exports_cli = {};
1642
+ __export(exports_cli, {
1643
+ runCheckpointCli: () => runCheckpointCli
1644
+ });
1645
+ import { parseArgs } from "util";
1646
+ async function runCheckpointCli(argv) {
1647
+ const subcommand = argv[0];
1648
+ switch (subcommand) {
1649
+ case "publish":
1650
+ return await cliPublish(argv.slice(1));
1651
+ case "list":
1652
+ return await cliList(argv.slice(1));
1653
+ case "rollback":
1654
+ return await cliRollback(argv.slice(1));
1655
+ case "remove":
1656
+ return await cliRemove(argv.slice(1));
1657
+ default:
1658
+ printCheckpointHelp();
1659
+ if (subcommand) {
1660
+ console.error(`
1661
+ Unknown checkpoint command: ${subcommand}`);
1662
+ }
1663
+ process.exit(1);
1664
+ }
1665
+ }
1666
+ async function cliPublish(args) {
1667
+ const { values } = parseArgs({
1668
+ args,
1669
+ options: {
1670
+ version: { type: "string", short: "v" },
1671
+ routes: { type: "string", short: "r", default: "./routes" },
1672
+ storage: { type: "string", short: "s" }
1673
+ },
1674
+ strict: true
1675
+ });
1676
+ if (!values.version) {
1677
+ console.error("Error: --version is required for publish");
1678
+ console.error("Usage: vector checkpoint publish --version <ver> [--routes <dir>]");
1679
+ process.exit(1);
1680
+ }
1681
+ const manager = new CheckpointManager(values.storage ? { storageDir: values.storage } : undefined);
1682
+ try {
1683
+ console.log(`Publishing checkpoint ${values.version}...`);
1684
+ const manifest = await manager.publish({
1685
+ version: values.version,
1686
+ routesDir: values.routes
1687
+ });
1688
+ console.log(`Checkpoint ${manifest.version} published successfully.`);
1689
+ console.log(` Bundle hash: ${manifest.bundleHash.slice(0, 12)}...`);
1690
+ console.log(` Bundle size: ${formatBytes2(manifest.bundleSize)}`);
1691
+ console.log(` Routes: ${manifest.routes.length}`);
1692
+ console.log(` Assets: ${manifest.assets.length}`);
1693
+ } catch (err) {
1694
+ console.error(`Failed to publish checkpoint: ${err.message}`);
1695
+ process.exit(1);
1696
+ }
1697
+ }
1698
+ async function cliList(args) {
1699
+ const { values } = parseArgs({
1700
+ args,
1701
+ options: {
1702
+ storage: { type: "string", short: "s" }
1703
+ },
1704
+ strict: true
1705
+ });
1706
+ const manager = new CheckpointManager(values.storage ? { storageDir: values.storage } : undefined);
1707
+ const manifests = await manager.listVersions();
1708
+ const active = await manager.getActive();
1709
+ if (manifests.length === 0) {
1710
+ console.log("No checkpoints found.");
1711
+ return;
1712
+ }
1713
+ console.log("");
1714
+ console.log(" Version Created Bundle Hash Size Status");
1715
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1716
+ for (const m of manifests) {
1717
+ const isActive = active?.version === m.version;
1718
+ const status = isActive ? "\u25CF active" : " ";
1719
+ const hash = m.bundleHash.slice(0, 12);
1720
+ const size = formatBytes2(m.bundleSize).padEnd(10);
1721
+ const created = new Date(m.createdAt).toISOString().replace("T", " ").slice(0, 19);
1722
+ console.log(` ${m.version.padEnd(12)} ${created} ${hash}... ${size} ${status}`);
1723
+ }
1724
+ console.log("");
1725
+ }
1726
+ async function cliRollback(args) {
1727
+ const { positionals, values } = parseArgs({
1728
+ args,
1729
+ options: {
1730
+ storage: { type: "string", short: "s" }
1731
+ },
1732
+ strict: true,
1733
+ allowPositionals: true
1734
+ });
1735
+ const version = positionals[0];
1736
+ if (!version) {
1737
+ console.error("Error: version argument is required");
1738
+ console.error("Usage: vector checkpoint rollback <version>");
1739
+ process.exit(1);
1740
+ }
1741
+ const manager = new CheckpointManager(values.storage ? { storageDir: values.storage } : undefined);
1742
+ try {
1743
+ await manager.setActive(version);
1744
+ console.log(`Active checkpoint set to ${version}.`);
1745
+ console.log("Note: Restart the server for the change to take effect.");
1746
+ } catch (err) {
1747
+ console.error(`Failed to rollback: ${err.message}`);
1748
+ process.exit(1);
1749
+ }
1750
+ }
1751
+ async function cliRemove(args) {
1752
+ const { positionals, values } = parseArgs({
1753
+ args,
1754
+ options: {
1755
+ storage: { type: "string", short: "s" }
1756
+ },
1757
+ strict: true,
1758
+ allowPositionals: true
1759
+ });
1760
+ const version = positionals[0];
1761
+ if (!version) {
1762
+ console.error("Error: version argument is required");
1763
+ console.error("Usage: vector checkpoint remove <version>");
1764
+ process.exit(1);
1765
+ }
1766
+ const manager = new CheckpointManager(values.storage ? { storageDir: values.storage } : undefined);
1767
+ try {
1768
+ await manager.remove(version);
1769
+ console.log(`Checkpoint ${version} removed.`);
1770
+ } catch (err) {
1771
+ console.error(`Failed to remove checkpoint: ${err.message}`);
1772
+ process.exit(1);
1773
+ }
1774
+ }
1775
+ function printCheckpointHelp() {
1776
+ console.log(`
1777
+ Usage: vector checkpoint <command>
1778
+
1779
+ Commands:
1780
+ publish --version <ver> [--routes <dir>] Build and store a checkpoint
1781
+ list List all stored checkpoints
1782
+ rollback <version> Activate a specific checkpoint
1783
+ remove <version> Delete a checkpoint
1784
+
1785
+ Options:
1786
+ -v, --version Semver version string (e.g. 1.2.0)
1787
+ -r, --routes Routes directory (default: ./routes)
1788
+ -s, --storage Checkpoint storage dir (default: .vector/checkpoints)
1789
+ `);
1790
+ }
1791
+ function formatBytes2(bytes) {
1792
+ if (bytes < 1024)
1793
+ return `${bytes} B`;
1794
+ if (bytes < 1024 * 1024)
1795
+ return `${(bytes / 1024).toFixed(1)} KB`;
1796
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1797
+ }
1798
+ var init_cli = __esm(() => {
1799
+ init_manager();
1800
+ });
3
1801
 
4
1802
  // src/cli/index.ts
5
1803
  import { watch } from "fs";
6
- import { parseArgs } from "util";
1804
+ import { parseArgs as parseArgs2 } from "util";
7
1805
 
8
1806
  // src/cli/option-resolution.ts
9
1807
  function resolveRoutesDir(configRoutesDir, hasRoutesOption, cliRoutes) {
@@ -100,7 +1898,7 @@ class ConfigLoader {
100
1898
  if (existsSync(this.configPath)) {
101
1899
  try {
102
1900
  const userConfigPath = toFileUrl(this.configPath);
103
- const userConfig = await import(userConfigPath);
1901
+ const userConfig = await import(`${userConfigPath}?t=${Date.now()}`);
104
1902
  this.config = userConfig.default || userConfig;
105
1903
  this.configSource = "user";
106
1904
  } catch (error) {
@@ -120,7 +1918,7 @@ class ConfigLoader {
120
1918
  async buildLegacyConfig() {
121
1919
  const config = {};
122
1920
  if (this.config) {
123
- config.port = this.config.port;
1921
+ config.port = this.normalizePort(this.config.port);
124
1922
  config.hostname = this.config.hostname;
125
1923
  config.reusePort = this.config.reusePort;
126
1924
  config.development = this.config.development;
@@ -131,6 +1929,7 @@ class ConfigLoader {
131
1929
  config.openapi = this.config.openapi;
132
1930
  config.startup = this.config.startup;
133
1931
  config.shutdown = this.config.shutdown;
1932
+ config.checkpoint = this.config.checkpoint;
134
1933
  }
135
1934
  config.autoDiscover = true;
136
1935
  if (this.config?.cors) {
@@ -155,6 +1954,12 @@ class ConfigLoader {
155
1954
  }
156
1955
  return config;
157
1956
  }
1957
+ normalizePort(port) {
1958
+ if (port === undefined) {
1959
+ return;
1960
+ }
1961
+ return Number(port);
1962
+ }
158
1963
  async loadAuthHandler() {
159
1964
  return this.config?.auth || null;
160
1965
  }
@@ -257,23 +2062,26 @@ class AuthManager {
257
2062
  clearProtectedHandler() {
258
2063
  this.protectedHandler = null;
259
2064
  }
260
- async authenticate(request) {
2065
+ async authenticate(context) {
261
2066
  if (!this.protectedHandler) {
262
2067
  throw new Error("Protected handler not configured. Use vector.protected() to set authentication handler.");
263
2068
  }
2069
+ if (!context || typeof context !== "object" || !context.request) {
2070
+ throw new Error("Authentication context is invalid: missing request");
2071
+ }
264
2072
  try {
265
- const authUser = await this.protectedHandler(request);
266
- request.authUser = authUser;
2073
+ const authUser = await this.protectedHandler(context);
2074
+ context.authUser = authUser;
267
2075
  return authUser;
268
2076
  } catch (error) {
269
2077
  throw new Error(`Authentication failed: ${error instanceof Error ? error.message : String(error)}`);
270
2078
  }
271
2079
  }
272
- isAuthenticated(request) {
273
- return !!request.authUser;
2080
+ isAuthenticated(context) {
2081
+ return !!context.authUser;
274
2082
  }
275
- getUser(request) {
276
- return request.authUser || null;
2083
+ getUser(context) {
2084
+ return context.authUser || null;
277
2085
  }
278
2086
  }
279
2087
 
@@ -302,10 +2110,11 @@ class CacheManager {
302
2110
  const now = Date.now();
303
2111
  const cached = this.memoryCache.get(key);
304
2112
  if (this.isCacheValid(cached, now)) {
305
- return cached.value;
2113
+ return this.cloneCachedValue(cached.value);
306
2114
  }
307
2115
  if (this.inflight.has(key)) {
308
- return await this.inflight.get(key);
2116
+ const inflightValue = await this.inflight.get(key);
2117
+ return this.cloneCachedValue(inflightValue);
309
2118
  }
310
2119
  const promise = (async () => {
311
2120
  const value = await factory();
@@ -324,15 +2133,31 @@ class CacheManager {
324
2133
  }
325
2134
  setInMemoryCache(key, value, ttl) {
326
2135
  const expires = Date.now() + ttl * 1000;
327
- this.memoryCache.set(key, { value, expires });
2136
+ this.memoryCache.set(key, { value: this.cloneForStore(value), expires });
328
2137
  this.scheduleCleanup();
329
2138
  }
2139
+ cloneForStore(value) {
2140
+ if (value instanceof Response) {
2141
+ return value.clone();
2142
+ }
2143
+ return value;
2144
+ }
2145
+ cloneCachedValue(value) {
2146
+ if (value instanceof Response) {
2147
+ return value.clone();
2148
+ }
2149
+ return value;
2150
+ }
330
2151
  scheduleCleanup() {
331
2152
  if (this.cleanupInterval)
332
2153
  return;
333
- this.cleanupInterval = setInterval(() => {
2154
+ const timer = setInterval(() => {
334
2155
  this.cleanupExpired();
335
2156
  }, 60000);
2157
+ if (typeof timer.unref === "function") {
2158
+ timer.unref();
2159
+ }
2160
+ this.cleanupInterval = timer;
336
2161
  }
337
2162
  cleanupExpired() {
338
2163
  const now = Date.now();
@@ -530,7 +2355,7 @@ class RouteScanner {
530
2355
  const routePath = relative2(this.routesDir, fullPath).replace(/\.(ts|js)$/, "").split(sep).join("/");
531
2356
  try {
532
2357
  const importPath = process.platform === "win32" ? `file:///${fullPath.replace(/\\/g, "/")}` : fullPath;
533
- const module = await import(importPath);
2358
+ const module = await import(`${importPath}?t=${Date.now()}`);
534
2359
  if (module.default && typeof module.default === "function") {
535
2360
  routes.push({
536
2361
  name: "default",
@@ -594,26 +2419,27 @@ class MiddlewareManager {
594
2419
  addFinally(...handlers) {
595
2420
  this.finallyHandlers.push(...handlers);
596
2421
  }
597
- async executeBefore(request) {
2422
+ async executeBefore(context) {
598
2423
  if (this.beforeHandlers.length === 0)
599
- return request;
600
- let currentRequest = request;
2424
+ return null;
601
2425
  for (const handler of this.beforeHandlers) {
602
- const result = await handler(currentRequest);
2426
+ const result = await handler(context);
603
2427
  if (result instanceof Response) {
604
2428
  return result;
605
2429
  }
606
- currentRequest = result;
2430
+ if (result !== undefined) {
2431
+ throw new TypeError("Before middleware must return void or Response");
2432
+ }
607
2433
  }
608
- return currentRequest;
2434
+ return null;
609
2435
  }
610
- async executeFinally(response, request) {
2436
+ async executeFinally(response, context) {
611
2437
  if (this.finallyHandlers.length === 0)
612
2438
  return response;
613
2439
  let currentResponse = response;
614
2440
  for (const handler of this.finallyHandlers) {
615
2441
  try {
616
- currentResponse = await handler(currentResponse, request);
2442
+ currentResponse = await handler(currentResponse, context);
617
2443
  } catch (error) {
618
2444
  console.error("After middleware error:", error);
619
2445
  }
@@ -644,6 +2470,52 @@ function stringifyData(data) {
644
2470
  throw e;
645
2471
  }
646
2472
  }
2473
+ function isJsonContentType(contentType) {
2474
+ const mimeType = contentType.split(";", 1)[0] ?? contentType;
2475
+ return mimeType.trim().toLowerCase() === CONTENT_TYPES.JSON;
2476
+ }
2477
+ function serializeCookie(cookie) {
2478
+ const segments = [`${cookie.name}=${cookie.value}`];
2479
+ if (cookie.maxAge !== undefined && Number.isFinite(cookie.maxAge)) {
2480
+ segments.push(`Max-Age=${Math.trunc(cookie.maxAge)}`);
2481
+ }
2482
+ if (cookie.domain) {
2483
+ segments.push(`Domain=${cookie.domain}`);
2484
+ }
2485
+ if (cookie.path) {
2486
+ segments.push(`Path=${cookie.path}`);
2487
+ }
2488
+ if (cookie.expires !== undefined) {
2489
+ const expiresAt = cookie.expires instanceof Date ? cookie.expires : new Date(cookie.expires);
2490
+ if (!Number.isNaN(expiresAt.getTime())) {
2491
+ segments.push(`Expires=${expiresAt.toUTCString()}`);
2492
+ }
2493
+ }
2494
+ if (cookie.httpOnly) {
2495
+ segments.push("HttpOnly");
2496
+ }
2497
+ if (cookie.secure) {
2498
+ segments.push("Secure");
2499
+ }
2500
+ if (cookie.sameSite) {
2501
+ segments.push(`SameSite=${cookie.sameSite}`);
2502
+ }
2503
+ if (cookie.partitioned) {
2504
+ segments.push("Partitioned");
2505
+ }
2506
+ if (cookie.priority) {
2507
+ segments.push(`Priority=${cookie.priority}`);
2508
+ }
2509
+ return segments.join("; ");
2510
+ }
2511
+ function appendSetCookieHeaders(headers, cookies) {
2512
+ if (!cookies || cookies.length === 0) {
2513
+ return;
2514
+ }
2515
+ for (const cookie of cookies) {
2516
+ headers.append("set-cookie", typeof cookie === "string" ? cookie : serializeCookie(cookie));
2517
+ }
2518
+ }
647
2519
  function createErrorResponse(code, message, contentType) {
648
2520
  const errorBody = {
649
2521
  error: true,
@@ -698,14 +2570,34 @@ var APIError = {
698
2570
  maintenance: (msg = "Service Under Maintenance", contentType) => createErrorResponse(503, msg, contentType),
699
2571
  custom: (statusCode, msg, contentType) => createErrorResponse(statusCode, msg, contentType)
700
2572
  };
701
- function createResponse(statusCode, data, contentType = CONTENT_TYPES.JSON) {
702
- const body = contentType === CONTENT_TYPES.JSON ? stringifyData(data) : data;
2573
+ function createResponse(statusCode, data, optionsOrContentType = CONTENT_TYPES.JSON) {
2574
+ const options = typeof optionsOrContentType === "string" ? { contentType: optionsOrContentType } : optionsOrContentType ?? {};
2575
+ const headers = new Headers(options.headers);
2576
+ const contentType = options.contentType ?? headers.get("content-type") ?? CONTENT_TYPES.JSON;
2577
+ if (options.contentType || !headers.has("content-type")) {
2578
+ headers.set("content-type", contentType);
2579
+ }
2580
+ appendSetCookieHeaders(headers, options.cookies);
2581
+ const body = isJsonContentType(contentType) ? stringifyData(data) : data;
703
2582
  return new Response(body, {
704
2583
  status: statusCode,
705
- headers: { "content-type": contentType }
2584
+ statusText: options.statusText,
2585
+ headers
706
2586
  });
707
2587
  }
708
2588
 
2589
+ // src/types/index.ts
2590
+ var AuthKind;
2591
+ ((AuthKind2) => {
2592
+ AuthKind2["ApiKey"] = "ApiKey";
2593
+ AuthKind2["HttpBasic"] = "HttpBasic";
2594
+ AuthKind2["HttpBearer"] = "HttpBearer";
2595
+ AuthKind2["HttpDigest"] = "HttpDigest";
2596
+ AuthKind2["OAuth2"] = "OAuth2";
2597
+ AuthKind2["OpenIdConnect"] = "OpenIdConnect";
2598
+ AuthKind2["MutualTls"] = "MutualTls";
2599
+ })(AuthKind ||= {});
2600
+
709
2601
  // src/utils/schema-validation.ts
710
2602
  function isStandardRouteSchema(schema) {
711
2603
  const standard = schema?.["~standard"];
@@ -783,6 +2675,11 @@ function createValidationErrorPayload(target, issues) {
783
2675
  }
784
2676
 
785
2677
  // src/core/router.ts
2678
+ var AUTH_KIND_VALUES = new Set(Object.values(AuthKind));
2679
+ function isAuthKindValue(value) {
2680
+ return typeof value === "string" && AUTH_KIND_VALUES.has(value);
2681
+ }
2682
+
786
2683
  class VectorRouter {
787
2684
  middlewareManager;
788
2685
  authManager;
@@ -794,6 +2691,7 @@ class VectorRouter {
794
2691
  routeMatchers = [];
795
2692
  corsHeadersEntries = null;
796
2693
  corsHandler = null;
2694
+ checkpointGateway = null;
797
2695
  constructor(middlewareManager, authManager, cacheManager) {
798
2696
  this.middlewareManager = middlewareManager;
799
2697
  this.authManager = authManager;
@@ -805,6 +2703,9 @@ class VectorRouter {
805
2703
  setCorsHandler(handler) {
806
2704
  this.corsHandler = handler;
807
2705
  }
2706
+ setCheckpointGateway(gateway) {
2707
+ this.checkpointGateway = gateway;
2708
+ }
808
2709
  setRouteBooleanDefaults(defaults) {
809
2710
  this.routeBooleanDefaults = { ...defaults };
810
2711
  }
@@ -820,6 +2721,9 @@ class VectorRouter {
820
2721
  resolved[key] = defaults[key];
821
2722
  }
822
2723
  }
2724
+ if (resolved.auth === true && isAuthKindValue(defaults.auth)) {
2725
+ resolved.auth = defaults.auth;
2726
+ }
823
2727
  return resolved;
824
2728
  }
825
2729
  route(options, handler) {
@@ -896,218 +2800,279 @@ class VectorRouter {
896
2800
  } catch {
897
2801
  return APIError.badRequest("Malformed request URL");
898
2802
  }
899
- request._parsedUrl = url;
900
2803
  const pathname = url.pathname;
2804
+ const exactPathRoute = this.routeTable[pathname];
2805
+ if (exactPathRoute) {
2806
+ if (exactPathRoute instanceof Response) {
2807
+ return this.applyCorsResponse(exactPathRoute.clone(), request);
2808
+ }
2809
+ const exactPathMethodMap = exactPathRoute;
2810
+ const handler = exactPathMethodMap[request.method] ?? (request.method === "HEAD" ? exactPathMethodMap["GET"] : undefined);
2811
+ if (handler) {
2812
+ const response = await handler(request);
2813
+ if (response) {
2814
+ return response;
2815
+ }
2816
+ }
2817
+ }
901
2818
  for (const matcher of this.routeMatchers) {
902
2819
  const path = matcher.path;
903
- const value = this.routeTable[path];
904
- if (!value)
2820
+ const routeEntry = this.routeTable[path];
2821
+ if (!routeEntry)
905
2822
  continue;
906
- if (value instanceof Response)
907
- continue;
908
- const methodMap = value;
909
- if (request.method === "OPTIONS" || request.method in methodMap) {
910
- const match = pathname.match(matcher.regex);
911
- if (match) {
912
- try {
913
- request.params = match.groups ?? {};
914
- } catch {}
915
- const handler = methodMap[request.method] ?? methodMap["GET"];
916
- if (handler) {
917
- const response = await handler(request);
918
- if (response)
919
- return response;
920
- }
2823
+ if (routeEntry instanceof Response) {
2824
+ if (pathname === path) {
2825
+ return this.applyCorsResponse(routeEntry.clone(), request);
921
2826
  }
2827
+ continue;
2828
+ }
2829
+ const methodMap = routeEntry;
2830
+ const handler = methodMap[request.method] ?? (request.method === "HEAD" ? methodMap["GET"] : undefined);
2831
+ if (!handler) {
2832
+ continue;
2833
+ }
2834
+ const match = pathname.match(matcher.regex);
2835
+ if (!match) {
2836
+ continue;
922
2837
  }
2838
+ const response = await handler(request);
2839
+ if (response)
2840
+ return response;
923
2841
  }
924
- return STATIC_RESPONSES.NOT_FOUND.clone();
2842
+ return this.applyCorsResponse(STATIC_RESPONSES.NOT_FOUND.clone(), request);
925
2843
  }
926
- prepareRequest(request, options) {
927
- if (!request.context) {
928
- request.context = {};
929
- }
930
- const hasEmptyParamsObject = !!request.params && typeof request.params === "object" && !Array.isArray(request.params) && Object.keys(request.params).length === 0;
931
- if (options?.params !== undefined && (request.params === undefined || hasEmptyParamsObject)) {
932
- try {
933
- request.params = options.params;
934
- } catch {}
2844
+ cloneMetadata(value) {
2845
+ if (Array.isArray(value)) {
2846
+ return [...value];
935
2847
  }
936
- if (options?.route !== undefined) {
937
- request.route = options.route;
2848
+ if (value && typeof value === "object") {
2849
+ return { ...value };
938
2850
  }
939
- if (options?.metadata !== undefined) {
940
- request.metadata = options.metadata;
941
- }
942
- if (request.query == null && request.url) {
943
- try {
944
- Object.defineProperty(request, "query", {
945
- get() {
946
- const url = this._parsedUrl ?? new URL(this.url);
947
- const query = VectorRouter.parseQuery(url);
948
- Object.defineProperty(this, "query", {
949
- value: query,
950
- writable: true,
951
- configurable: true,
952
- enumerable: true
953
- });
954
- return query;
955
- },
956
- set(value) {
957
- Object.defineProperty(this, "query", {
958
- value,
959
- writable: true,
960
- configurable: true,
961
- enumerable: true
962
- });
963
- },
964
- configurable: true,
965
- enumerable: true
966
- });
967
- } catch {
968
- const url = request._parsedUrl ?? new URL(request.url);
969
- try {
970
- request.query = VectorRouter.parseQuery(url);
971
- } catch {}
2851
+ return value;
2852
+ }
2853
+ createContext(request, options) {
2854
+ const context = {
2855
+ request
2856
+ };
2857
+ this.setContextField(context, "metadata", options?.metadata !== undefined ? this.cloneMetadata(options.metadata) : {});
2858
+ this.setContextField(context, "params", options?.params ?? {});
2859
+ this.setContextField(context, "query", options?.query ?? {});
2860
+ this.setContextField(context, "cookies", options?.cookies ?? {});
2861
+ return context;
2862
+ }
2863
+ setContextField(context, key, value) {
2864
+ context[key] = value;
2865
+ }
2866
+ hasOwnContextField(context, key) {
2867
+ return Object.prototype.hasOwnProperty.call(context, key);
2868
+ }
2869
+ buildCheckpointContextPayload(context) {
2870
+ const payload = {};
2871
+ const allowedKeys = ["metadata", "content", "validatedInput", "authUser"];
2872
+ for (const key of allowedKeys) {
2873
+ if (!this.hasOwnContextField(context, key)) {
2874
+ continue;
972
2875
  }
2876
+ const value = context[key];
2877
+ if (typeof value === "function" || typeof value === "symbol" || value === undefined) {
2878
+ continue;
2879
+ }
2880
+ payload[key] = value;
973
2881
  }
974
- if (!Object.getOwnPropertyDescriptor(request, "cookies")) {
975
- Object.defineProperty(request, "cookies", {
976
- get() {
977
- const cookieHeader = this.headers.get("cookie") ?? "";
978
- const cookies = {};
979
- if (cookieHeader) {
980
- for (const pair of cookieHeader.split(";")) {
981
- const idx = pair.indexOf("=");
982
- if (idx > 0) {
983
- cookies[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
984
- }
985
- }
986
- }
987
- Object.defineProperty(this, "cookies", {
988
- value: cookies,
989
- writable: true,
990
- configurable: true,
991
- enumerable: true
992
- });
993
- return cookies;
994
- },
995
- configurable: true,
996
- enumerable: true
997
- });
998
- }
2882
+ return payload;
999
2883
  }
1000
- resolveFallbackParams(request, routeMatcher) {
2884
+ resolveFallbackParams(pathname, routeMatcher) {
1001
2885
  if (!routeMatcher) {
1002
2886
  return;
1003
2887
  }
1004
- const currentParams = request.params;
1005
- if (currentParams && typeof currentParams === "object" && !Array.isArray(currentParams) && Object.keys(currentParams).length > 0) {
2888
+ const matched = pathname.match(routeMatcher);
2889
+ if (!matched?.groups) {
1006
2890
  return;
1007
2891
  }
1008
- let pathname;
2892
+ return matched.groups;
2893
+ }
2894
+ getRequestedCheckpointVersion(request) {
2895
+ if (!this.checkpointGateway) {
2896
+ return null;
2897
+ }
2898
+ const gateway = this.checkpointGateway;
2899
+ if (gateway?.getRequestedVersion) {
2900
+ return gateway.getRequestedVersion(request);
2901
+ }
2902
+ const primary = request.headers.get("x-vector-checkpoint-version");
2903
+ if (primary && primary.trim().length > 0) {
2904
+ return primary.trim();
2905
+ }
2906
+ const fallback = request.headers.get("x-vector-checkpoint");
2907
+ if (fallback && fallback.trim().length > 0) {
2908
+ return fallback.trim();
2909
+ }
2910
+ return null;
2911
+ }
2912
+ getCheckpointCacheKeyOverrideValue(request) {
2913
+ if (!this.checkpointGateway) {
2914
+ return null;
2915
+ }
2916
+ const gateway = this.checkpointGateway;
2917
+ if (gateway?.getCacheKeyOverrideValue) {
2918
+ return gateway.getCacheKeyOverrideValue(request);
2919
+ }
2920
+ const primary = request.headers.get("x-vector-checkpoint-version");
2921
+ if (primary && primary.trim().length > 0) {
2922
+ return `x-vector-checkpoint-version:${primary.trim()}`;
2923
+ }
2924
+ const fallback = request.headers.get("x-vector-checkpoint");
2925
+ if (fallback && fallback.trim().length > 0) {
2926
+ return `x-vector-checkpoint:${fallback.trim()}`;
2927
+ }
2928
+ return null;
2929
+ }
2930
+ applyCheckpointCacheNamespace(cacheKey, request) {
2931
+ const checkpointVersion = this.getRequestedCheckpointVersion(request);
2932
+ if (!checkpointVersion) {
2933
+ return cacheKey;
2934
+ }
2935
+ return `${cacheKey}:checkpoint=${checkpointVersion}`;
2936
+ }
2937
+ applyCheckpointRouteKeyOverride(cacheKey, request) {
2938
+ const override = this.getCheckpointCacheKeyOverrideValue(request);
2939
+ if (!override) {
2940
+ return cacheKey;
2941
+ }
2942
+ return override;
2943
+ }
2944
+ async parseRequestBodyForContext(context, request, checkpointRequested) {
2945
+ let parsedContent = null;
1009
2946
  try {
1010
- pathname = (request._parsedUrl ?? new URL(request.url)).pathname;
2947
+ const bodyReadRequest = checkpointRequested ? request.clone() : request;
2948
+ const contentType = bodyReadRequest.headers.get("content-type");
2949
+ if (contentType?.startsWith("application/json")) {
2950
+ parsedContent = await bodyReadRequest.json();
2951
+ } else if (contentType?.startsWith("application/x-www-form-urlencoded")) {
2952
+ parsedContent = Object.fromEntries(await bodyReadRequest.formData());
2953
+ } else if (contentType?.startsWith("multipart/form-data")) {
2954
+ parsedContent = await bodyReadRequest.formData();
2955
+ } else {
2956
+ parsedContent = await bodyReadRequest.text();
2957
+ }
1011
2958
  } catch {
1012
- return;
2959
+ parsedContent = null;
1013
2960
  }
1014
- const matched = pathname.match(routeMatcher);
1015
- if (!matched?.groups) {
1016
- return;
2961
+ this.setContextField(context, "content", parsedContent);
2962
+ }
2963
+ isLikelyStreamingBodyRequest(request) {
2964
+ if (request.method === "GET" || request.method === "HEAD") {
2965
+ return false;
1017
2966
  }
1018
- return matched.groups;
2967
+ if (!request.body) {
2968
+ return false;
2969
+ }
2970
+ if (request.duplex === "half") {
2971
+ return true;
2972
+ }
2973
+ const transferEncoding = request.headers.get("transfer-encoding");
2974
+ if (transferEncoding) {
2975
+ const hasChunked = transferEncoding.split(",").some((value) => value.trim().toLowerCase() === "chunked");
2976
+ if (hasChunked) {
2977
+ return true;
2978
+ }
2979
+ }
2980
+ return false;
1019
2981
  }
1020
2982
  wrapHandler(options, handler) {
1021
2983
  const routePath = options.path;
1022
2984
  const routeMatcher = routePath.includes(":") ? buildRouteRegex(routePath) : null;
1023
2985
  return async (request) => {
1024
2986
  const vectorRequest = request;
1025
- const fallbackParams = this.resolveFallbackParams(request, routeMatcher);
1026
- this.prepareRequest(vectorRequest, {
1027
- params: fallbackParams,
1028
- route: routePath,
1029
- metadata: options.metadata
2987
+ let pathname = "";
2988
+ try {
2989
+ pathname = new URL(request.url).pathname;
2990
+ } catch {}
2991
+ const fallbackParams = this.resolveFallbackParams(pathname, routeMatcher);
2992
+ const context = this.createContext(vectorRequest, {
2993
+ metadata: options.metadata,
2994
+ params: this.getRequestParams(request, fallbackParams),
2995
+ query: this.getRequestQuery(request),
2996
+ cookies: this.getRequestCookies(request)
1030
2997
  });
1031
2998
  try {
1032
2999
  if (options.expose === false) {
1033
3000
  return APIError.forbidden("Forbidden");
1034
3001
  }
1035
- const beforeResult = await this.middlewareManager.executeBefore(vectorRequest);
1036
- if (beforeResult instanceof Response) {
1037
- return beforeResult;
3002
+ const beforeResponse = await this.middlewareManager.executeBefore(context);
3003
+ if (beforeResponse instanceof Response) {
3004
+ return beforeResponse;
1038
3005
  }
1039
- const req = beforeResult;
1040
3006
  if (options.auth) {
1041
3007
  try {
1042
- await this.authManager.authenticate(req);
3008
+ await this.authManager.authenticate(context);
1043
3009
  } catch (error) {
1044
3010
  return APIError.unauthorized(error instanceof Error ? error.message : "Authentication failed", options.responseContentType);
1045
3011
  }
1046
3012
  }
1047
- if (!options.rawRequest && req.method !== "GET" && req.method !== "HEAD") {
1048
- let parsedContent = null;
1049
- try {
1050
- const contentType = req.headers.get("content-type");
1051
- if (contentType?.startsWith("application/json")) {
1052
- parsedContent = await req.json();
1053
- } else if (contentType?.startsWith("application/x-www-form-urlencoded")) {
1054
- parsedContent = Object.fromEntries(await req.formData());
1055
- } else if (contentType?.startsWith("multipart/form-data")) {
1056
- parsedContent = await req.formData();
1057
- } else {
1058
- parsedContent = await req.text();
3013
+ const executeRoute = async () => {
3014
+ const req = context.request;
3015
+ const requestForRoute = req;
3016
+ const checkpointRequested = this.getRequestedCheckpointVersion(requestForRoute) !== null;
3017
+ const shouldDeferStreamingValidation = this.isLikelyStreamingBodyRequest(requestForRoute) && options.schema?.input !== undefined && options.validate !== false;
3018
+ if (!options.rawRequest && req.method !== "GET" && req.method !== "HEAD" && !shouldDeferStreamingValidation) {
3019
+ await this.parseRequestBodyForContext(context, requestForRoute, checkpointRequested);
3020
+ }
3021
+ if (shouldDeferStreamingValidation) {
3022
+ const validationWithoutBody = await this.validateInputSchema(context, options, fallbackParams, {
3023
+ includeBody: false,
3024
+ allowBodyDeferral: true
3025
+ });
3026
+ if (validationWithoutBody.response) {
3027
+ return validationWithoutBody.response;
3028
+ }
3029
+ if (validationWithoutBody.requiresBody) {
3030
+ if (!options.rawRequest && req.method !== "GET" && req.method !== "HEAD") {
3031
+ await this.parseRequestBodyForContext(context, requestForRoute, checkpointRequested);
3032
+ }
3033
+ const fullValidation = await this.validateInputSchema(context, options, fallbackParams);
3034
+ if (fullValidation.response) {
3035
+ return fullValidation.response;
3036
+ }
3037
+ }
3038
+ } else {
3039
+ const inputValidation = await this.validateInputSchema(context, options, fallbackParams);
3040
+ if (inputValidation.response) {
3041
+ return inputValidation.response;
1059
3042
  }
1060
- } catch {
1061
- parsedContent = null;
1062
3043
  }
1063
- this.setContentAndBodyAlias(req, parsedContent);
1064
- }
1065
- const inputValidationResponse = await this.validateInputSchema(req, options);
1066
- if (inputValidationResponse) {
1067
- return inputValidationResponse;
1068
- }
3044
+ if (this.checkpointGateway) {
3045
+ const checkpointResponse = await this.checkpointGateway.handle(req, this.buildCheckpointContextPayload(context));
3046
+ if (checkpointResponse) {
3047
+ return checkpointResponse;
3048
+ }
3049
+ }
3050
+ return await handler(context);
3051
+ };
1069
3052
  let result;
1070
3053
  const cacheOptions = options.cache;
1071
3054
  if (cacheOptions && typeof cacheOptions === "number" && cacheOptions > 0) {
1072
- const cacheKey = this.cacheManager.generateKey(req, {
1073
- authUser: req.authUser
1074
- });
1075
- result = await this.cacheManager.get(cacheKey, async () => {
1076
- const res = await handler(req);
1077
- if (res instanceof Response) {
1078
- return {
1079
- _isResponse: true,
1080
- body: await res.text(),
1081
- status: res.status,
1082
- headers: Object.fromEntries(res.headers.entries())
1083
- };
1084
- }
1085
- return res;
1086
- }, cacheOptions);
3055
+ const cacheKey = this.applyCheckpointCacheNamespace(this.cacheManager.generateKey(context.request, {
3056
+ authUser: context.authUser
3057
+ }), context.request);
3058
+ result = await this.cacheManager.get(cacheKey, async () => await executeRoute(), cacheOptions);
1087
3059
  } else if (cacheOptions && typeof cacheOptions === "object" && cacheOptions.ttl) {
1088
- const cacheKey = cacheOptions.key || this.cacheManager.generateKey(req, {
1089
- authUser: req.authUser
1090
- });
1091
- result = await this.cacheManager.get(cacheKey, async () => {
1092
- const res = await handler(req);
1093
- if (res instanceof Response) {
1094
- return {
1095
- _isResponse: true,
1096
- body: await res.text(),
1097
- status: res.status,
1098
- headers: Object.fromEntries(res.headers.entries())
1099
- };
1100
- }
1101
- return res;
1102
- }, cacheOptions.ttl);
3060
+ const hasRouteCacheKey = typeof cacheOptions.key === "string" && cacheOptions.key.length > 0;
3061
+ let cacheKey;
3062
+ if (hasRouteCacheKey) {
3063
+ cacheKey = this.applyCheckpointRouteKeyOverride(cacheOptions.key, context.request);
3064
+ } else {
3065
+ const generatedKey = this.cacheManager.generateKey(context.request, {
3066
+ authUser: context.authUser
3067
+ });
3068
+ cacheKey = this.applyCheckpointCacheNamespace(generatedKey, context.request);
3069
+ }
3070
+ result = await this.cacheManager.get(cacheKey, async () => await executeRoute(), cacheOptions.ttl);
1103
3071
  } else {
1104
- result = await handler(req);
3072
+ result = await executeRoute();
1105
3073
  }
1106
- if (result && typeof result === "object" && result._isResponse === true) {
1107
- result = new Response(result.body, {
1108
- status: result.status,
1109
- headers: result.headers
1110
- });
3074
+ if (result instanceof Response && !!cacheOptions) {
3075
+ result = result.clone();
1111
3076
  }
1112
3077
  let response;
1113
3078
  if (options.rawResponse || result instanceof Response) {
@@ -1115,19 +3080,8 @@ class VectorRouter {
1115
3080
  } else {
1116
3081
  response = createResponse(200, result, options.responseContentType);
1117
3082
  }
1118
- response = await this.middlewareManager.executeFinally(response, req);
1119
- const entries = this.corsHeadersEntries;
1120
- if (entries) {
1121
- for (const [k, v] of entries) {
1122
- response.headers.set(k, v);
1123
- }
1124
- } else {
1125
- const dynamicCors = this.corsHandler;
1126
- if (dynamicCors) {
1127
- response = dynamicCors(response, req);
1128
- }
1129
- }
1130
- return response;
3083
+ response = await this.middlewareManager.executeFinally(response, context);
3084
+ return this.applyCorsResponse(response, context.request);
1131
3085
  } catch (error) {
1132
3086
  if (error instanceof Response) {
1133
3087
  return error;
@@ -1144,9 +3098,11 @@ class VectorRouter {
1144
3098
  const nodeEnv = typeof Bun !== "undefined" ? Bun.env.NODE_ENV : "development";
1145
3099
  return nodeEnv !== "production";
1146
3100
  }
1147
- async buildInputValidationPayload(request, options) {
1148
- let body = request.content;
1149
- if (options.rawRequest && request.method !== "GET" && request.method !== "HEAD") {
3101
+ async buildInputValidationPayload(context, options, fallbackParams, validationOptions) {
3102
+ const request = context.request;
3103
+ const includeBody = validationOptions?.includeBody !== false;
3104
+ let body = includeBody && this.hasOwnContextField(context, "content") ? context.content : undefined;
3105
+ if (includeBody && options.rawRequest && request.method !== "GET" && request.method !== "HEAD") {
1150
3106
  try {
1151
3107
  body = await request.clone().text();
1152
3108
  } catch {
@@ -1154,79 +3110,150 @@ class VectorRouter {
1154
3110
  }
1155
3111
  }
1156
3112
  return {
1157
- params: request.params ?? {},
1158
- query: request.query ?? {},
3113
+ params: this.getRequestParams(request, fallbackParams),
3114
+ query: this.getRequestQuery(request),
1159
3115
  headers: Object.fromEntries(request.headers.entries()),
1160
- cookies: request.cookies ?? {},
3116
+ cookies: this.getRequestCookies(request),
1161
3117
  body
1162
3118
  };
1163
3119
  }
1164
- applyValidatedInput(request, validatedValue) {
1165
- request.validatedInput = validatedValue;
1166
- if (!validatedValue || typeof validatedValue !== "object") {
3120
+ getRequestParams(request, fallbackParams) {
3121
+ const nativeParams = this.readRequestObjectField(request, "params");
3122
+ if (nativeParams && Object.keys(nativeParams).length > 0) {
3123
+ return nativeParams;
3124
+ }
3125
+ return fallbackParams ?? {};
3126
+ }
3127
+ getRequestQuery(request) {
3128
+ const nativeQuery = this.readRequestObjectField(request, "query");
3129
+ if (nativeQuery) {
3130
+ return nativeQuery;
3131
+ }
3132
+ try {
3133
+ return VectorRouter.parseQuery(new URL(request.url));
3134
+ } catch {
3135
+ return {};
3136
+ }
3137
+ }
3138
+ getRequestCookies(request) {
3139
+ const nativeCookies = this.readRequestObjectField(request, "cookies");
3140
+ if (nativeCookies) {
3141
+ return nativeCookies;
3142
+ }
3143
+ return VectorRouter.parseCookies(request.headers.get("cookie"));
3144
+ }
3145
+ readRequestObjectField(request, key) {
3146
+ const value = request[key];
3147
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1167
3148
  return;
1168
3149
  }
1169
- const validated = validatedValue;
1170
- if ("params" in validated) {
1171
- try {
1172
- request.params = validated.params;
1173
- } catch {}
3150
+ return value;
3151
+ }
3152
+ applyValidatedInput(context, validatedValue) {
3153
+ this.setContextField(context, "validatedInput", validatedValue);
3154
+ }
3155
+ issueHasBodyPath(issue) {
3156
+ if (!issue || typeof issue !== "object" || !("path" in issue)) {
3157
+ return false;
1174
3158
  }
1175
- if ("query" in validated) {
1176
- try {
1177
- request.query = validated.query;
1178
- } catch {}
3159
+ const path = issue.path;
3160
+ if (!Array.isArray(path) || path.length === 0) {
3161
+ return false;
1179
3162
  }
1180
- if ("cookies" in validated) {
1181
- try {
1182
- request.cookies = validated.cookies;
1183
- } catch {}
3163
+ const segment = path[0];
3164
+ if (segment && typeof segment === "object" && "key" in segment) {
3165
+ return segment.key === "body";
3166
+ }
3167
+ return segment === "body";
3168
+ }
3169
+ issueHasExplicitNonBodyPath(issue) {
3170
+ if (!issue || typeof issue !== "object" || !("path" in issue)) {
3171
+ return false;
1184
3172
  }
1185
- if ("body" in validated) {
1186
- this.setContentAndBodyAlias(request, validated.body);
3173
+ const path = issue.path;
3174
+ if (!Array.isArray(path) || path.length === 0) {
3175
+ return false;
3176
+ }
3177
+ const segment = path[0];
3178
+ if (segment && typeof segment === "object" && "key" in segment) {
3179
+ return segment.key !== "body";
1187
3180
  }
3181
+ return segment !== "body";
1188
3182
  }
1189
- setContentAndBodyAlias(request, value) {
1190
- try {
1191
- request.content = value;
1192
- } catch {
1193
- return;
3183
+ issueHasUnknownPath(issue) {
3184
+ if (!issue || typeof issue !== "object" || !("path" in issue)) {
3185
+ return true;
1194
3186
  }
1195
- this.setBodyAlias(request, value);
3187
+ const path = issue.path;
3188
+ if (!Array.isArray(path)) {
3189
+ return true;
3190
+ }
3191
+ return path.length === 0;
1196
3192
  }
1197
- setBodyAlias(request, value) {
1198
- try {
1199
- request.body = value;
1200
- } catch {}
3193
+ shouldDeferBodyValidation(issues, context, validationOptions) {
3194
+ if (!(validationOptions?.allowBodyDeferral === true && validationOptions?.includeBody === false)) {
3195
+ return false;
3196
+ }
3197
+ const request = context.request;
3198
+ const mayHaveRequestBody = request.method !== "GET" && request.method !== "HEAD" && request.body !== null;
3199
+ if (!mayHaveRequestBody || issues.length === 0) {
3200
+ return false;
3201
+ }
3202
+ if (issues.some((issue) => this.issueHasBodyPath(issue))) {
3203
+ return true;
3204
+ }
3205
+ const hasExplicitNonBodyPath = issues.some((issue) => this.issueHasExplicitNonBodyPath(issue));
3206
+ const hasUnknownPath = issues.some((issue) => this.issueHasUnknownPath(issue));
3207
+ return !hasExplicitNonBodyPath && hasUnknownPath;
1201
3208
  }
1202
- async validateInputSchema(request, options) {
3209
+ async validateInputSchema(context, options, fallbackParams, validationOptions) {
1203
3210
  const inputSchema = options.schema?.input;
1204
3211
  if (!inputSchema) {
1205
- return null;
3212
+ return { response: null, requiresBody: false };
1206
3213
  }
1207
3214
  if (options.validate === false) {
1208
- return null;
3215
+ return { response: null, requiresBody: false };
1209
3216
  }
1210
3217
  if (!isStandardRouteSchema(inputSchema)) {
1211
- return APIError.internalServerError("Invalid route schema configuration", options.responseContentType);
3218
+ return {
3219
+ response: APIError.internalServerError("Invalid route schema configuration", options.responseContentType),
3220
+ requiresBody: false
3221
+ };
1212
3222
  }
1213
3223
  const includeRawIssues = this.isDevelopmentMode();
1214
- const payload = await this.buildInputValidationPayload(request, options);
3224
+ const payload = await this.buildInputValidationPayload(context, options, fallbackParams, {
3225
+ includeBody: validationOptions?.includeBody
3226
+ });
1215
3227
  try {
1216
3228
  const validation = await runStandardValidation(inputSchema, payload);
1217
3229
  if (validation.success === false) {
3230
+ if (this.shouldDeferBodyValidation(validation.issues, context, validationOptions)) {
3231
+ return { response: null, requiresBody: true };
3232
+ }
1218
3233
  const issues = normalizeValidationIssues(validation.issues, includeRawIssues);
1219
- return createResponse(422, createValidationErrorPayload("input", issues), options.responseContentType);
3234
+ return {
3235
+ response: createResponse(422, createValidationErrorPayload("input", issues), options.responseContentType),
3236
+ requiresBody: false
3237
+ };
1220
3238
  }
1221
- this.applyValidatedInput(request, validation.value);
1222
- return null;
3239
+ this.applyValidatedInput(context, validation.value);
3240
+ return { response: null, requiresBody: false };
1223
3241
  } catch (error) {
1224
3242
  const thrownIssues = extractThrownIssues(error);
1225
3243
  if (thrownIssues) {
3244
+ if (this.shouldDeferBodyValidation(thrownIssues, context, validationOptions)) {
3245
+ return { response: null, requiresBody: true };
3246
+ }
1226
3247
  const issues = normalizeValidationIssues(thrownIssues, includeRawIssues);
1227
- return createResponse(422, createValidationErrorPayload("input", issues), options.responseContentType);
3248
+ return {
3249
+ response: createResponse(422, createValidationErrorPayload("input", issues), options.responseContentType),
3250
+ requiresBody: false
3251
+ };
1228
3252
  }
1229
- return APIError.internalServerError(error instanceof Error ? error.message : "Validation failed", options.responseContentType);
3253
+ return {
3254
+ response: APIError.internalServerError(error instanceof Error ? error.message : "Validation failed", options.responseContentType),
3255
+ requiresBody: false
3256
+ };
1230
3257
  }
1231
3258
  }
1232
3259
  getOrCreateMethodMap(path) {
@@ -1277,6 +3304,19 @@ class VectorRouter {
1277
3304
  }
1278
3305
  return query;
1279
3306
  }
3307
+ static parseCookies(cookieHeader) {
3308
+ const cookies = {};
3309
+ if (!cookieHeader) {
3310
+ return cookies;
3311
+ }
3312
+ for (const pair of cookieHeader.split(";")) {
3313
+ const idx = pair.indexOf("=");
3314
+ if (idx > 0) {
3315
+ cookies[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
3316
+ }
3317
+ }
3318
+ return cookies;
3319
+ }
1280
3320
  routeSpecificityScore(path) {
1281
3321
  const STATIC_SEGMENT_WEIGHT = 1000;
1282
3322
  const PARAM_SEGMENT_WEIGHT = 10;
@@ -1299,6 +3339,20 @@ class VectorRouter {
1299
3339
  }
1300
3340
  return score;
1301
3341
  }
3342
+ applyCorsResponse(response, request) {
3343
+ const entries = this.corsHeadersEntries;
3344
+ if (entries) {
3345
+ for (const [k, v] of entries) {
3346
+ response.headers.set(k, v);
3347
+ }
3348
+ return response;
3349
+ }
3350
+ const dynamicCors = this.corsHandler;
3351
+ if (dynamicCors) {
3352
+ return dynamicCors(response, request);
3353
+ }
3354
+ return response;
3355
+ }
1302
3356
  }
1303
3357
 
1304
3358
  // src/core/server.ts
@@ -1525,6 +3579,124 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1525
3579
  .dark .json-number { color: #7dc9ff; }
1526
3580
  .dark .json-boolean { color: #93a4bf; }
1527
3581
  .dark .json-null { color: #7c8ba3; }
3582
+ .param-row {
3583
+ --param-row-bg-rgb: 255 255 255;
3584
+ }
3585
+ .dark .param-row {
3586
+ --param-row-bg-rgb: 10 10 10;
3587
+ }
3588
+ .param-row-head {
3589
+ display: grid;
3590
+ grid-template-columns: minmax(0, 1fr) auto;
3591
+ align-items: center;
3592
+ gap: 0.5rem;
3593
+ min-width: 0;
3594
+ width: 100%;
3595
+ }
3596
+ .param-row-main {
3597
+ min-width: 0;
3598
+ display: flex;
3599
+ align-items: center;
3600
+ gap: 0.4rem;
3601
+ overflow: hidden;
3602
+ }
3603
+ .param-tooltip-trigger {
3604
+ border: 0;
3605
+ margin: 0;
3606
+ padding: 0;
3607
+ background: transparent;
3608
+ color: inherit;
3609
+ cursor: pointer;
3610
+ font: inherit;
3611
+ text-align: left;
3612
+ min-width: 0;
3613
+ }
3614
+ .param-tooltip-trigger:focus-visible {
3615
+ outline: 2px solid rgba(0, 161, 255, 0.65);
3616
+ outline-offset: 2px;
3617
+ border-radius: 0.375rem;
3618
+ }
3619
+ .param-name-trigger {
3620
+ min-width: 0;
3621
+ flex: 1 1 auto;
3622
+ overflow: hidden;
3623
+ }
3624
+ .param-name-text {
3625
+ display: block;
3626
+ max-width: 100%;
3627
+ overflow: hidden;
3628
+ text-overflow: ellipsis;
3629
+ white-space: nowrap;
3630
+ mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 18px), transparent 100%);
3631
+ -webkit-mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 18px), transparent 100%);
3632
+ }
3633
+ .param-type-fade {
3634
+ position: relative;
3635
+ z-index: 1;
3636
+ display: block;
3637
+ max-width: none;
3638
+ overflow: visible;
3639
+ text-overflow: clip;
3640
+ padding-left: 1.25rem;
3641
+ white-space: nowrap;
3642
+ text-align: left;
3643
+ justify-self: end;
3644
+ background: linear-gradient(
3645
+ 90deg,
3646
+ rgba(var(--param-row-bg-rgb), 0) 0%,
3647
+ rgba(var(--param-row-bg-rgb), 0.76) 36%,
3648
+ rgba(var(--param-row-bg-rgb), 0.94) 68%,
3649
+ rgba(var(--param-row-bg-rgb), 1) 100%
3650
+ );
3651
+ }
3652
+ #param-value-tooltip {
3653
+ position: fixed;
3654
+ top: 0;
3655
+ left: 0;
3656
+ z-index: 70;
3657
+ width: min(42rem, calc(100vw - 0.75rem));
3658
+ border-radius: 0.5rem;
3659
+ border: 1px solid rgba(15, 23, 42, 0.12);
3660
+ background: rgba(255, 255, 255, 0.92);
3661
+ color: #111111;
3662
+ box-shadow: 0 10px 20px rgba(15, 23, 42, 0.14);
3663
+ backdrop-filter: blur(12px) saturate(145%);
3664
+ -webkit-backdrop-filter: blur(12px) saturate(145%);
3665
+ padding: 0.4rem 0.6rem;
3666
+ opacity: 0;
3667
+ pointer-events: none;
3668
+ transform: translateY(6px) scale(0.98);
3669
+ transition:
3670
+ opacity var(--motion-fast) var(--motion-ease),
3671
+ transform var(--motion-fast) var(--motion-ease);
3672
+ }
3673
+ #param-value-tooltip.is-visible {
3674
+ opacity: 1;
3675
+ pointer-events: auto;
3676
+ transform: translateY(0) scale(1);
3677
+ }
3678
+ .dark #param-value-tooltip {
3679
+ border-color: rgba(148, 163, 184, 0.24);
3680
+ background: rgba(17, 17, 17, 0.9);
3681
+ color: #ededed;
3682
+ box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45);
3683
+ }
3684
+ #param-tooltip-line {
3685
+ margin: 0;
3686
+ font-size: 11px;
3687
+ line-height: 1.3;
3688
+ font-family: "JetBrains Mono", monospace;
3689
+ white-space: normal;
3690
+ word-break: break-word;
3691
+ }
3692
+ #param-tooltip-description {
3693
+ margin: 0.2rem 0 0;
3694
+ font-size: 11px;
3695
+ line-height: 1.3;
3696
+ opacity: 0.8;
3697
+ white-space: normal;
3698
+ word-break: break-word;
3699
+ }
1528
3700
  </style>
1529
3701
  </head>
1530
3702
  <body class="bg-light-bg text-light-text dark:bg-dark-bg dark:text-dark-text font-sans antialiased flex h-screen overflow-hidden">
@@ -1554,6 +3726,16 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1554
3726
  />
1555
3727
  </div>
1556
3728
  </div>
3729
+ <div id="auth-panel" class="border-b border-light-border dark:border-dark-border">
3730
+ <button id="auth-toggle" class="w-full flex items-center justify-between px-4 py-2.5 text-xs font-semibold uppercase tracking-wider opacity-60 hover:opacity-100 transition-opacity">
3731
+ <span class="flex items-center gap-1.5">
3732
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
3733
+ Auth
3734
+ </span>
3735
+ <svg id="auth-chevron" class="w-3.5 h-3.5 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
3736
+ </button>
3737
+ <div id="auth-fields" class="px-4 pb-3 space-y-2"></div>
3738
+ </div>
1557
3739
  <nav class="flex-1 overflow-y-auto px-3 py-2 space-y-6 text-sm" id="sidebar-nav"></nav>
1558
3740
  </aside>
1559
3741
 
@@ -1587,6 +3769,9 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1587
3769
  <span id="endpoint-method" class="px-2.5 py-0.5 rounded-full text-xs font-mono font-medium"></span>
1588
3770
  <h2 class="text-xl font-semibold tracking-tight" id="endpoint-title">Operation</h2>
1589
3771
  </div>
3772
+ <div id="deprecated-banner" class="hidden mb-4 px-3 py-2 rounded border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 text-xs font-medium">
3773
+ ! This operation is deprecated
3774
+ </div>
1590
3775
  <p class="text-sm opacity-80 mb-8 font-mono" id="endpoint-path">/</p>
1591
3776
  <div class="grid grid-cols-1 lg:grid-cols-12 gap-10">
1592
3777
  <div class="lg:col-span-5 space-y-8" id="params-column"></div>
@@ -1682,6 +3867,10 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1682
3867
  <pre id="expand-viewer" class="hidden w-full h-[70vh] text-sm p-3 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-auto font-mono"></pre>
1683
3868
  </div>
1684
3869
  </div>
3870
+ <div id="param-value-tooltip" aria-hidden="true" role="tooltip">
3871
+ <p id="param-tooltip-line"></p>
3872
+ <p id="param-tooltip-description" class="hidden"></p>
3873
+ </div>
1685
3874
 
1686
3875
  <script>
1687
3876
  const spec = ${specJson};
@@ -1752,14 +3941,71 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1752
3941
  return ops;
1753
3942
  }
1754
3943
 
3944
+ const AUTH_STATE_KEY = "vector-docs-auth-v1";
3945
+ const AUTH_SELECTION_KEY = "vector-docs-auth-selection-v1";
3946
+ const HEADERS_STATE_KEY = "vector-docs-headers-v1";
3947
+
3948
+ function loadSavedHeaders() {
3949
+ try {
3950
+ const raw = localStorage.getItem(HEADERS_STATE_KEY);
3951
+ if (raw) {
3952
+ const parsed = JSON.parse(raw);
3953
+ if (Array.isArray(parsed) && parsed.length > 0) return parsed;
3954
+ }
3955
+ } catch {}
3956
+ return [{ key: "", value: "" }];
3957
+ }
3958
+
3959
+ function saveHeaders() {
3960
+ try { localStorage.setItem(HEADERS_STATE_KEY, JSON.stringify(requestHeaders)); } catch {}
3961
+ }
3962
+
3963
+ function loadAuthState() {
3964
+ try {
3965
+ const raw = localStorage.getItem(AUTH_STATE_KEY);
3966
+ if (raw) return JSON.parse(raw);
3967
+ } catch {}
3968
+ return {};
3969
+ }
3970
+
3971
+ function saveAuthState() {
3972
+ try { localStorage.setItem(AUTH_STATE_KEY, JSON.stringify(authState)); } catch {}
3973
+ }
3974
+
3975
+ function loadAuthSelectionState() {
3976
+ try {
3977
+ const raw = localStorage.getItem(AUTH_SELECTION_KEY);
3978
+ if (raw) {
3979
+ const parsed = JSON.parse(raw);
3980
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3981
+ return parsed;
3982
+ }
3983
+ }
3984
+ } catch {}
3985
+ return {};
3986
+ }
3987
+
3988
+ function saveAuthSelectionState() {
3989
+ try { localStorage.setItem(AUTH_SELECTION_KEY, JSON.stringify(authSelectionState)); } catch {}
3990
+ }
3991
+
3992
+ const authSchemes = (spec.components && spec.components.securitySchemes) || {};
3993
+ let authState = loadAuthState();
3994
+ let authSelectionState = loadAuthSelectionState();
3995
+
1755
3996
  const operations = getOperations();
1756
3997
  let selected = operations[0] || null;
1757
3998
  const operationParamValues = new Map();
1758
3999
  const operationBodyDrafts = new Map();
1759
- const requestHeaders = [{ key: "Authorization", value: "" }];
4000
+ const requestHeaders = loadSavedHeaders();
1760
4001
  let expandModalMode = null;
1761
4002
  let isMobileSidebarOpen = false;
1762
4003
  let sidebarSearchQuery = "";
4004
+ const paramTooltipRoot = document.getElementById("param-value-tooltip");
4005
+ const paramTooltipLine = document.getElementById("param-tooltip-line");
4006
+ const paramTooltipDescription = document.getElementById("param-tooltip-description");
4007
+ let activeParamTooltipTrigger = null;
4008
+ let paramTooltipHideTimer = null;
1763
4009
 
1764
4010
  function setMobileSidebarOpen(open) {
1765
4011
  const sidebar = document.getElementById("docs-sidebar");
@@ -1779,6 +4025,29 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1779
4025
  return op.method + " " + op.path;
1780
4026
  }
1781
4027
 
4028
+ function getOpHash(op) {
4029
+ var tag = op.tag || "default";
4030
+ var id = (op.operation && op.operation.operationId)
4031
+ ? op.operation.operationId
4032
+ : op.method.toLowerCase() + "_" + op.path.split("/").filter(Boolean).join("_").replace(/[{}]/g, "");
4033
+ return "#/" + encodeURIComponent(tag) + "/" + encodeURIComponent(id);
4034
+ }
4035
+
4036
+ function findOpByHash(hash) {
4037
+ if (!hash || hash.length <= 1) return null;
4038
+ var parts = hash.slice(1).split("/").filter(Boolean);
4039
+ if (parts.length < 2) return null;
4040
+ var hashTag = decodeURIComponent(parts[0]);
4041
+ var hashId = decodeURIComponent(parts[1]);
4042
+ return operations.find(function(op) {
4043
+ if (op.tag !== hashTag) return false;
4044
+ var id = (op.operation && op.operation.operationId)
4045
+ ? op.operation.operationId
4046
+ : op.method.toLowerCase() + "_" + op.path.split("/").filter(Boolean).join("_").replace(/[{}]/g, "");
4047
+ return id === hashId;
4048
+ }) || null;
4049
+ }
4050
+
1782
4051
  function getOperationParameterGroups(op) {
1783
4052
  const params =
1784
4053
  op &&
@@ -1828,7 +4097,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1828
4097
  return resolved;
1829
4098
  }
1830
4099
 
1831
- function buildRequestPath(op, pathParams, queryParams, values) {
4100
+ function buildRequestPath(op, pathParams, queryParams, values, extraQuery) {
1832
4101
  const resolvedPath = resolvePath(op.path, pathParams, values);
1833
4102
  const query = new URLSearchParams();
1834
4103
 
@@ -1840,6 +4109,13 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1840
4109
  query.append(param.name, String(rawValue));
1841
4110
  }
1842
4111
 
4112
+ if (extraQuery) {
4113
+ for (const key of Object.keys(extraQuery)) {
4114
+ const val = extraQuery[key];
4115
+ if (val) query.set(key, String(val));
4116
+ }
4117
+ }
4118
+
1843
4119
  const queryString = query.toString();
1844
4120
  return queryString ? resolvedPath + "?" + queryString : resolvedPath;
1845
4121
  }
@@ -1979,14 +4255,25 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
1979
4255
 
1980
4256
  const name = document.createElement("span");
1981
4257
  name.textContent = op.name;
4258
+ if (op.operation && op.operation.deprecated) {
4259
+ name.style.textDecoration = "line-through";
4260
+ name.style.opacity = "0.5";
4261
+ }
1982
4262
 
1983
4263
  row.appendChild(method);
1984
4264
  row.appendChild(name);
4265
+ if (op.operation && op.operation.deprecated) {
4266
+ const badge = document.createElement("span");
4267
+ badge.className = "text-[9px] px-1 py-0.5 rounded bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-500 font-semibold shrink-0";
4268
+ badge.textContent = "deprecated";
4269
+ row.appendChild(badge);
4270
+ }
1985
4271
  a.appendChild(row);
1986
4272
 
1987
4273
  a.onclick = (e) => {
1988
4274
  e.preventDefault();
1989
4275
  selected = op;
4276
+ history.pushState(null, "", getOpHash(op));
1990
4277
  renderSidebar();
1991
4278
  renderEndpoint();
1992
4279
  if (window.innerWidth < 768) {
@@ -2000,51 +4287,257 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2000
4287
  }
2001
4288
  }
2002
4289
 
4290
+ function hideParamTooltip() {
4291
+ if (!paramTooltipRoot) return;
4292
+ if (paramTooltipHideTimer) {
4293
+ window.clearTimeout(paramTooltipHideTimer);
4294
+ paramTooltipHideTimer = null;
4295
+ }
4296
+ paramTooltipRoot.classList.remove("is-visible");
4297
+ paramTooltipRoot.setAttribute("aria-hidden", "true");
4298
+ if (activeParamTooltipTrigger) {
4299
+ activeParamTooltipTrigger.setAttribute("aria-expanded", "false");
4300
+ }
4301
+ activeParamTooltipTrigger = null;
4302
+ }
4303
+
4304
+ function scheduleParamTooltipHide() {
4305
+ if (paramTooltipHideTimer) {
4306
+ window.clearTimeout(paramTooltipHideTimer);
4307
+ }
4308
+ paramTooltipHideTimer = window.setTimeout(() => {
4309
+ hideParamTooltip();
4310
+ }, 95);
4311
+ }
4312
+
4313
+ function positionParamTooltip(trigger) {
4314
+ if (!paramTooltipRoot || !trigger) return;
4315
+ const viewportPadding = 8;
4316
+ const spacing = 10;
4317
+ const triggerRect = trigger.getBoundingClientRect();
4318
+ const tooltipRect = paramTooltipRoot.getBoundingClientRect();
4319
+ let left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
4320
+ left = Math.max(viewportPadding, Math.min(left, window.innerWidth - tooltipRect.width - viewportPadding));
4321
+ let top = triggerRect.top - tooltipRect.height - spacing;
4322
+ if (top < viewportPadding) {
4323
+ top = triggerRect.bottom + spacing;
4324
+ }
4325
+ if (top + tooltipRect.height > window.innerHeight - viewportPadding) {
4326
+ top = window.innerHeight - tooltipRect.height - viewportPadding;
4327
+ }
4328
+ paramTooltipRoot.style.left = Math.round(left) + "px";
4329
+ paramTooltipRoot.style.top = Math.round(top) + "px";
4330
+ }
4331
+
4332
+ function showParamTooltip(trigger) {
4333
+ if (
4334
+ !paramTooltipRoot ||
4335
+ !paramTooltipLine ||
4336
+ !paramTooltipDescription ||
4337
+ !trigger
4338
+ ) {
4339
+ return;
4340
+ }
4341
+ if (paramTooltipHideTimer) {
4342
+ window.clearTimeout(paramTooltipHideTimer);
4343
+ paramTooltipHideTimer = null;
4344
+ }
4345
+ const label = trigger.getAttribute("data-param-tooltip-label") || "Value";
4346
+ const value = trigger.getAttribute("data-param-tooltip-value") || "";
4347
+ const related = trigger.getAttribute("data-param-tooltip-related") || "";
4348
+ const description = trigger.getAttribute("data-param-tooltip-description") || "";
4349
+ if (activeParamTooltipTrigger && activeParamTooltipTrigger !== trigger) {
4350
+ activeParamTooltipTrigger.setAttribute("aria-expanded", "false");
4351
+ }
4352
+ activeParamTooltipTrigger = trigger;
4353
+ activeParamTooltipTrigger.setAttribute("aria-expanded", "true");
4354
+ const pathLabel = related ? " | path: " + related : "";
4355
+ paramTooltipLine.textContent = label + ": " + value + pathLabel;
4356
+ if (description.trim()) {
4357
+ paramTooltipDescription.textContent = description;
4358
+ paramTooltipDescription.classList.remove("hidden");
4359
+ } else {
4360
+ paramTooltipDescription.textContent = "";
4361
+ paramTooltipDescription.classList.add("hidden");
4362
+ }
4363
+ paramTooltipRoot.classList.add("is-visible");
4364
+ paramTooltipRoot.setAttribute("aria-hidden", "false");
4365
+ positionParamTooltip(trigger);
4366
+ }
4367
+
4368
+ function registerParamTooltipTargets(scope) {
4369
+ if (!scope) return;
4370
+ const targets = scope.querySelectorAll("[data-param-tooltip-value]");
4371
+ for (const target of targets) {
4372
+ target.addEventListener("click", (event) => {
4373
+ event.preventDefault();
4374
+ if (
4375
+ activeParamTooltipTrigger === target &&
4376
+ paramTooltipRoot &&
4377
+ paramTooltipRoot.classList.contains("is-visible")
4378
+ ) {
4379
+ hideParamTooltip();
4380
+ return;
4381
+ }
4382
+ showParamTooltip(target);
4383
+ });
4384
+ target.addEventListener("mouseenter", () => {
4385
+ showParamTooltip(target);
4386
+ });
4387
+ target.addEventListener("mouseleave", (event) => {
4388
+ const related = event.relatedTarget;
4389
+ if (paramTooltipRoot && related && paramTooltipRoot.contains(related)) return;
4390
+ scheduleParamTooltipHide();
4391
+ });
4392
+ target.addEventListener("focus", () => {
4393
+ showParamTooltip(target);
4394
+ });
4395
+ target.addEventListener("blur", (event) => {
4396
+ const related = event.relatedTarget;
4397
+ if (paramTooltipRoot && related && paramTooltipRoot.contains(related)) return;
4398
+ scheduleParamTooltipHide();
4399
+ });
4400
+ }
4401
+ }
4402
+
2003
4403
  function renderParamSection(title, params) {
2004
4404
  if (!params.length) return "";
2005
4405
  let rows = "";
2006
4406
  for (const p of params) {
2007
- const type = escapeHtml((p.schema && p.schema.type) || "unknown");
2008
- const name = escapeHtml(p.name || "");
2009
- rows += '<div class="py-2 flex justify-between border-b border-light-border/50 dark:border-dark-border/50"><div><code class="text-sm font-mono">' + name + '</code><span class="text-xs text-brand ml-2">' + (p.required ? "required" : "optional") + '</span></div><span class="text-xs font-mono opacity-60">' + type + '</span></div>';
4407
+ const schema = resolveSchemaRef(p.schema || {});
4408
+ const typeRaw = getSchemaTypeLabel(schema);
4409
+ const type = escapeHtml(typeRaw);
4410
+ const nameRaw = p.name || "";
4411
+ const name = escapeHtml(nameRaw);
4412
+ const tooltipName = escapeHtmlAttribute(nameRaw);
4413
+ const tooltipType = escapeHtmlAttribute(typeRaw);
4414
+ const tooltipDescription = (typeof p.description === "string" && p.description.trim())
4415
+ ? escapeHtmlAttribute(p.description.trim())
4416
+ : (typeof schema.description === "string" && schema.description.trim())
4417
+ ? escapeHtmlAttribute(schema.description.trim())
4418
+ : "";
4419
+ const desc = (typeof p.description === "string" && p.description.trim())
4420
+ ? '<p class="text-xs opacity-60 mt-0.5 leading-snug">' + renderMarkdown(p.description.trim()) + '</p>'
4421
+ : "";
4422
+ const extra = buildSchemaExtra(schema);
4423
+ rows +=
4424
+ '<div class="param-row py-2 border-b border-light-border/50 dark:border-dark-border/50">' +
4425
+ '<div class="param-row-head">' +
4426
+ '<div class="param-row-main">' +
4427
+ '<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Parameter" data-param-tooltip-value="' +
4428
+ tooltipName +
4429
+ '" data-param-tooltip-description="' +
4430
+ tooltipDescription +
4431
+ '" aria-expanded="false">' +
4432
+ '<code class="text-sm font-mono param-name-text">' +
4433
+ name +
4434
+ "</code></button>" +
4435
+ '<span class="text-xs text-brand shrink-0">' +
4436
+ (p.required ? "required" : "optional") +
4437
+ "</span></div>" +
4438
+ '<button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
4439
+ tooltipType +
4440
+ '" data-param-tooltip-description="' +
4441
+ tooltipDescription +
4442
+ '" aria-expanded="false">' +
4443
+ type +
4444
+ "</button></div>" +
4445
+ desc +
4446
+ extra +
4447
+ "</div>";
2010
4448
  }
2011
4449
  return '<div><h3 class="text-sm font-semibold mb-3 flex items-center border-b border-light-border dark:border-dark-border pb-2">' + escapeHtml(title) + "</h3>" + rows + "</div>";
2012
4450
  }
2013
4451
 
2014
4452
  function getSchemaTypeLabel(schema) {
2015
- if (!schema || typeof schema !== "object") return "unknown";
2016
- if (Array.isArray(schema.type)) return schema.type.join(" | ");
2017
- if (schema.type) return String(schema.type);
2018
- if (schema.properties) return "object";
2019
- if (schema.items) return "array";
2020
- if (Array.isArray(schema.oneOf)) return "oneOf";
2021
- if (Array.isArray(schema.anyOf)) return "anyOf";
2022
- if (Array.isArray(schema.allOf)) return "allOf";
4453
+ const resolved = resolveSchemaRef(schema);
4454
+ if (!resolved || typeof resolved !== "object") return "unknown";
4455
+ if (Array.isArray(resolved.type)) return resolved.type.join(" | ");
4456
+ if (resolved.type) return String(resolved.type);
4457
+ if (resolved.properties) return "object";
4458
+ if (resolved.items) return "array";
4459
+ if (Array.isArray(resolved.oneOf)) return "oneOf";
4460
+ if (Array.isArray(resolved.anyOf)) return "anyOf";
4461
+ if (Array.isArray(resolved.allOf)) return "allOf";
2023
4462
  return "unknown";
2024
4463
  }
2025
4464
 
4465
+ function buildSchemaExtra(schema) {
4466
+ const resolved = resolveSchemaRef(schema);
4467
+ if (!resolved || typeof resolved !== "object") return "";
4468
+ const chips = [];
4469
+ if (resolved.format) chips.push(escapeHtml(String(resolved.format)));
4470
+ if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
4471
+ const shown = resolved.enum.slice(0, 5).map(function(v) { return escapeHtml(JSON.stringify(v)); });
4472
+ chips.push(shown.join(" | ") + (resolved.enum.length > 5 ? " \u2026" : ""));
4473
+ }
4474
+ if (resolved.minimum !== undefined) chips.push("min: " + resolved.minimum);
4475
+ if (resolved.maximum !== undefined) chips.push("max: " + resolved.maximum);
4476
+ if (typeof resolved.exclusiveMinimum === "number") chips.push("&gt;" + resolved.exclusiveMinimum);
4477
+ if (typeof resolved.exclusiveMaximum === "number") chips.push("&lt;" + resolved.exclusiveMaximum);
4478
+ if (resolved.minLength !== undefined) chips.push("minLen: " + resolved.minLength);
4479
+ if (resolved.maxLength !== undefined) chips.push("maxLen: " + resolved.maxLength);
4480
+ if (resolved.minItems !== undefined) chips.push("minItems: " + resolved.minItems);
4481
+ if (resolved.maxItems !== undefined) chips.push("maxItems: " + resolved.maxItems);
4482
+ if (resolved.uniqueItems) chips.push("unique");
4483
+ if (resolved.pattern) chips.push("/" + escapeHtml(String(resolved.pattern)) + "/");
4484
+ if (!chips.length) return "";
4485
+ return '<div class="flex flex-wrap gap-1 mt-1.5">' +
4486
+ chips.map(function(c) {
4487
+ return '<span class="text-[10px] px-1.5 py-0.5 rounded bg-black/5 dark:bg-white/5 font-mono opacity-80">' + c + '</span>';
4488
+ }).join("") +
4489
+ '</div>';
4490
+ }
4491
+
4492
+ function resolveSchemaRef(schema, visitedRefs) {
4493
+ if (!schema || typeof schema !== "object") return schema;
4494
+ const ref = typeof schema.$ref === "string" ? schema.$ref : "";
4495
+ if (!ref || !ref.startsWith("#/components/schemas/")) {
4496
+ return schema;
4497
+ }
4498
+
4499
+ const seen = visitedRefs || new Set();
4500
+ if (seen.has(ref)) return schema;
4501
+ seen.add(ref);
4502
+
4503
+ const parts = ref.split("/");
4504
+ const schemaName = parts[parts.length - 1];
4505
+ const referenced = spec && spec.components && spec.components.schemas && spec.components.schemas[schemaName];
4506
+ if (!referenced || typeof referenced !== "object") return schema;
4507
+
4508
+ const merged = Object.assign({}, referenced, schema);
4509
+ delete merged.$ref;
4510
+ return resolveSchemaRef(merged, seen);
4511
+ }
4512
+
2026
4513
  function buildSchemaChildren(schema) {
2027
- if (!schema || typeof schema !== "object") return [];
4514
+ const resolved = resolveSchemaRef(schema);
4515
+ if (!resolved || typeof resolved !== "object") return [];
2028
4516
 
2029
4517
  const children = [];
2030
4518
 
2031
- if (schema.properties && typeof schema.properties === "object") {
4519
+ if (resolved.properties && typeof resolved.properties === "object") {
2032
4520
  const requiredSet = new Set(
2033
- Array.isArray(schema.required) ? schema.required : [],
4521
+ Array.isArray(resolved.required) ? resolved.required : [],
2034
4522
  );
2035
- for (const [name, childSchema] of Object.entries(schema.properties)) {
4523
+ for (const [name, childSchema] of Object.entries(resolved.properties)) {
4524
+ const childDef = childSchema || {};
4525
+ const isArrayType = Array.isArray(childDef.type)
4526
+ ? childDef.type.includes("array")
4527
+ : childDef.type === "array";
4528
+ const isArrayLike = isArrayType || childDef.items !== undefined;
2036
4529
  children.push({
2037
- name,
2038
- schema: childSchema || {},
4530
+ name: isArrayLike ? (name + "[]") : name,
4531
+ schema: childDef,
2039
4532
  required: requiredSet.has(name),
2040
4533
  });
2041
4534
  }
2042
4535
  }
2043
4536
 
2044
- if (schema.items) {
4537
+ if (resolved.items) {
2045
4538
  children.push({
2046
- name: "items[]",
2047
- schema: schema.items,
4539
+ name: getArrayItemNodeName(resolved.items),
4540
+ schema: resolved.items,
2048
4541
  required: true,
2049
4542
  });
2050
4543
  }
@@ -2052,45 +4545,100 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2052
4545
  return children;
2053
4546
  }
2054
4547
 
2055
- function renderSchemaFieldNode(field, depth) {
2056
- const schema = field.schema || {};
2057
- const name = escapeHtml(field.name || "field");
4548
+ function getArrayItemNodeName(itemSchema) {
4549
+ if (!itemSchema || typeof itemSchema !== "object") return "item";
4550
+ const title =
4551
+ typeof itemSchema.title === "string" && itemSchema.title.trim()
4552
+ ? itemSchema.title.trim()
4553
+ : "";
4554
+ if (title) return title;
4555
+
4556
+ const ref =
4557
+ typeof itemSchema.$ref === "string" && itemSchema.$ref.trim()
4558
+ ? itemSchema.$ref.trim()
4559
+ : "";
4560
+ if (ref) {
4561
+ const parts = ref.split("/").filter(Boolean);
4562
+ const last = parts[parts.length - 1];
4563
+ if (last) return last;
4564
+ }
4565
+
4566
+ const typeLabel = getSchemaTypeLabel(itemSchema);
4567
+ if (typeLabel && typeLabel !== "unknown") return typeLabel;
4568
+ return "type";
4569
+ }
4570
+
4571
+ function renderSchemaFieldNode(field, depth, parentPath) {
4572
+ const schema = resolveSchemaRef(field.schema || {});
4573
+ const nameRaw = field.name || "field";
4574
+ const name = escapeHtml(nameRaw);
2058
4575
  const requiredLabel = field.required ? "required" : "optional";
2059
- const type = escapeHtml(getSchemaTypeLabel(schema));
4576
+ const typeRaw = getSchemaTypeLabel(schema);
4577
+ const type = escapeHtml(typeRaw);
4578
+ const tooltipName = escapeHtmlAttribute(nameRaw);
4579
+ const tooltipType = escapeHtmlAttribute(typeRaw);
4580
+ const fieldPath = parentPath ? (parentPath + "." + nameRaw) : nameRaw;
4581
+ const tooltipPath = escapeHtmlAttribute(fieldPath);
4582
+ const tooltipDescription = (typeof schema.description === "string" && schema.description.trim())
4583
+ ? escapeHtmlAttribute(schema.description.trim())
4584
+ : "";
2060
4585
  const children = buildSchemaChildren(schema);
2061
4586
  const padding = depth * 14;
4587
+ const extra = buildSchemaExtra(schema);
2062
4588
 
2063
4589
  if (!children.length) {
2064
4590
  return (
2065
- '<div class="py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
4591
+ '<div class="param-row py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
2066
4592
  padding +
2067
- 'px"><div class="flex justify-between"><div><code class="text-sm font-mono">' +
4593
+ 'px"><div class="param-row-head"><div class="param-row-main">' +
4594
+ '<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Field" data-param-tooltip-value="' +
4595
+ tooltipName +
4596
+ '" data-param-tooltip-related="' +
4597
+ tooltipPath +
4598
+ '" data-param-tooltip-description="' +
4599
+ tooltipDescription +
4600
+ '" aria-expanded="false"><code class="text-sm font-mono param-name-text">' +
2068
4601
  name +
2069
- '</code><span class="text-xs text-brand ml-2">' +
4602
+ '</code></button><span class="text-xs text-brand shrink-0">' +
2070
4603
  requiredLabel +
2071
- '</span></div><span class="text-xs font-mono opacity-60">' +
4604
+ '</span></div><button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
4605
+ tooltipType +
4606
+ '" data-param-tooltip-description="' +
4607
+ tooltipDescription +
4608
+ '" aria-expanded="false">' +
2072
4609
  type +
2073
- "</span></div></div>"
4610
+ "</button></div>" + extra + "</div>"
2074
4611
  );
2075
4612
  }
2076
4613
 
2077
4614
  let nested = "";
2078
4615
  for (const child of children) {
2079
- nested += renderSchemaFieldNode(child, depth + 1);
4616
+ nested += renderSchemaFieldNode(child, depth + 1, fieldPath);
2080
4617
  }
2081
4618
 
2082
4619
  return (
2083
- '<details class="border-b border-light-border/50 dark:border-dark-border/50" open>' +
2084
- '<summary class="list-none cursor-pointer py-2 flex justify-between items-center" style="padding-left:' +
4620
+ '<details open>' +
4621
+ '<summary class="list-none cursor-pointer py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
2085
4622
  padding +
2086
- 'px"><div class="flex items-center gap-2"><span class="text-xs opacity-70">\u25BE</span><code class="text-sm font-mono">' +
4623
+ 'px"><div class="param-row-head"><div class="param-row-main"><span class="text-xs opacity-70 shrink-0">\u25BE</span>' +
4624
+ '<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Field" data-param-tooltip-value="' +
4625
+ tooltipName +
4626
+ '" data-param-tooltip-related="' +
4627
+ tooltipPath +
4628
+ '" data-param-tooltip-description="' +
4629
+ tooltipDescription +
4630
+ '" aria-expanded="false"><code class="text-sm font-mono param-name-text">' +
2087
4631
  name +
2088
- '</code><span class="text-xs text-brand">' +
4632
+ '</code></button><span class="text-xs text-brand shrink-0">' +
2089
4633
  requiredLabel +
2090
- '</span></div><span class="text-xs font-mono opacity-60">' +
4634
+ '</span></div><button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
4635
+ tooltipType +
4636
+ '" data-param-tooltip-description="' +
4637
+ tooltipDescription +
4638
+ '" aria-expanded="false">' +
2091
4639
  type +
2092
- "</span></summary>" +
2093
- '<div class="pb-1">' +
4640
+ "</button></div>" + extra + "</summary>" +
4641
+ "<div>" +
2094
4642
  nested +
2095
4643
  "</div></details>"
2096
4644
  );
@@ -2103,7 +4651,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2103
4651
 
2104
4652
  let rows = "";
2105
4653
  for (const child of rootChildren) {
2106
- rows += renderSchemaFieldNode(child, 0);
4654
+ rows += renderSchemaFieldNode(child, 0, "");
2107
4655
  }
2108
4656
 
2109
4657
  return (
@@ -2130,27 +4678,40 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2130
4678
  const responseDef = responses[statusCode];
2131
4679
  if (!responseDef || typeof responseDef !== "object") continue;
2132
4680
 
4681
+ const responseDesc = (typeof responseDef.description === "string" && responseDef.description.trim())
4682
+ ? responseDef.description.trim()
4683
+ : "";
4684
+
2133
4685
  const jsonSchema =
2134
4686
  responseDef.content &&
2135
4687
  responseDef.content["application/json"] &&
2136
4688
  responseDef.content["application/json"].schema;
2137
4689
 
2138
- if (!jsonSchema || typeof jsonSchema !== "object") continue;
2139
-
2140
- const rootChildren = buildSchemaChildren(jsonSchema);
2141
- if (!rootChildren.length) continue;
2142
-
2143
4690
  let rows = "";
2144
- for (const child of rootChildren) {
2145
- rows += renderSchemaFieldNode(child, 0);
4691
+ if (jsonSchema && typeof jsonSchema === "object") {
4692
+ const rootChildren = buildSchemaChildren(jsonSchema);
4693
+ for (const child of rootChildren) {
4694
+ rows += renderSchemaFieldNode(child, 0, "");
4695
+ }
2146
4696
  }
2147
4697
 
4698
+ if (!responseDesc && !rows) continue;
4699
+
4700
+ const descHtml = responseDesc
4701
+ ? ' <span class="normal-case font-sans opacity-70 ml-1">\u2014 ' + escapeHtml(responseDesc) + '</span>'
4702
+ : "";
4703
+ const contentHtml = rows || '<p class="text-xs opacity-60 mt-1">No schema fields</p>';
4704
+
2148
4705
  sections +=
2149
- '<div class="mb-4"><h4 class="text-xs font-mono uppercase tracking-wider opacity-70 mb-2">Status ' +
4706
+ '<details class="mb-4">' +
4707
+ '<summary class="list-none cursor-pointer">' +
4708
+ '<h4 class="text-xs font-mono uppercase tracking-wider opacity-70 mb-2">Status ' +
2150
4709
  escapeHtml(statusCode) +
4710
+ descHtml +
2151
4711
  "</h4>" +
2152
- rows +
2153
- "</div>";
4712
+ "</summary>" +
4713
+ contentHtml +
4714
+ "</details>";
2154
4715
  }
2155
4716
 
2156
4717
  if (!sections) return "";
@@ -2242,6 +4803,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2242
4803
  "w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
2243
4804
  keyInput.addEventListener("input", () => {
2244
4805
  entry.key = keyInput.value;
4806
+ saveHeaders();
2245
4807
  updateRequestPreview();
2246
4808
  });
2247
4809
 
@@ -2256,6 +4818,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2256
4818
  "w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
2257
4819
  valueInput.addEventListener("input", () => {
2258
4820
  entry.value = valueInput.value;
4821
+ saveHeaders();
2259
4822
  updateRequestPreview();
2260
4823
  });
2261
4824
 
@@ -2269,6 +4832,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2269
4832
  '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h12"></path></svg>';
2270
4833
  removeButton.addEventListener("click", () => {
2271
4834
  requestHeaders.splice(index, 1);
4835
+ saveHeaders();
2272
4836
  renderHeaderInputs();
2273
4837
  updateRequestPreview();
2274
4838
  });
@@ -2285,15 +4849,32 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2285
4849
  return Object.keys(headers).some((key) => key.toLowerCase() === target);
2286
4850
  }
2287
4851
 
2288
- function getRequestHeadersObject() {
2289
- const headers = {};
4852
+ function buildCookieHeaderValue(cookieValues) {
4853
+ const entries = Object.entries(cookieValues);
4854
+ if (!entries.length) return "";
4855
+ return entries
4856
+ .map(([name, value]) => String(name) + "=" + encodeURIComponent(String(value)))
4857
+ .join("; ");
4858
+ }
4859
+
4860
+ function getRequestHeadersObject(op) {
4861
+ const auth = getAuthHeaders(op);
4862
+ const authCookies = getAuthCookieParams(op);
4863
+ if (Object.keys(authCookies).length > 0) {
4864
+ const cookieHeader = buildCookieHeaderValue(authCookies);
4865
+ if (cookieHeader) {
4866
+ auth["Cookie"] = cookieHeader;
4867
+ }
4868
+ }
4869
+ const manual = {};
2290
4870
  for (const entry of requestHeaders) {
2291
4871
  const key = String(entry.key || "").trim();
2292
4872
  const value = String(entry.value || "").trim();
2293
4873
  if (!key || !value) continue;
2294
- headers[key] = value;
4874
+ manual[key] = value;
2295
4875
  }
2296
- return headers;
4876
+ // Auth provides defaults; manual headers win on conflict
4877
+ return Object.assign({}, auth, manual);
2297
4878
  }
2298
4879
 
2299
4880
  function buildCurl(op, headers, body, requestPath) {
@@ -2330,6 +4911,33 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2330
4911
  .replace(/>/g, "&gt;");
2331
4912
  }
2332
4913
 
4914
+ function escapeHtmlAttribute(value) {
4915
+ return String(value)
4916
+ .replace(/&/g, "&amp;")
4917
+ .replace(/</g, "&lt;")
4918
+ .replace(/>/g, "&gt;")
4919
+ .replace(/"/g, "&quot;")
4920
+ .replace(/'/g, "&#39;");
4921
+ }
4922
+
4923
+ function renderMarkdown(text) {
4924
+ if (!text || typeof text !== "string") return "";
4925
+ var s = escapeHtml(text);
4926
+ // inline code \u2014 process first to protect content inside backticks
4927
+ s = s.replace(/\`([^\`\\n]+)\`/g, '<code class="text-xs font-mono bg-black/5 dark:bg-white/5 px-1 py-0.5 rounded">$1</code>');
4928
+ // bold **text**
4929
+ s = s.replace(/\\*\\*([^*\\n]+)\\*\\*/g, '<strong>$1</strong>');
4930
+ // italic *text*
4931
+ s = s.replace(/\\*([^*\\n]+)\\*/g, '<em>$1</em>');
4932
+ // links [text](url)
4933
+ s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, function(m, txt, url) {
4934
+ var lc = url.toLowerCase().replace(/\\s/g, "");
4935
+ if (lc.indexOf("javascript:") === 0 || lc.indexOf("data:") === 0 || lc.indexOf("vbscript:") === 0) return txt;
4936
+ return '<a href="' + url.replace(/"/g, '&quot;') + '" target="_blank" rel="noopener noreferrer" class="text-brand hover:underline">' + txt + '</a>';
4937
+ });
4938
+ return s;
4939
+ }
4940
+
2333
4941
  function toPrettyJson(value) {
2334
4942
  const trimmed = (value || "").trim();
2335
4943
  if (!trimmed) return null;
@@ -2463,10 +5071,10 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2463
5071
 
2464
5072
  const { path, query } = getOperationParameterGroups(selected);
2465
5073
  const values = getParameterValues(selected);
2466
- const requestPath = buildRequestPath(selected, path, query, values);
5074
+ const requestPath = buildRequestPath(selected, path, query, values, getAuthQueryParams(selected));
2467
5075
  const bodyInput = document.getElementById("body-input");
2468
5076
  const body = bodyInput ? bodyInput.value.trim() : "";
2469
- const headers = getRequestHeadersObject();
5077
+ const headers = getRequestHeadersObject(selected);
2470
5078
  if (body && !hasHeaderName(headers, "Content-Type")) {
2471
5079
  headers["Content-Type"] = "application/json";
2472
5080
  }
@@ -2517,8 +5125,13 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2517
5125
  }
2518
5126
  setResponseContent("", "", false);
2519
5127
 
5128
+ const deprecatedBanner = document.getElementById("deprecated-banner");
5129
+ if (deprecatedBanner) {
5130
+ deprecatedBanner.classList.toggle("hidden", !op.deprecated);
5131
+ }
5132
+
2520
5133
  document.getElementById("tag-title").textContent = selected.tag;
2521
- document.getElementById("tag-description").textContent = op.description || "Interactive API documentation.";
5134
+ document.getElementById("tag-description").innerHTML = op.description ? renderMarkdown(op.description) : "Interactive API documentation.";
2522
5135
  const methodNode = document.getElementById("endpoint-method");
2523
5136
  methodNode.textContent = selected.method;
2524
5137
  methodNode.className = "px-2.5 py-0.5 rounded-full text-xs font-mono font-medium " + (methodBadge[selected.method] || methodBadgeDefault);
@@ -2535,7 +5148,13 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2535
5148
 
2536
5149
  html += renderRequestBodySchemaSection(reqSchema);
2537
5150
  html += renderResponseSchemasSection(op.responses);
2538
- document.getElementById("params-column").innerHTML = html || '<div class="text-sm opacity-70">No parameters</div>';
5151
+ const paramsColumn = document.getElementById("params-column");
5152
+ if (paramsColumn) {
5153
+ hideParamTooltip();
5154
+ paramsColumn.innerHTML = html || '<div class="text-sm opacity-70">No parameters</div>';
5155
+ registerParamTooltipTargets(paramsColumn);
5156
+ }
5157
+ renderAuthPanel();
2539
5158
  renderTryItParameterInputs(path, query);
2540
5159
  renderHeaderInputs();
2541
5160
  updateRequestPreview();
@@ -2546,6 +5165,24 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2546
5165
  document.getElementById("copy-curl").addEventListener("click", async () => {
2547
5166
  try { await navigator.clipboard.writeText(document.getElementById("curl-code").textContent || ""); } catch {}
2548
5167
  });
5168
+ if (paramTooltipRoot) {
5169
+ paramTooltipRoot.addEventListener("mouseenter", () => {
5170
+ if (paramTooltipHideTimer) {
5171
+ window.clearTimeout(paramTooltipHideTimer);
5172
+ paramTooltipHideTimer = null;
5173
+ }
5174
+ });
5175
+ paramTooltipRoot.addEventListener("mouseleave", () => {
5176
+ scheduleParamTooltipHide();
5177
+ });
5178
+ }
5179
+ document.addEventListener("pointerdown", (event) => {
5180
+ if (!paramTooltipRoot) return;
5181
+ const target = event.target;
5182
+ if (target && paramTooltipRoot.contains(target)) return;
5183
+ if (target && target.closest && target.closest("[data-param-tooltip-value]")) return;
5184
+ hideParamTooltip();
5185
+ });
2549
5186
  document.getElementById("sidebar-search").addEventListener("input", (event) => {
2550
5187
  sidebarSearchQuery = event.currentTarget.value || "";
2551
5188
  renderSidebar();
@@ -2571,7 +5208,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2571
5208
  return;
2572
5209
  }
2573
5210
 
2574
- const requestPath = buildRequestPath(selected, path, query, values);
5211
+ const requestPath = buildRequestPath(selected, path, query, values, getAuthQueryParams(selected));
2575
5212
  formatBodyJsonInput();
2576
5213
  updateBodyJsonPresentation();
2577
5214
  const op = selected.operation || {};
@@ -2580,7 +5217,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2580
5217
  const bodyInput = document.getElementById("body-input");
2581
5218
  const body =
2582
5219
  supportsBody && bodyInput ? bodyInput.value.trim() : "";
2583
- const headers = getRequestHeadersObject();
5220
+ const headers = getRequestHeadersObject(selected);
2584
5221
  if (body && !hasHeaderName(headers, "Content-Type")) {
2585
5222
  headers["Content-Type"] = "application/json";
2586
5223
  }
@@ -2588,7 +5225,13 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2588
5225
  setSubmitLoading(true);
2589
5226
  try {
2590
5227
  const requestStart = performance.now();
2591
- const response = await fetch(requestPath, { method: selected.method, headers, body: body || undefined });
5228
+ applyAuthCookies(selected);
5229
+ const response = await fetch(requestPath, {
5230
+ method: selected.method,
5231
+ headers,
5232
+ body: body || undefined,
5233
+ credentials: "same-origin",
5234
+ });
2592
5235
  const text = await response.text();
2593
5236
  const responseTimeMs = Math.round(performance.now() - requestStart);
2594
5237
  const contentType = response.headers.get("content-type") || "unknown";
@@ -2664,6 +5307,7 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2664
5307
 
2665
5308
  document.getElementById("add-header-btn").addEventListener("click", () => {
2666
5309
  requestHeaders.push({ key: "", value: "" });
5310
+ saveHeaders();
2667
5311
  renderHeaderInputs();
2668
5312
  updateRequestPreview();
2669
5313
  });
@@ -2777,12 +5421,21 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2777
5421
  setMobileSidebarOpen(false);
2778
5422
  });
2779
5423
  window.addEventListener("resize", () => {
5424
+ if (activeParamTooltipTrigger) {
5425
+ positionParamTooltip(activeParamTooltipTrigger);
5426
+ }
2780
5427
  if (window.innerWidth >= 768 && isMobileSidebarOpen) {
2781
5428
  setMobileSidebarOpen(false);
2782
5429
  }
2783
5430
  });
5431
+ window.addEventListener("scroll", () => {
5432
+ if (activeParamTooltipTrigger) {
5433
+ positionParamTooltip(activeParamTooltipTrigger);
5434
+ }
5435
+ }, true);
2784
5436
  document.addEventListener("keydown", (event) => {
2785
5437
  if (event.key === "Escape") {
5438
+ hideParamTooltip();
2786
5439
  if (isMobileSidebarOpen) {
2787
5440
  setMobileSidebarOpen(false);
2788
5441
  }
@@ -2807,7 +5460,444 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2807
5460
  }
2808
5461
  });
2809
5462
 
5463
+ function getOperationSecurityRequirements(op) {
5464
+ const operationSecurity = op && op.operation && Array.isArray(op.operation.security)
5465
+ ? op.operation.security
5466
+ : null;
5467
+ if (operationSecurity) {
5468
+ return operationSecurity;
5469
+ }
5470
+ return Array.isArray(spec.security) ? spec.security : [];
5471
+ }
5472
+
5473
+ function getAuthSelectionKeyForOperation(op) {
5474
+ if (!op) return "";
5475
+ return getOperationKey(op);
5476
+ }
5477
+
5478
+ function getAuthSchemeOptionsForOperation(op) {
5479
+ const requirements = getOperationSecurityRequirements(op).filter((requirement) =>
5480
+ requirement && typeof requirement === "object" && !Array.isArray(requirement)
5481
+ );
5482
+ if (!requirements.length) return [];
5483
+
5484
+ const seen = new Set();
5485
+ const options = [];
5486
+ for (const requirement of requirements) {
5487
+ for (const schemeName of Object.keys(requirement)) {
5488
+ if (!Object.prototype.hasOwnProperty.call(authSchemes, schemeName)) continue;
5489
+ if (seen.has(schemeName)) continue;
5490
+ seen.add(schemeName);
5491
+ options.push(schemeName);
5492
+ }
5493
+ }
5494
+ return options;
5495
+ }
5496
+
5497
+ function getSelectedAuthSchemeForOperation(op) {
5498
+ const selectionKey = getAuthSelectionKeyForOperation(op);
5499
+ if (!selectionKey) return null;
5500
+
5501
+ const selectedScheme = authSelectionState[selectionKey];
5502
+ if (!selectedScheme || typeof selectedScheme !== "string") return null;
5503
+
5504
+ const options = Object.keys(authSchemes);
5505
+ if (!options.includes(selectedScheme)) {
5506
+ delete authSelectionState[selectionKey];
5507
+ saveAuthSelectionState();
5508
+ return null;
5509
+ }
5510
+
5511
+ return selectedScheme;
5512
+ }
5513
+
5514
+ function setSelectedAuthSchemeForOperation(op, schemeName) {
5515
+ const selectionKey = getAuthSelectionKeyForOperation(op);
5516
+ if (!selectionKey) return;
5517
+
5518
+ if (!schemeName) {
5519
+ delete authSelectionState[selectionKey];
5520
+ } else {
5521
+ authSelectionState[selectionKey] = schemeName;
5522
+ }
5523
+ saveAuthSelectionState();
5524
+ }
5525
+
5526
+ function hasAuthStateForScheme(schemeName) {
5527
+ const scheme = authSchemes[schemeName];
5528
+ if (!scheme) return false;
5529
+
5530
+ const state = authState[schemeName] || {};
5531
+ const type = (scheme.type || "").toLowerCase();
5532
+ const httpScheme = (scheme.scheme || "").toLowerCase();
5533
+
5534
+ if (type === "http" && httpScheme === "basic") {
5535
+ return Boolean(state.username && state.password);
5536
+ }
5537
+ if (type === "http") {
5538
+ return Boolean(state.token);
5539
+ }
5540
+ if (type === "apikey") {
5541
+ return Boolean(state.value);
5542
+ }
5543
+ if (type === "oauth2" || type === "openidconnect") {
5544
+ return Boolean(state.token);
5545
+ }
5546
+
5547
+ return false;
5548
+ }
5549
+
5550
+ function chooseOperationSecurityRequirement(op) {
5551
+ const requirements = getOperationSecurityRequirements(op).filter((requirement) =>
5552
+ requirement && typeof requirement === "object" && !Array.isArray(requirement)
5553
+ );
5554
+ if (!requirements.length) return null;
5555
+
5556
+ const selectedScheme = getSelectedAuthSchemeForOperation(op);
5557
+ if (selectedScheme) {
5558
+ const selectedRequirement = requirements.find((requirement) =>
5559
+ Object.prototype.hasOwnProperty.call(requirement, selectedScheme)
5560
+ );
5561
+ if (selectedRequirement) return selectedRequirement;
5562
+ }
5563
+
5564
+ let bestRequirement = null;
5565
+ let bestScore = -1;
5566
+
5567
+ for (const requirement of requirements) {
5568
+ const schemeNames = Object.keys(requirement).filter((schemeName) =>
5569
+ Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
5570
+ );
5571
+ if (!schemeNames.length) continue;
5572
+
5573
+ const providedCount = schemeNames.filter((schemeName) => hasAuthStateForScheme(schemeName)).length;
5574
+ const isComplete = providedCount === schemeNames.length;
5575
+ const score = isComplete ? 1000 + providedCount : providedCount;
5576
+
5577
+ if (score > bestScore) {
5578
+ bestScore = score;
5579
+ bestRequirement = requirement;
5580
+ }
5581
+ }
5582
+
5583
+ return bestRequirement || requirements[0];
5584
+ }
5585
+
5586
+ function getAuthSchemeNamesForOperation(op) {
5587
+ const schemeNames = Object.keys(authSchemes);
5588
+ if (!schemeNames.length) return [];
5589
+
5590
+ const selectedScheme = getSelectedAuthSchemeForOperation(op);
5591
+ if (selectedScheme) {
5592
+ const requirement = chooseOperationSecurityRequirement(op);
5593
+ if (requirement && Object.prototype.hasOwnProperty.call(requirement, selectedScheme)) {
5594
+ return Object.keys(requirement).filter((schemeName) =>
5595
+ Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
5596
+ );
5597
+ }
5598
+ return [selectedScheme];
5599
+ }
5600
+
5601
+ const requirement = chooseOperationSecurityRequirement(op);
5602
+ if (!requirement) return [];
5603
+
5604
+ return Object.keys(requirement).filter((schemeName) =>
5605
+ Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
5606
+ );
5607
+ }
5608
+
5609
+ function getAuthHeaders(op) {
5610
+ const headers = {};
5611
+ const schemeNames = getAuthSchemeNamesForOperation(op);
5612
+ const allSchemeNames = Object.keys(authSchemes);
5613
+
5614
+ if (!allSchemeNames.length) {
5615
+ const state = authState["__default__"] || {};
5616
+ if (state.token) headers["Authorization"] = "Bearer " + state.token;
5617
+ return headers;
5618
+ }
5619
+ if (!schemeNames.length) return headers;
5620
+
5621
+ for (const schemeName of schemeNames) {
5622
+ const scheme = authSchemes[schemeName];
5623
+ const state = authState[schemeName] || {};
5624
+ const type = (scheme.type || "").toLowerCase();
5625
+ const httpScheme = (scheme.scheme || "").toLowerCase();
5626
+
5627
+ if (type === "http" && httpScheme === "basic") {
5628
+ if (state.username && state.password) {
5629
+ try {
5630
+ headers["Authorization"] = "Basic " + btoa(state.username + ":" + state.password);
5631
+ } catch {}
5632
+ }
5633
+ } else if (type === "http") {
5634
+ if (state.token) headers["Authorization"] = "Bearer " + state.token;
5635
+ } else if (type === "apikey" && (scheme.in || "").toLowerCase() === "header") {
5636
+ if (state.value && scheme.name) headers[scheme.name] = state.value;
5637
+ } else if (type === "oauth2" || type === "openidconnect") {
5638
+ if (state.token) headers["Authorization"] = "Bearer " + state.token;
5639
+ }
5640
+ }
5641
+ return headers;
5642
+ }
5643
+
5644
+ function getAuthQueryParams(op) {
5645
+ const params = {};
5646
+ for (const schemeName of getAuthSchemeNamesForOperation(op)) {
5647
+ const scheme = authSchemes[schemeName];
5648
+ if ((scheme.type || "").toLowerCase() === "apikey" && (scheme.in || "").toLowerCase() === "query") {
5649
+ const state = authState[schemeName] || {};
5650
+ if (state.value && scheme.name) params[scheme.name] = state.value;
5651
+ }
5652
+ }
5653
+ return params;
5654
+ }
5655
+
5656
+ function getAuthCookieParams(op) {
5657
+ const cookies = {};
5658
+ for (const schemeName of getAuthSchemeNamesForOperation(op)) {
5659
+ const scheme = authSchemes[schemeName];
5660
+ if ((scheme.type || "").toLowerCase() !== "apikey") continue;
5661
+ if ((scheme.in || "").toLowerCase() !== "cookie") continue;
5662
+ const state = authState[schemeName] || {};
5663
+ if (state.value && scheme.name) {
5664
+ cookies[scheme.name] = state.value;
5665
+ }
5666
+ }
5667
+ return cookies;
5668
+ }
5669
+
5670
+ function applyAuthCookies(op) {
5671
+ const cookies = getAuthCookieParams(op);
5672
+ for (const [name, value] of Object.entries(cookies)) {
5673
+ try {
5674
+ document.cookie = encodeURIComponent(String(name)) + "=" + encodeURIComponent(String(value)) + "; path=/";
5675
+ } catch {}
5676
+ }
5677
+ }
5678
+
5679
+ function renderAuthPanel() {
5680
+ const fields = document.getElementById("auth-fields");
5681
+ if (!fields) return;
5682
+ fields.innerHTML = "";
5683
+
5684
+ const schemeNames = Object.keys(authSchemes);
5685
+ const op = selected;
5686
+ const operationSchemeOptions = op ? getAuthSchemeOptionsForOperation(op) : [];
5687
+ const availableSchemeOptions = Object.keys(authSchemes);
5688
+ const selectedScheme = op ? getSelectedAuthSchemeForOperation(op) : null;
5689
+
5690
+ function getAuthSchemeDisplayLabel(schemeName) {
5691
+ const scheme = authSchemes[schemeName] || {};
5692
+ const type = (scheme.type || "").toLowerCase();
5693
+ const httpScheme = (scheme.scheme || "").toLowerCase();
5694
+ const location = (scheme.in || "").toLowerCase();
5695
+
5696
+ if (type === "http" && httpScheme === "basic") return "HTTP Basic";
5697
+ if (type === "http" && httpScheme === "bearer") return "HTTP Bearer";
5698
+ if (type === "http" && httpScheme === "digest") return "HTTP Digest";
5699
+ if (type === "http") return "HTTP " + (httpScheme || "Token");
5700
+ if (type === "apikey") return "API Key" + (location ? " (" + location + ")" : "");
5701
+ if (type === "oauth2") return "OAuth 2.0";
5702
+ if (type === "openidconnect") return "OpenID Connect";
5703
+ if (type === "mutualtls") return "Mutual TLS";
5704
+ return schemeName;
5705
+ }
5706
+
5707
+ function makeInput(placeholder, value, onInput, type) {
5708
+ const inp = document.createElement("input");
5709
+ inp.type = type || "text";
5710
+ inp.value = value || "";
5711
+ inp.placeholder = placeholder;
5712
+ inp.className = "w-full text-xs px-2.5 py-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
5713
+ inp.addEventListener("input", onInput);
5714
+ return inp;
5715
+ }
5716
+
5717
+ function makeLabel(text, small) {
5718
+ const el = document.createElement("p");
5719
+ el.className = small
5720
+ ? "text-[10px] font-medium uppercase tracking-wider opacity-55"
5721
+ : "text-[10px] font-semibold uppercase tracking-wider opacity-55";
5722
+ el.textContent = text;
5723
+ return el;
5724
+ }
5725
+
5726
+ function makeField(label, input) {
5727
+ const wrapper = document.createElement("div");
5728
+ wrapper.className = "space-y-1";
5729
+ wrapper.appendChild(makeLabel(label, true));
5730
+ wrapper.appendChild(input);
5731
+ return wrapper;
5732
+ }
5733
+
5734
+ function makeSchemeCard(title, subtitle) {
5735
+ const card = document.createElement("div");
5736
+ card.className = "space-y-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg/40 dark:bg-dark-bg/40 p-2.5";
5737
+
5738
+ const titleRow = document.createElement("div");
5739
+ titleRow.className = "flex items-center justify-between gap-2";
5740
+
5741
+ const heading = document.createElement("p");
5742
+ heading.className = "text-[11px] font-semibold tracking-wide";
5743
+ heading.textContent = title;
5744
+ titleRow.appendChild(heading);
5745
+
5746
+ if (subtitle) {
5747
+ const note = document.createElement("span");
5748
+ note.className = "text-[10px] opacity-60 font-mono";
5749
+ note.textContent = subtitle;
5750
+ titleRow.appendChild(note);
5751
+ }
5752
+
5753
+ card.appendChild(titleRow);
5754
+ return card;
5755
+ }
5756
+
5757
+ if (!schemeNames.length) {
5758
+ if (!authState["__default__"]) authState["__default__"] = {};
5759
+ const defaultCard = makeSchemeCard("Default Auth", "bearer");
5760
+ defaultCard.appendChild(makeField("Token", makeInput("Enter token\u2026", authState["__default__"].token, function(e) {
5761
+ authState["__default__"].token = e.target.value;
5762
+ saveAuthState();
5763
+ updateRequestPreview();
5764
+ })));
5765
+ fields.appendChild(defaultCard);
5766
+ return;
5767
+ }
5768
+
5769
+ if (op && availableSchemeOptions.length > 0) {
5770
+ const selectorWrap = document.createElement("div");
5771
+ selectorWrap.className = "space-y-1";
5772
+ selectorWrap.appendChild(makeLabel("Auth Type"));
5773
+ const select = document.createElement("select");
5774
+ select.className = "w-full text-xs px-2.5 py-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
5775
+
5776
+ const autoOption = document.createElement("option");
5777
+ autoOption.value = "";
5778
+ autoOption.textContent = "Auto";
5779
+ select.appendChild(autoOption);
5780
+
5781
+ for (const schemeName of availableSchemeOptions) {
5782
+ const option = document.createElement("option");
5783
+ option.value = schemeName;
5784
+ const isOperationScheme = operationSchemeOptions.includes(schemeName);
5785
+ const label = getAuthSchemeDisplayLabel(schemeName);
5786
+ option.textContent = isOperationScheme
5787
+ ? label
5788
+ : (label + " \u2022 override");
5789
+ select.appendChild(option);
5790
+ }
5791
+
5792
+ select.value = selectedScheme || "";
5793
+ select.addEventListener("change", function(e) {
5794
+ setSelectedAuthSchemeForOperation(op, e.target.value || "");
5795
+ renderAuthPanel();
5796
+ updateRequestPreview();
5797
+ });
5798
+ selectorWrap.appendChild(select);
5799
+ fields.appendChild(selectorWrap);
5800
+ }
5801
+
5802
+ const schemesToRender = selectedScheme
5803
+ ? [selectedScheme]
5804
+ : (operationSchemeOptions.length ? operationSchemeOptions : schemeNames);
5805
+
5806
+ for (const schemeName of schemesToRender) {
5807
+ const scheme = authSchemes[schemeName];
5808
+ if (!authState[schemeName]) authState[schemeName] = {};
5809
+ const state = authState[schemeName];
5810
+ const type = (scheme.type || "").toLowerCase();
5811
+ const httpScheme = (scheme.scheme || "").toLowerCase();
5812
+ const card = makeSchemeCard(getAuthSchemeDisplayLabel(schemeName), schemeName);
5813
+
5814
+ if (type === "http" && httpScheme === "basic") {
5815
+ card.appendChild(makeField("Username", makeInput("Username", state.username, function(e) {
5816
+ authState[schemeName].username = e.target.value;
5817
+ saveAuthState();
5818
+ updateRequestPreview();
5819
+ })));
5820
+ card.appendChild(makeField("Password", makeInput("Password", state.password, function(e) {
5821
+ authState[schemeName].password = e.target.value;
5822
+ saveAuthState();
5823
+ updateRequestPreview();
5824
+ }, "password")));
5825
+ } else if (type === "apikey") {
5826
+ const paramName = scheme.name || "key";
5827
+ const location = (scheme.in || "header").toLowerCase();
5828
+ card.appendChild(makeField("API Key", makeInput(paramName + " (" + location + ")", state.value, function(e) {
5829
+ authState[schemeName].value = e.target.value;
5830
+ saveAuthState();
5831
+ updateRequestPreview();
5832
+ })));
5833
+ } else if (type === "oauth2") {
5834
+ card.appendChild(makeField("Access Token", makeInput("OAuth2 access token\u2026", state.token, function(e) {
5835
+ authState[schemeName].token = e.target.value;
5836
+ saveAuthState();
5837
+ updateRequestPreview();
5838
+ })));
5839
+ } else if (type === "openidconnect") {
5840
+ card.appendChild(makeField("ID Token / Access Token", makeInput("OpenID Connect token\u2026", state.token, function(e) {
5841
+ authState[schemeName].token = e.target.value;
5842
+ saveAuthState();
5843
+ updateRequestPreview();
5844
+ })));
5845
+ } else if (type === "http" && httpScheme === "digest") {
5846
+ card.appendChild(makeField("Digest Credential", makeInput("Digest token\u2026", state.token, function(e) {
5847
+ authState[schemeName].token = e.target.value;
5848
+ saveAuthState();
5849
+ updateRequestPreview();
5850
+ })));
5851
+ } else if (type === "http" && httpScheme === "bearer") {
5852
+ card.appendChild(makeField("Bearer Token", makeInput("Bearer token\u2026", state.token, function(e) {
5853
+ authState[schemeName].token = e.target.value;
5854
+ saveAuthState();
5855
+ updateRequestPreview();
5856
+ })));
5857
+ } else if (type === "mutualtls") {
5858
+ const hint = document.createElement("p");
5859
+ hint.className = "text-xs opacity-70 leading-relaxed";
5860
+ hint.textContent = "Configured by your client certificate. No token input required.";
5861
+ card.appendChild(hint);
5862
+ } else {
5863
+ card.appendChild(makeField("Token", makeInput("Token\u2026", state.token, function(e) {
5864
+ authState[schemeName].token = e.target.value;
5865
+ saveAuthState();
5866
+ updateRequestPreview();
5867
+ })));
5868
+ }
5869
+ fields.appendChild(card);
5870
+ }
5871
+ }
5872
+
5873
+ let authPanelOpen = true;
5874
+ document.getElementById("auth-toggle").addEventListener("click", function() {
5875
+ authPanelOpen = !authPanelOpen;
5876
+ const fieldsEl = document.getElementById("auth-fields");
5877
+ const chevron = document.getElementById("auth-chevron");
5878
+ if (fieldsEl) fieldsEl.classList.toggle("hidden", !authPanelOpen);
5879
+ if (chevron) chevron.style.transform = authPanelOpen ? "" : "rotate(-90deg)";
5880
+ });
5881
+
5882
+ // Restore selected operation from URL hash, or set hash for the default selection
5883
+ var initMatch = findOpByHash(window.location.hash);
5884
+ if (initMatch) {
5885
+ selected = initMatch;
5886
+ } else if (selected) {
5887
+ history.replaceState(null, "", getOpHash(selected));
5888
+ }
5889
+
5890
+ window.addEventListener("popstate", function() {
5891
+ var match = findOpByHash(window.location.hash);
5892
+ if (match) {
5893
+ selected = match;
5894
+ renderSidebar();
5895
+ renderEndpoint();
5896
+ }
5897
+ });
5898
+
2810
5899
  setMobileSidebarOpen(false);
5900
+ renderAuthPanel();
2811
5901
  renderSidebar();
2812
5902
  renderEndpoint();
2813
5903
  </script>
@@ -2816,6 +5906,106 @@ function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, logoDarkPa
2816
5906
  }
2817
5907
 
2818
5908
  // src/openapi/generator.ts
5909
+ var AUTH_KIND_VALUES2 = new Set(Object.values(AuthKind));
5910
+ var DEFAULT_SECURITY_SCHEME_NAMES = {
5911
+ ["ApiKey" /* ApiKey */]: "apiKeyAuth",
5912
+ ["HttpBasic" /* HttpBasic */]: "basicAuth",
5913
+ ["HttpBearer" /* HttpBearer */]: "bearerAuth",
5914
+ ["HttpDigest" /* HttpDigest */]: "digestAuth",
5915
+ ["OAuth2" /* OAuth2 */]: "oauth2Auth",
5916
+ ["OpenIdConnect" /* OpenIdConnect */]: "openIdConnectAuth",
5917
+ ["MutualTls" /* MutualTls */]: "mutualTlsAuth"
5918
+ };
5919
+ function isAuthKind(value) {
5920
+ return typeof value === "string" && AUTH_KIND_VALUES2.has(value);
5921
+ }
5922
+ function resolveRouteAuthKind(routeAuth, defaultAuthKind) {
5923
+ if (routeAuth === undefined || routeAuth === false || routeAuth === null) {
5924
+ return null;
5925
+ }
5926
+ if (routeAuth === true) {
5927
+ return defaultAuthKind;
5928
+ }
5929
+ if (isAuthKind(routeAuth)) {
5930
+ return routeAuth;
5931
+ }
5932
+ return defaultAuthKind;
5933
+ }
5934
+ function resolveSecuritySchemeName(kind, authOptions) {
5935
+ const configuredName = authOptions?.securitySchemeNames?.[kind];
5936
+ if (typeof configuredName === "string" && configuredName.trim().length > 0) {
5937
+ return configuredName.trim();
5938
+ }
5939
+ return DEFAULT_SECURITY_SCHEME_NAMES[kind];
5940
+ }
5941
+ function toOpenApiSecurityScheme(kind) {
5942
+ switch (kind) {
5943
+ case "ApiKey" /* ApiKey */:
5944
+ return {
5945
+ type: "apiKey" /* ApiKey */,
5946
+ name: "X-API-Key",
5947
+ in: "header"
5948
+ };
5949
+ case "HttpBasic" /* HttpBasic */:
5950
+ return {
5951
+ type: "http" /* Http */,
5952
+ scheme: "basic" /* Basic */
5953
+ };
5954
+ case "HttpBearer" /* HttpBearer */:
5955
+ return {
5956
+ type: "http" /* Http */,
5957
+ scheme: "bearer" /* Bearer */,
5958
+ bearerFormat: "JWT"
5959
+ };
5960
+ case "HttpDigest" /* HttpDigest */:
5961
+ return {
5962
+ type: "http" /* Http */,
5963
+ scheme: "digest" /* Digest */
5964
+ };
5965
+ case "OAuth2" /* OAuth2 */:
5966
+ return {
5967
+ type: "oauth2" /* OAuth2 */,
5968
+ flows: {
5969
+ authorizationCode: {
5970
+ authorizationUrl: "https://example.com/oauth/authorize",
5971
+ tokenUrl: "https://example.com/oauth/token",
5972
+ scopes: {}
5973
+ }
5974
+ }
5975
+ };
5976
+ case "OpenIdConnect" /* OpenIdConnect */:
5977
+ return {
5978
+ type: "openIdConnect" /* OpenIdConnect */,
5979
+ openIdConnectUrl: "https://example.com/.well-known/openid-configuration"
5980
+ };
5981
+ case "MutualTls" /* MutualTls */:
5982
+ return {
5983
+ type: "mutualTLS" /* MutualTls */
5984
+ };
5985
+ default: {
5986
+ const exhaustiveCheck = kind;
5987
+ return exhaustiveCheck;
5988
+ }
5989
+ }
5990
+ }
5991
+ function resolveSecurityScheme(kind, authOptions) {
5992
+ const defaultScheme = toOpenApiSecurityScheme(kind);
5993
+ const override = authOptions?.securitySchemes?.[kind];
5994
+ if (!override) {
5995
+ return defaultScheme;
5996
+ }
5997
+ const merged = {
5998
+ ...defaultScheme,
5999
+ ...override
6000
+ };
6001
+ if (isRecord(defaultScheme.flows) && isRecord(override.flows)) {
6002
+ merged.flows = {
6003
+ ...defaultScheme.flows,
6004
+ ...override.flows
6005
+ };
6006
+ }
6007
+ return merged;
6008
+ }
2819
6009
  function isJSONSchemaCapable(schema) {
2820
6010
  const standard = schema?.["~standard"];
2821
6011
  const converter = standard?.jsonSchema;
@@ -2887,15 +6077,88 @@ function isNoBodyResponseStatus(status) {
2887
6077
  return numericStatus >= 100 && numericStatus < 200 || numericStatus === 204 || numericStatus === 205 || numericStatus === 304;
2888
6078
  }
2889
6079
  function getResponseDescription(status) {
2890
- if (status === "204")
2891
- return "No Content";
2892
- if (status === "205")
2893
- return "Reset Content";
2894
- if (status === "304")
2895
- return "Not Modified";
6080
+ const knownDescriptions = {
6081
+ "100": "Continue",
6082
+ "101": "Switching Protocols",
6083
+ "102": "Processing",
6084
+ "103": "Early Hints",
6085
+ "200": "OK",
6086
+ "201": "Created",
6087
+ "202": "Accepted",
6088
+ "203": "Non-Authoritative Information",
6089
+ "204": "No Content",
6090
+ "205": "Reset Content",
6091
+ "206": "Partial Content",
6092
+ "207": "Multi-Status",
6093
+ "208": "Already Reported",
6094
+ "226": "IM Used",
6095
+ "300": "Multiple Choices",
6096
+ "301": "Moved Permanently",
6097
+ "302": "Found",
6098
+ "303": "See Other",
6099
+ "304": "Not Modified",
6100
+ "305": "Use Proxy",
6101
+ "307": "Temporary Redirect",
6102
+ "308": "Permanent Redirect",
6103
+ "400": "Bad Request",
6104
+ "401": "Unauthorized",
6105
+ "402": "Payment Required",
6106
+ "403": "Forbidden",
6107
+ "404": "Not Found",
6108
+ "405": "Method Not Allowed",
6109
+ "406": "Not Acceptable",
6110
+ "407": "Proxy Authentication Required",
6111
+ "408": "Request Timeout",
6112
+ "409": "Conflict",
6113
+ "410": "Gone",
6114
+ "411": "Length Required",
6115
+ "412": "Precondition Failed",
6116
+ "413": "Payload Too Large",
6117
+ "414": "URI Too Long",
6118
+ "415": "Unsupported Media Type",
6119
+ "416": "Range Not Satisfiable",
6120
+ "417": "Expectation Failed",
6121
+ "418": "I'm a teapot",
6122
+ "421": "Misdirected Request",
6123
+ "422": "Unprocessable Content",
6124
+ "423": "Locked",
6125
+ "424": "Failed Dependency",
6126
+ "425": "Too Early",
6127
+ "426": "Upgrade Required",
6128
+ "428": "Precondition Required",
6129
+ "429": "Too Many Requests",
6130
+ "431": "Request Header Fields Too Large",
6131
+ "451": "Unavailable For Legal Reasons",
6132
+ "500": "Internal Server Error",
6133
+ "501": "Not Implemented",
6134
+ "502": "Bad Gateway",
6135
+ "503": "Service Unavailable",
6136
+ "504": "Gateway Timeout",
6137
+ "505": "HTTP Version Not Supported",
6138
+ "506": "Variant Also Negotiates",
6139
+ "507": "Insufficient Storage",
6140
+ "508": "Loop Detected",
6141
+ "510": "Not Extended",
6142
+ "511": "Network Authentication Required"
6143
+ };
6144
+ if (knownDescriptions[status]) {
6145
+ return knownDescriptions[status];
6146
+ }
2896
6147
  const numericStatus = Number(status);
2897
6148
  if (Number.isInteger(numericStatus) && numericStatus >= 100 && numericStatus < 200) {
2898
- return "Informational";
6149
+ return "Informational Response";
6150
+ }
6151
+ if (Number.isInteger(numericStatus) && numericStatus >= 200 && numericStatus < 300) {
6152
+ return "Successful Response";
6153
+ }
6154
+ if (Number.isInteger(numericStatus) && numericStatus >= 300 && numericStatus < 400) {
6155
+ return "Redirection";
6156
+ }
6157
+ if (Number.isInteger(numericStatus) && numericStatus >= 400 && numericStatus < 500) {
6158
+ return "Client Error";
6159
+ }
6160
+ if (Number.isInteger(numericStatus) && numericStatus >= 500 && numericStatus < 600) {
6161
+ return "Server Error";
2899
6162
  }
2900
6163
  return "OK";
2901
6164
  }
@@ -3330,6 +6593,8 @@ function addOutputSchemasToOperation(operation, routePath, routeSchema, target,
3330
6593
  function generateOpenAPIDocument(routes, options) {
3331
6594
  const warnings = [];
3332
6595
  const paths = {};
6596
+ const defaultAuthKind = "HttpBearer" /* HttpBearer */;
6597
+ const usedAuthKinds = new Set;
3333
6598
  for (const route of routes) {
3334
6599
  if (route.options.expose === false)
3335
6600
  continue;
@@ -3343,12 +6608,50 @@ function generateOpenAPIDocument(routes, options) {
3343
6608
  operationId: createOperationId(method, openapiPath),
3344
6609
  tags: [route.options.schema?.tag || inferTagFromPath(route.path)]
3345
6610
  };
6611
+ if (typeof route.options.schema?.summary === "string" && route.options.schema.summary.trim()) {
6612
+ operation.summary = route.options.schema.summary.trim();
6613
+ }
6614
+ const routeSchemaDescription = typeof route.options.schema?.description === "string" && route.options.schema.description.trim() ? route.options.schema.description.trim() : typeof route.options.schema?.descrition === "string" && route.options.schema.descrition.trim() ? route.options.schema.descrition.trim() : undefined;
6615
+ if (routeSchemaDescription) {
6616
+ operation.description = routeSchemaDescription;
6617
+ }
6618
+ if (route.options.deprecated === true) {
6619
+ operation.deprecated = true;
6620
+ }
6621
+ const routeAuthKind = resolveRouteAuthKind(route.options.auth, defaultAuthKind);
6622
+ if (routeAuthKind) {
6623
+ usedAuthKinds.add(routeAuthKind);
6624
+ const securitySchemeName = resolveSecuritySchemeName(routeAuthKind, options.auth);
6625
+ operation.security = [{ [securitySchemeName]: [] }];
6626
+ }
3346
6627
  const inputJSONSchema = convertInputSchema(route.path, route.options.schema?.input, options.target, warnings);
3347
6628
  if (inputJSONSchema) {
6629
+ if (!operation.summary && typeof inputJSONSchema.title === "string" && inputJSONSchema.title.trim()) {
6630
+ operation.summary = inputJSONSchema.title.trim();
6631
+ }
6632
+ if (!operation.description && typeof inputJSONSchema.description === "string" && inputJSONSchema.description.trim()) {
6633
+ operation.description = inputJSONSchema.description.trim();
6634
+ }
3348
6635
  addStructuredInputToOperation(operation, inputJSONSchema);
3349
6636
  }
3350
6637
  addMissingPathParameters(operation, route.path);
3351
6638
  addOutputSchemasToOperation(operation, route.path, route.options.schema || {}, options.target, warnings);
6639
+ if (!operation.summary || !operation.description) {
6640
+ const responseEntries = Object.values(operation.responses || {});
6641
+ for (const response of responseEntries) {
6642
+ const responseSchema = response?.content?.["application/json"]?.schema;
6643
+ if (!responseSchema || typeof responseSchema !== "object")
6644
+ continue;
6645
+ if (!operation.summary && typeof responseSchema.title === "string" && responseSchema.title.trim()) {
6646
+ operation.summary = responseSchema.title.trim();
6647
+ }
6648
+ if (!operation.description && typeof responseSchema.description === "string" && responseSchema.description.trim()) {
6649
+ operation.description = responseSchema.description.trim();
6650
+ }
6651
+ if (operation.summary && operation.description)
6652
+ break;
6653
+ }
6654
+ }
3352
6655
  paths[openapiPath] ||= {};
3353
6656
  paths[openapiPath][method] = operation;
3354
6657
  }
@@ -3362,6 +6665,16 @@ function generateOpenAPIDocument(routes, options) {
3362
6665
  },
3363
6666
  paths
3364
6667
  };
6668
+ if (usedAuthKinds.size > 0) {
6669
+ const securitySchemes = {};
6670
+ for (const authKind of usedAuthKinds) {
6671
+ const name = resolveSecuritySchemeName(authKind, options.auth);
6672
+ securitySchemes[name] = resolveSecurityScheme(authKind, options.auth);
6673
+ }
6674
+ document.components = {
6675
+ securitySchemes
6676
+ };
6677
+ }
3365
6678
  return {
3366
6679
  document,
3367
6680
  warnings
@@ -3498,6 +6811,17 @@ var OPENAPI_FAVICON_ASSETS = [
3498
6811
  var DOCS_HTML_CACHE_CONTROL = "public, max-age=0, must-revalidate";
3499
6812
  var DOCS_ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
3500
6813
  var DOCS_ASSET_ERROR_CACHE_CONTROL = "no-store";
6814
+ var DEFAULT_PORT = 3000;
6815
+ function normalizePort(port) {
6816
+ if (port === undefined) {
6817
+ return DEFAULT_PORT;
6818
+ }
6819
+ const normalized = Number(port);
6820
+ if (!Number.isInteger(normalized) || normalized < 0 || normalized > 65535) {
6821
+ throw new Error(`Invalid port: ${String(port)}. Port must be an integer between 0 and 65535.`);
6822
+ }
6823
+ return normalized;
6824
+ }
3501
6825
  function escapeRegex(value) {
3502
6826
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3503
6827
  }
@@ -3559,6 +6883,9 @@ class VectorServer {
3559
6883
  this.router.setCorsHandler(this.corsHeadersEntries ? null : this.corsHandler.corsify);
3560
6884
  }
3561
6885
  }
6886
+ setCheckpointGateway(gateway) {
6887
+ this.router.setCheckpointGateway(gateway);
6888
+ }
3562
6889
  normalizeOpenAPIConfig(openapi, development) {
3563
6890
  const isDev = development !== false && true;
3564
6891
  const defaultEnabled = isDev;
@@ -3590,7 +6917,8 @@ class VectorServer {
3590
6917
  path: openapiObject.path || "/openapi.json",
3591
6918
  target: openapiObject.target || "openapi-3.0",
3592
6919
  docs,
3593
- info: openapiObject.info
6920
+ info: openapiObject.info,
6921
+ auth: openapiObject.auth
3594
6922
  };
3595
6923
  }
3596
6924
  isDocsReservedPath(path) {
@@ -3612,7 +6940,8 @@ class VectorServer {
3612
6940
  const routes = this.router.getRouteDefinitions().filter((route) => !this.isDocsReservedPath(route.path));
3613
6941
  const result = generateOpenAPIDocument(routes, {
3614
6942
  target: this.openapiConfig.target,
3615
- info: this.openapiConfig.info
6943
+ info: this.openapiConfig.info,
6944
+ auth: this.openapiConfig.auth
3616
6945
  });
3617
6946
  if (!this.openapiWarningsLogged && result.warnings.length > 0) {
3618
6947
  if (this.shouldLogOpenAPIConversionWarnings()) {
@@ -3832,10 +7161,10 @@ class VectorServer {
3832
7161
  return response;
3833
7162
  }
3834
7163
  async start() {
3835
- const port = this.config.port ?? 3000;
7164
+ const port = normalizePort(this.config.port);
3836
7165
  const hostname = this.config.hostname || "localhost";
3837
7166
  this.validateReservedOpenAPIPaths();
3838
- const fallbackFetch = async (request) => {
7167
+ const appFetch = async (request) => {
3839
7168
  try {
3840
7169
  if (this.corsHandler && request.method === "OPTIONS") {
3841
7170
  return this.corsHandler.preflight(request);
@@ -3844,7 +7173,7 @@ class VectorServer {
3844
7173
  if (openapiResponse) {
3845
7174
  return this.applyCors(openapiResponse, request);
3846
7175
  }
3847
- return this.applyCors(STATIC_RESPONSES.NOT_FOUND.clone(), request);
7176
+ return await this.router.handle(request);
3848
7177
  } catch (error) {
3849
7178
  console.error("Server error:", error);
3850
7179
  return this.applyCors(new Response("Internal Server Error", { status: 500 }), request);
@@ -3855,8 +7184,7 @@ class VectorServer {
3855
7184
  port,
3856
7185
  hostname,
3857
7186
  reusePort: this.config.reusePort !== false,
3858
- routes: this.router.getRouteTable(),
3859
- fetch: fallbackFetch,
7187
+ fetch: appFetch,
3860
7188
  idleTimeout: this.config.idleTimeout ?? 60,
3861
7189
  error: (error, request) => {
3862
7190
  console.error("[ERROR] Server error:", error);
@@ -3895,7 +7223,7 @@ class VectorServer {
3895
7223
  return this.server;
3896
7224
  }
3897
7225
  getPort() {
3898
- return this.server?.port ?? this.config.port ?? 3000;
7226
+ return this.server?.port ?? normalizePort(this.config.port);
3899
7227
  }
3900
7228
  getHostname() {
3901
7229
  return this.server?.hostname || this.config.hostname || "localhost";
@@ -3921,6 +7249,7 @@ class Vector {
3921
7249
  _protectedHandler = null;
3922
7250
  _cacheHandler = null;
3923
7251
  shutdownPromise = null;
7252
+ checkpointProcessManager = null;
3924
7253
  constructor() {
3925
7254
  this.middlewareManager = new MiddlewareManager;
3926
7255
  this.authManager = new AuthManager;
@@ -3959,6 +7288,10 @@ class Vector {
3959
7288
  this.router.route(options, handler);
3960
7289
  }
3961
7290
  async startServer(config) {
7291
+ if (this.checkpointProcessManager) {
7292
+ await this.checkpointProcessManager.stopAll();
7293
+ this.checkpointProcessManager = null;
7294
+ }
3962
7295
  this.config = { ...this.config, ...config };
3963
7296
  const routeDefaults = { ...this.config.defaults?.route };
3964
7297
  this.router.setRouteBooleanDefaults(routeDefaults);
@@ -3981,6 +7314,9 @@ class Vector {
3981
7314
  }
3982
7315
  this.server = new VectorServer(this.router, this.config);
3983
7316
  const bunServer = await this.server.start();
7317
+ if (this.config.checkpoint && this.config.checkpoint.enabled !== false) {
7318
+ await this.enableCheckpointGateway(this.config.checkpoint);
7319
+ }
3984
7320
  return bunServer;
3985
7321
  }
3986
7322
  async discoverRoutes() {
@@ -3999,7 +7335,7 @@ class Vector {
3999
7335
  for (const route of routes) {
4000
7336
  try {
4001
7337
  const importPath = toFileUrl(route.path);
4002
- const module = await import(importPath);
7338
+ const module = await import(`${importPath}?t=${Date.now()}`);
4003
7339
  const exported = route.name === "default" ? module.default : module[route.name];
4004
7340
  if (exported) {
4005
7341
  if (this.isRouteDefinition(exported)) {
@@ -4052,6 +7388,41 @@ class Vector {
4052
7388
  return value !== null && typeof value === "object" && "entry" in value && "options" in value && "handler" in value && typeof value.handler === "function";
4053
7389
  }
4054
7390
  logRouteLoaded(_) {}
7391
+ async enableCheckpointGateway(checkpointConfig) {
7392
+ if (!this.server) {
7393
+ throw new Error("Cannot enable checkpoint gateway before server is started. Call startServer() first.");
7394
+ }
7395
+ const { CheckpointManager: CheckpointManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
7396
+ const { CheckpointProcessManager: CheckpointProcessManager2 } = await Promise.resolve().then(() => (init_process_manager(), exports_process_manager));
7397
+ const { CheckpointResolver: CheckpointResolver2 } = await Promise.resolve().then(() => exports_resolver);
7398
+ const { CheckpointForwarder: CheckpointForwarder2 } = await Promise.resolve().then(() => exports_forwarder);
7399
+ const { CheckpointGateway: CheckpointGateway2 } = await Promise.resolve().then(() => exports_gateway);
7400
+ if (this.checkpointProcessManager) {
7401
+ await this.checkpointProcessManager.stopAll();
7402
+ this.checkpointProcessManager = null;
7403
+ }
7404
+ const manager = new CheckpointManager2(checkpointConfig);
7405
+ const processManager = new CheckpointProcessManager2({
7406
+ idleTimeoutMs: checkpointConfig?.idleTimeoutMs
7407
+ });
7408
+ this.checkpointProcessManager = processManager;
7409
+ const resolver = new CheckpointResolver2(manager, processManager, {
7410
+ versionHeader: checkpointConfig?.versionHeader,
7411
+ cacheKeyOverride: checkpointConfig?.cacheKeyOverride
7412
+ });
7413
+ const forwarder = new CheckpointForwarder2;
7414
+ const gateway = new CheckpointGateway2(resolver, forwarder);
7415
+ this.server.setCheckpointGateway(gateway);
7416
+ const active = await manager.getActive();
7417
+ if (active) {
7418
+ try {
7419
+ const manifest = await manager.readManifest(active.version);
7420
+ await processManager.spawn(manifest, manager.getStorageDir());
7421
+ } catch (err) {
7422
+ console.error(`[Checkpoint] Failed to start active checkpoint ${active.version}:`, err);
7423
+ }
7424
+ }
7425
+ }
4055
7426
  stop() {
4056
7427
  if (this.server) {
4057
7428
  this.server.stop();
@@ -4064,6 +7435,10 @@ class Vector {
4064
7435
  }
4065
7436
  this.shutdownPromise = (async () => {
4066
7437
  this.stop();
7438
+ if (this.checkpointProcessManager) {
7439
+ await this.checkpointProcessManager.stopAll();
7440
+ this.checkpointProcessManager = null;
7441
+ }
4067
7442
  if (typeof this.config.shutdown === "function") {
4068
7443
  await this.config.shutdown();
4069
7444
  }
@@ -4130,7 +7505,12 @@ async function startVector(options = {}) {
4130
7505
 
4131
7506
  // src/cli/index.ts
4132
7507
  var args = typeof Bun !== "undefined" ? Bun.argv.slice(2) : process.argv.slice(2);
4133
- var { values, positionals } = parseArgs({
7508
+ if (args[0] === "checkpoint") {
7509
+ const { runCheckpointCli: runCheckpointCli2 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
7510
+ await runCheckpointCli2(args.slice(1));
7511
+ process.exit(0);
7512
+ }
7513
+ var { values, positionals } = parseArgs2({
4134
7514
  args,
4135
7515
  options: {
4136
7516
  port: {
@@ -4252,7 +7632,7 @@ Listening on ${cyan}http://${config.hostname}:${config.port}${reset}
4252
7632
  if (app) {
4253
7633
  app.stop();
4254
7634
  }
4255
- await new Promise((resolve3) => setTimeout(resolve3, 100));
7635
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
4256
7636
  try {
4257
7637
  const result2 = await startServer();
4258
7638
  server = result2.server;
@@ -4299,8 +7679,9 @@ switch (command) {
4299
7679
  Usage: vector [command] [options]
4300
7680
 
4301
7681
  Commands:
4302
- dev Start development server (default)
4303
- start Start production server
7682
+ dev Start development server (default)
7683
+ start Start production server
7684
+ checkpoint Manage versioned checkpoints (publish, list, rollback, remove)
4304
7685
 
4305
7686
  Options:
4306
7687
  -p, --port <port> Port to listen on (default: 3000)