veryfront 0.1.748 → 0.1.749

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 (83) hide show
  1. package/esm/cli/templates/manifest.js +2 -2
  2. package/esm/deno.js +1 -1
  3. package/esm/src/build/production-build/static-generation.d.ts.map +1 -1
  4. package/esm/src/build/production-build/static-generation.js +2 -0
  5. package/esm/src/cache/backends/disk.d.ts +1 -1
  6. package/esm/src/cache/backends/disk.d.ts.map +1 -1
  7. package/esm/src/cache/backends/disk.js +41 -13
  8. package/esm/src/cache/backends/glob.d.ts +6 -0
  9. package/esm/src/cache/backends/glob.d.ts.map +1 -0
  10. package/esm/src/cache/backends/glob.js +41 -0
  11. package/esm/src/cache/backends/memory.d.ts +1 -1
  12. package/esm/src/cache/backends/memory.d.ts.map +1 -1
  13. package/esm/src/cache/backends/memory.js +14 -12
  14. package/esm/src/cache/backends/redis.d.ts.map +1 -1
  15. package/esm/src/cache/backends/redis.js +33 -7
  16. package/esm/src/cache/dependency-graph.d.ts +1 -0
  17. package/esm/src/cache/dependency-graph.d.ts.map +1 -1
  18. package/esm/src/cache/dependency-graph.js +15 -1
  19. package/esm/src/cache/paths.d.ts.map +1 -1
  20. package/esm/src/cache/paths.js +5 -0
  21. package/esm/src/embedding/index.d.ts +4 -1
  22. package/esm/src/embedding/index.d.ts.map +1 -1
  23. package/esm/src/embedding/index.js +3 -1
  24. package/esm/src/embedding/rag-store.d.ts.map +1 -1
  25. package/esm/src/embedding/rag-store.js +157 -8
  26. package/esm/src/embedding/react/use-uploads.d.ts +1 -0
  27. package/esm/src/embedding/react/use-uploads.d.ts.map +1 -1
  28. package/esm/src/embedding/react/use-uploads.js +4 -1
  29. package/esm/src/embedding/upload-handler.d.ts +29 -8
  30. package/esm/src/embedding/upload-handler.d.ts.map +1 -1
  31. package/esm/src/embedding/upload-handler.js +66 -6
  32. package/esm/src/html/html-shell-generator.js +5 -5
  33. package/esm/src/html/nonce-injection.d.ts.map +1 -1
  34. package/esm/src/html/nonce-injection.js +11 -4
  35. package/esm/src/html/utils.d.ts +5 -0
  36. package/esm/src/html/utils.d.ts.map +1 -1
  37. package/esm/src/html/utils.js +19 -9
  38. package/esm/src/modules/react-loader/ssr-module-loader/import-rewriter.d.ts.map +1 -1
  39. package/esm/src/modules/react-loader/ssr-module-loader/import-rewriter.js +12 -5
  40. package/esm/src/modules/server/module-batch-handler.d.ts +1 -0
  41. package/esm/src/modules/server/module-batch-handler.d.ts.map +1 -1
  42. package/esm/src/modules/server/module-batch-handler.js +52 -26
  43. package/esm/src/modules/server/module-server.d.ts.map +1 -1
  44. package/esm/src/modules/server/module-server.js +87 -69
  45. package/esm/src/modules/server/module-source-resolution-cache.d.ts +16 -0
  46. package/esm/src/modules/server/module-source-resolution-cache.d.ts.map +1 -0
  47. package/esm/src/modules/server/module-source-resolution-cache.js +43 -0
  48. package/esm/src/oauth/providers/base.d.ts +0 -2
  49. package/esm/src/oauth/providers/base.d.ts.map +1 -1
  50. package/esm/src/oauth/providers/base.js +35 -10
  51. package/esm/src/oauth/token-store/memory.d.ts +7 -0
  52. package/esm/src/oauth/token-store/memory.d.ts.map +1 -1
  53. package/esm/src/oauth/token-store/memory.js +14 -1
  54. package/esm/src/observability/file-log-subscriber.d.ts +1 -0
  55. package/esm/src/observability/file-log-subscriber.d.ts.map +1 -1
  56. package/esm/src/observability/file-log-subscriber.js +2 -1
  57. package/esm/src/observability/tracing/api-shim.d.ts +1 -0
  58. package/esm/src/observability/tracing/api-shim.d.ts.map +1 -1
  59. package/esm/src/observability/tracing/api-shim.js +7 -0
  60. package/esm/src/observability/tracing/otlp-setup.d.ts.map +1 -1
  61. package/esm/src/observability/tracing/otlp-setup.js +17 -7
  62. package/esm/src/proxy/handler.d.ts +5 -2
  63. package/esm/src/proxy/handler.d.ts.map +1 -1
  64. package/esm/src/proxy/handler.js +26 -25
  65. package/esm/src/proxy/main.js +8 -11
  66. package/esm/src/rendering/renderer.d.ts.map +1 -1
  67. package/esm/src/rendering/renderer.js +32 -12
  68. package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
  69. package/esm/src/server/context/cache-invalidation.js +2 -0
  70. package/esm/src/server/handlers/dev/scripts/error-overlay.d.ts.map +1 -1
  71. package/esm/src/server/handlers/dev/scripts/error-overlay.js +32 -18
  72. package/esm/src/server/handlers/monitoring/client-log.handler.d.ts +1 -0
  73. package/esm/src/server/handlers/monitoring/client-log.handler.d.ts.map +1 -1
  74. package/esm/src/server/handlers/monitoring/client-log.handler.js +39 -5
  75. package/esm/src/server/handlers/request/module/batch-module-handler.d.ts.map +1 -1
  76. package/esm/src/server/handlers/request/module/batch-module-handler.js +2 -1
  77. package/esm/src/utils/logger/redact.d.ts.map +1 -1
  78. package/esm/src/utils/logger/redact.js +13 -1
  79. package/esm/src/utils/redis-client.d.ts +1 -0
  80. package/esm/src/utils/redis-client.d.ts.map +1 -1
  81. package/esm/src/utils/version-constant.d.ts +1 -1
  82. package/esm/src/utils/version-constant.js +1 -1
  83. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as dntShim from "../../_dnt.shims.js";
2
- import { isNotFoundError, mkdir, readDir, readTextFile, writeTextFile, } from "../platform/compat/fs.js";
2
+ import { isNotFoundError, mkdir, readDir, readTextFile, stat, writeTextFile, } from "../platform/compat/fs.js";
3
3
  import { dirname, extname, join } from "../platform/compat/path/basic-operations.js";
4
4
  import { serverLogger } from "../utils/index.js";
5
5
  import { isVeryfrontCloudEnabled } from "../platform/cloud/resolver.js";
@@ -12,6 +12,77 @@ import { cosineSimilarity } from "../runtime/runtime-bridge.js";
12
12
  import { INVALID_ARGUMENT } from "../errors/index.js";
13
13
  /** Default number of top results returned by similarity search. */
14
14
  const DEFAULT_TOP_K = 5;
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function isFiniteNumber(value) {
19
+ return typeof value === "number" && Number.isFinite(value);
20
+ }
21
+ function isNumberArray(value) {
22
+ return Array.isArray(value) && value.every(isFiniteNumber);
23
+ }
24
+ function isNonNegativeInteger(value) {
25
+ return Number.isInteger(value) && Number(value) >= 0;
26
+ }
27
+ function isRagDocumentMeta(value) {
28
+ if (!isRecord(value))
29
+ return false;
30
+ return typeof value.id === "string" &&
31
+ typeof value.title === "string" &&
32
+ typeof value.source === "string" &&
33
+ typeof value.type === "string" &&
34
+ isFiniteNumber(value.createdAt) &&
35
+ (value.url === undefined || typeof value.url === "string");
36
+ }
37
+ function isRagChunk(value) {
38
+ if (!isRecord(value))
39
+ return false;
40
+ return typeof value.id === "string" &&
41
+ typeof value.documentId === "string" &&
42
+ typeof value.text === "string" &&
43
+ isNumberArray(value.embedding) &&
44
+ isNonNegativeInteger(value.index);
45
+ }
46
+ function isRagStoreData(value) {
47
+ if (!isRecord(value))
48
+ return false;
49
+ return Array.isArray(value.documents) &&
50
+ value.documents.every(isRagDocumentMeta) &&
51
+ Array.isArray(value.chunks) &&
52
+ value.chunks.every(isRagChunk);
53
+ }
54
+ function cloneRagStoreData(data) {
55
+ return {
56
+ documents: data.documents.map((document) => ({ ...document })),
57
+ chunks: data.chunks.map((chunk) => ({
58
+ ...chunk,
59
+ embedding: [...chunk.embedding],
60
+ })),
61
+ };
62
+ }
63
+ function hashStoreText(text) {
64
+ let hash = 0x811c9dc5;
65
+ for (let i = 0; i < text.length; i++) {
66
+ hash ^= text.charCodeAt(i);
67
+ hash = Math.imul(hash, 0x01000193) >>> 0;
68
+ }
69
+ return hash.toString(16).padStart(8, "0");
70
+ }
71
+ function sameStoreFileSignature(left, right) {
72
+ return left.contentHash === right.contentHash &&
73
+ left.changeTimeMs === right.changeTimeMs &&
74
+ left.mtimeMs === right.mtimeMs &&
75
+ left.size === right.size;
76
+ }
77
+ function isLegacyStoredChunk(value) {
78
+ if (!isRecord(value))
79
+ return false;
80
+ return typeof value.id === "string" &&
81
+ typeof value.uploadId === "string" &&
82
+ typeof value.text === "string" &&
83
+ isNumberArray(value.embedding) &&
84
+ isNonNegativeInteger(value.index);
85
+ }
15
86
  /**
16
87
  * Creates a persistent RAG store with lazy embedding and similarity search.
17
88
  *
@@ -109,6 +180,7 @@ function createLocalJsonRagStore(config) {
109
180
  const contentDir = config.contentDir;
110
181
  const contentExtensions = new Set(config.contentExtensions ?? [".md", ".mdx", ".txt"]);
111
182
  const chunkOptions = config.chunkOptions;
183
+ let storeDataCache = null;
112
184
  const MAX_TEXT_LENGTH = 5 * 1024 * 1024; // 5 MB text limit per document
113
185
  // Serialize all load→modify→save operations to prevent concurrent overwrites.
114
186
  // NOTE: This is a single-process, single-instance mutex. In multi-instance
@@ -133,7 +205,10 @@ function createLocalJsonRagStore(config) {
133
205
  if (!value || typeof value !== "object")
134
206
  return false;
135
207
  const data = value;
136
- return Array.isArray(data.uploads) && Array.isArray(data.chunks);
208
+ return Array.isArray(data.uploads) &&
209
+ data.uploads.every(isRagDocumentMeta) &&
210
+ Array.isArray(data.chunks) &&
211
+ data.chunks.every(isLegacyStoredChunk);
137
212
  }
138
213
  function migrateLegacyUploadStoreData(data) {
139
214
  return {
@@ -147,25 +222,98 @@ function createLocalJsonRagStore(config) {
147
222
  })),
148
223
  };
149
224
  }
225
+ async function getStoreFileMetadata() {
226
+ try {
227
+ if (typeof dntShim.Deno !== "undefined") {
228
+ const info = await dntShim.Deno.stat(storagePath);
229
+ const changeTime = info.ctime;
230
+ return {
231
+ changeTimeMs: changeTime?.getTime() ?? null,
232
+ mtimeMs: info.mtime?.getTime() ?? null,
233
+ size: info.size,
234
+ };
235
+ }
236
+ const info = await stat(storagePath);
237
+ let changeTimeMs = null;
238
+ try {
239
+ const nodeFs = await import("node:fs/promises");
240
+ const nodeInfo = await nodeFs.stat(storagePath);
241
+ changeTimeMs = nodeInfo.ctime.getTime();
242
+ }
243
+ catch {
244
+ // expected: not every runtime exposes a file change time
245
+ }
246
+ return {
247
+ changeTimeMs,
248
+ mtimeMs: info.mtime?.getTime() ?? null,
249
+ size: info.size,
250
+ };
251
+ }
252
+ catch (err) {
253
+ if (isNotFoundError(err))
254
+ return null;
255
+ throw err;
256
+ }
257
+ }
258
+ async function readStoreFileSnapshot() {
259
+ const metadata = await getStoreFileMetadata();
260
+ if (metadata === null)
261
+ return null;
262
+ const text = await readTextFile(storagePath);
263
+ return {
264
+ signature: {
265
+ ...metadata,
266
+ contentHash: hashStoreText(text),
267
+ },
268
+ text,
269
+ };
270
+ }
271
+ async function updateStoreDataCache(data, payload) {
272
+ const metadata = await getStoreFileMetadata();
273
+ storeDataCache = metadata === null ? null : {
274
+ signature: {
275
+ ...metadata,
276
+ contentHash: hashStoreText(payload),
277
+ },
278
+ data: cloneRagStoreData(data),
279
+ };
280
+ }
150
281
  async function load() {
151
282
  try {
152
- const data = await readTextFile(storagePath);
153
- const parsed = JSON.parse(data);
283
+ const snapshot = await readStoreFileSnapshot();
284
+ if (snapshot === null) {
285
+ storeDataCache = null;
286
+ return { documents: [], chunks: [] };
287
+ }
288
+ if (storeDataCache !== null &&
289
+ sameStoreFileSignature(storeDataCache.signature, snapshot.signature)) {
290
+ return cloneRagStoreData(storeDataCache.data);
291
+ }
292
+ const parsed = JSON.parse(snapshot.text);
154
293
  if (isLegacyUploadStoreData(parsed)) {
155
- return migrateLegacyUploadStoreData(parsed);
294
+ const migrated = migrateLegacyUploadStoreData(parsed);
295
+ storeDataCache = {
296
+ signature: snapshot.signature,
297
+ data: cloneRagStoreData(migrated),
298
+ };
299
+ return cloneRagStoreData(migrated);
156
300
  }
157
- if (!parsed || !Array.isArray(parsed.documents) || !Array.isArray(parsed.chunks)) {
301
+ if (!isRagStoreData(parsed)) {
158
302
  serverLogger.warn("[rag-store] Corrupted store file, resetting", { storagePath });
303
+ storeDataCache = null;
159
304
  return { documents: [], chunks: [] };
160
305
  }
161
- return parsed;
306
+ storeDataCache = { signature: snapshot.signature, data: cloneRagStoreData(parsed) };
307
+ return cloneRagStoreData(parsed);
162
308
  }
163
309
  catch (err) {
164
310
  // File not found is expected on first run; anything else is worth logging
165
311
  if (isNotFoundError(err)) {
312
+ storeDataCache = null;
166
313
  return { documents: [], chunks: [] };
167
314
  }
168
315
  serverLogger.warn("[rag-store] Failed to load store, resetting", err);
316
+ storeDataCache = null;
169
317
  return { documents: [], chunks: [] };
170
318
  }
171
319
  }
@@ -174,7 +322,7 @@ function createLocalJsonRagStore(config) {
174
322
  if (dir && dir !== ".") {
175
323
  await mkdir(dir, { recursive: true });
176
324
  }
177
- const payload = JSON.stringify(data, null, 2);
325
+ const payload = JSON.stringify(data);
178
326
  // Atomic write: write to temp file then rename to prevent corruption on crash
179
327
  const tmpPath = storagePath + ".tmp";
180
328
  await writeTextFile(tmpPath, payload);
@@ -191,6 +339,7 @@ function createLocalJsonRagStore(config) {
191
339
  // expected: rename not available in all environments, fall back to direct write
192
340
  await writeTextFile(storagePath, payload);
193
341
  }
342
+ await updateStoreDataCache(data, payload);
194
343
  }
195
344
  async function ensureEmbeddings(data) {
196
345
  const unembedded = data.chunks.filter((c) => c.embedding.length === 0);
@@ -25,6 +25,7 @@ export interface UseUploadsResult {
25
25
  /** Re-fetch the upload list */
26
26
  refresh: () => Promise<void>;
27
27
  }
28
+ export declare function buildUploadResourceUrl(api: string, id: string): string;
28
29
  /**
29
30
  * useUploads hook for managing RAG upload lifecycle.
30
31
  */
@@ -1 +1 @@
1
- {"version":3,"file":"use-uploads.d.ts","sourceRoot":"","sources":["../../../../src/src/embedding/react/use-uploads.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qCAAqC;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,6BAA6B;IAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,+BAA+B;IAC/B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAmEvE"}
1
+ {"version":3,"file":"use-uploads.d.ts","sourceRoot":"","sources":["../../../../src/src/embedding/react/use-uploads.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qCAAqC;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,6BAA6B;IAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,+BAA+B;IAC/B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAmEvE"}
@@ -6,6 +6,9 @@
6
6
  */
7
7
  import { useCallback, useEffect, useRef, useState } from "../../../react/react.js";
8
8
  import { API_ERROR } from "../../errors/index.js";
9
+ export function buildUploadResourceUrl(api, id) {
10
+ return `${api}/${encodeURIComponent(id)}`;
11
+ }
9
12
  /**
10
13
  * useUploads hook for managing RAG upload lifecycle.
11
14
  */
@@ -61,7 +64,7 @@ export function useUploads(options) {
61
64
  const remove = useCallback(async (id) => {
62
65
  setError(null);
63
66
  try {
64
- await fetch(`${options.api}/${id}`, { method: "DELETE" });
67
+ await fetch(buildUploadResourceUrl(options.api, id), { method: "DELETE" });
65
68
  await refresh();
66
69
  }
67
70
  catch (error) {
@@ -1,12 +1,24 @@
1
1
  import type { RagStore } from "./types.js";
2
- interface UploadHandlerConfig {
2
+ export type UploadAuthorizationResult = boolean | Response | void | undefined;
3
+ export type UploadAuthorize = (request: Request) => UploadAuthorizationResult | Promise<UploadAuthorizationResult>;
4
+ export type UploadHandlerAuthConfig = {
5
+ type: "none";
6
+ allowUnauthenticated: true;
7
+ } | {
8
+ authorize: UploadAuthorize;
9
+ };
10
+ export interface UploadHandlerConfig {
3
11
  maxFileSize?: number;
12
+ auth?: UploadHandlerAuthConfig;
4
13
  }
5
14
  /**
6
15
  * Creates HTTP route handlers for upload, listing, and deletion.
7
16
  *
8
- * **Important:** These handlers do not include authentication or authorization.
9
- * Add your own auth middleware before exposing them in production.
17
+ * Pass `auth: { authorize }` to protect these handlers before they read
18
+ * request bodies or access the RAG store. For local development, pass
19
+ * `auth: { type: "none", allowUnauthenticated: true }` to explicitly allow
20
+ * unauthenticated upload routes. Omitting `auth` still allows the route for
21
+ * compatibility and logs a warning.
10
22
  *
11
23
  * Returns `{ POST, GET, DELETE }` handlers compatible with file-based routing.
12
24
  * POST accepts multipart form data with a `file` field, extracts text via
@@ -21,7 +33,15 @@ interface UploadHandlerConfig {
21
33
  * import { createUploadHandler } from "veryfront/embedding";
22
34
  * import { store } from "lib/store.ts";
23
35
  *
24
- * export const { POST, GET } = createUploadHandler(store);
36
+ * export const { POST, GET } = createUploadHandler(store, {
37
+ * auth: {
38
+ * authorize: (request) => {
39
+ * const token = Deno.env.get("UPLOAD_TOKEN");
40
+ * return token !== undefined &&
41
+ * request.headers.get("authorization") === `Bearer ${token}`;
42
+ * },
43
+ * },
44
+ * });
25
45
  * ```
26
46
  *
27
47
  * @example
@@ -30,15 +50,16 @@ interface UploadHandlerConfig {
30
50
  * import { createUploadHandler } from "veryfront/embedding";
31
51
  * import { store } from "lib/store.ts";
32
52
  *
33
- * export const { DELETE } = createUploadHandler(store);
53
+ * export const { DELETE } = createUploadHandler(store, {
54
+ * auth: { type: "none", allowUnauthenticated: true },
55
+ * });
34
56
  * ```
35
57
  */
36
58
  export declare function createUploadHandler(store: RagStore, config?: UploadHandlerConfig): {
37
59
  POST: (request: Request) => Promise<Response>;
38
- GET: () => Promise<Response>;
39
- DELETE: (_request: Request, context: {
60
+ GET: (request?: Request) => Promise<Response>;
61
+ DELETE: (request: Request, context: {
40
62
  params: Record<string, string>;
41
63
  }) => Promise<Response>;
42
64
  };
43
- export {};
44
65
  //# sourceMappingURL=upload-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-handler.d.ts","sourceRoot":"","sources":["../../../src/src/embedding/upload-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAqF3C,UAAU,mBAAmB;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA2CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,EACf,MAAM,CAAC,EAAE,mBAAmB;oBAIC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;eA8ElC,OAAO,CAAC,QAAQ,CAAC;uBAW3B,OAAO,WACR;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAC1C,OAAO,CAAC,QAAQ,CAAC;EA0BrB"}
1
+ {"version":3,"file":"upload-handler.d.ts","sourceRoot":"","sources":["../../../src/src/embedding/upload-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAqF3C,MAAM,MAAM,yBAAyB,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC;AAE9E,MAAM,MAAM,eAAe,GAAG,CAC5B,OAAO,EAAE,OAAO,KACb,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAEpE,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,IAAI,CAAA;CAAE,GAC5C;IAAE,SAAS,EAAE,eAAe,CAAA;CAAE,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,uBAAuB,CAAC;CAChC;AA6FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,EACf,MAAM,CAAC,EAAE,mBAAmB;oBAKC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;oBAiF3B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;sBAc7C,OAAO,WACP;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAC1C,OAAO,CAAC,QAAQ,CAAC;EA6BrB"}
@@ -75,6 +75,43 @@ function sanitizeFileName(raw) {
75
75
  return sanitized || "untitled";
76
76
  }
77
77
  const MAX_CONCURRENT_URL_LOOKUPS = 5;
78
+ let missingAuthWarningEmitted = false;
79
+ function warnMissingAuthConfig() {
80
+ if (missingAuthWarningEmitted)
81
+ return;
82
+ missingAuthWarningEmitted = true;
83
+ serverLogger.warn("createUploadHandler registered without auth. Pass auth: { authorize } for protected routes, " +
84
+ "or auth: { type: 'none', allowUnauthenticated: true } to explicitly allow unauthenticated uploads.");
85
+ }
86
+ function resolveUploadAuthorize(auth) {
87
+ if (auth === undefined) {
88
+ warnMissingAuthConfig();
89
+ return null;
90
+ }
91
+ if ("type" in auth) {
92
+ if (auth.type === "none" && auth.allowUnauthenticated === true)
93
+ return null;
94
+ throw new Error("createUploadHandler auth type 'none' requires allowUnauthenticated: true.");
95
+ }
96
+ if (typeof auth.authorize === "function")
97
+ return auth.authorize;
98
+ throw new Error("createUploadHandler auth must be { authorize } or " +
99
+ "{ type: 'none', allowUnauthenticated: true }.");
100
+ }
101
+ async function authorizeUploadRequest(request, authorize) {
102
+ if (!authorize)
103
+ return null;
104
+ if (!request) {
105
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
106
+ }
107
+ const result = await authorize(request);
108
+ if (result instanceof Response)
109
+ return result;
110
+ if (result === false) {
111
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
112
+ }
113
+ return null;
114
+ }
78
115
  async function enrichUploadsWithSourceUrls(uploads) {
79
116
  const sourceBlobStorage = getSourceBlobStorage();
80
117
  if (!sourceBlobStorage)
@@ -111,8 +148,11 @@ function getSourceBlobStorage() {
111
148
  /**
112
149
  * Creates HTTP route handlers for upload, listing, and deletion.
113
150
  *
114
- * **Important:** These handlers do not include authentication or authorization.
115
- * Add your own auth middleware before exposing them in production.
151
+ * Pass `auth: { authorize }` to protect these handlers before they read
152
+ * request bodies or access the RAG store. For local development, pass
153
+ * `auth: { type: "none", allowUnauthenticated: true }` to explicitly allow
154
+ * unauthenticated upload routes. Omitting `auth` still allows the route for
155
+ * compatibility and logs a warning.
116
156
  *
117
157
  * Returns `{ POST, GET, DELETE }` handlers compatible with file-based routing.
118
158
  * POST accepts multipart form data with a `file` field, extracts text via
@@ -127,7 +167,15 @@ function getSourceBlobStorage() {
127
167
  * import { createUploadHandler } from "veryfront/embedding";
128
168
  * import { store } from "lib/store.ts";
129
169
  *
130
- * export const { POST, GET } = createUploadHandler(store);
170
+ * export const { POST, GET } = createUploadHandler(store, {
171
+ * auth: {
172
+ * authorize: (request) => {
173
+ * const token = Deno.env.get("UPLOAD_TOKEN");
174
+ * return token !== undefined &&
175
+ * request.headers.get("authorization") === `Bearer ${token}`;
176
+ * },
177
+ * },
178
+ * });
131
179
  * ```
132
180
  *
133
181
  * @example
@@ -136,13 +184,19 @@ function getSourceBlobStorage() {
136
184
  * import { createUploadHandler } from "veryfront/embedding";
137
185
  * import { store } from "lib/store.ts";
138
186
  *
139
- * export const { DELETE } = createUploadHandler(store);
187
+ * export const { DELETE } = createUploadHandler(store, {
188
+ * auth: { type: "none", allowUnauthenticated: true },
189
+ * });
140
190
  * ```
141
191
  */
142
192
  export function createUploadHandler(store, config) {
143
193
  const maxSize = config?.maxFileSize ?? MAX_FILE_SIZE;
194
+ const authorize = resolveUploadAuthorize(config?.auth);
144
195
  async function POST(request) {
145
196
  try {
197
+ const unauthorized = await authorizeUploadRequest(request, authorize);
198
+ if (unauthorized)
199
+ return unauthorized;
146
200
  const formData = await request.formData();
147
201
  const file = formData.get("file");
148
202
  if (!file || !(file instanceof FileCtor)) {
@@ -201,8 +255,11 @@ export function createUploadHandler(store, config) {
201
255
  return Response.json({ error: message }, { status: 500 });
202
256
  }
203
257
  }
204
- async function GET() {
258
+ async function GET(request) {
205
259
  try {
260
+ const unauthorized = await authorizeUploadRequest(request, authorize);
261
+ if (unauthorized)
262
+ return unauthorized;
206
263
  const uploads = await enrichUploadsWithSourceUrls(await store.listDocuments());
207
264
  return Response.json({ uploads });
208
265
  }
@@ -211,8 +268,11 @@ export function createUploadHandler(store, config) {
211
268
  return Response.json({ error: "Failed to list uploads" }, { status: 500 });
212
269
  }
213
270
  }
214
- async function DELETE(_request, context) {
271
+ async function DELETE(request, context) {
215
272
  try {
273
+ const unauthorized = await authorizeUploadRequest(request, authorize);
274
+ if (unauthorized)
275
+ return unauthorized;
216
276
  const id = context.params.id;
217
277
  if (!id) {
218
278
  return Response.json({ error: "Missing upload ID" }, { status: 400 });
@@ -10,7 +10,7 @@ import { generateHydrationData, getDevScripts, getProdScripts, PROD_HYDRATION_MO
10
10
  import { getPreviewStylesheetLink, getStudioScripts } from "./dev-scripts.js";
11
11
  import { processMetadata } from "./metadata-builder.js";
12
12
  import { extractCandidates, getDevStyles as getErrorOverlayStyles, getProjectCSS, } from "./styles-builder/index.js";
13
- import { buildImportMapJson, buildRootAttributes, shouldDisableLayout } from "./utils.js";
13
+ import { buildImportMap, buildRootAttributes, shouldDisableLayout } from "./utils.js";
14
14
  function pathToModuleUrl(path, studioEmbed) {
15
15
  if (!path)
16
16
  return "";
@@ -114,7 +114,7 @@ async function generateHTMLShellPartsImpl(meta, options, params, props, contentF
114
114
  // Enable dev scripts for local dev OR preview mode (for HMR support in Studio),
115
115
  // unless a caller explicitly forces production client scripts for fair benchmarking.
116
116
  const useDevScripts = !options.forceProductionScripts && (isLocalProject || isPreviewMode);
117
- const importMapJsonPromise = buildImportMapJson({
117
+ const importMapPromise = buildImportMap({
118
118
  projectDir: options.projectDir,
119
119
  config: options.config,
120
120
  customImports: options.importMap,
@@ -130,11 +130,11 @@ async function generateHTMLShellPartsImpl(meta, options, params, props, contentF
130
130
  : getProdScripts(meta.slug || "", params, props, nonce);
131
131
  const modeStyles = useDevScripts ? getErrorOverlayStyles(nonce) : "";
132
132
  const modulePreloadHints = generateModulePreloadHints(options);
133
- const importMapJson = await profilePhase("html.import_map", () => importMapJsonPromise);
133
+ const importMap = await profilePhase("html.import_map", () => importMapPromise);
134
+ const importMapJson = importMap.json;
134
135
  // Preload critical React dependencies to avoid waterfall delays.
135
136
  // jsx-runtime is discovered late (only when modules execute), adding ~500ms latency.
136
- const importMapData = JSON.parse(importMapJson);
137
- const jsxRuntimeUrl = importMapData.imports?.["react/jsx-runtime"];
137
+ const jsxRuntimeUrl = importMap.imports["react/jsx-runtime"];
138
138
  const criticalDepsPreload = jsxRuntimeUrl
139
139
  ? `<link rel="modulepreload" href="${jsxRuntimeUrl}">`
140
140
  : "";
@@ -1 +1 @@
1
- {"version":3,"file":"nonce-injection.d.ts","sourceRoot":"","sources":["../../../src/src/html/nonce-injection.ts"],"names":[],"mappings":"AA8HA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA6DvE;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,KAAK,CAAC,EAAE,MAAM,GACb,cAAc,CAAC,UAAU,CAAC,CAuI5B"}
1
+ {"version":3,"file":"nonce-injection.d.ts","sourceRoot":"","sources":["../../../src/src/html/nonce-injection.ts"],"names":[],"mappings":"AA8HA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA6DvE;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,KAAK,CAAC,EAAE,MAAM,GACb,cAAc,CAAC,UAAU,CAAC,CA8I5B"}
@@ -160,11 +160,11 @@ export function addNonceToHtmlStream(stream, nonce) {
160
160
  const decoder = new TextDecoder();
161
161
  const encoder = new TextEncoder();
162
162
  let buffer = "";
163
+ let lowerBuffer = "";
163
164
  let rawTextTag = null;
164
165
  function transformBuffer(flush) {
165
166
  let result = "";
166
167
  let index = 0;
167
- const lowerBuffer = buffer.toLowerCase();
168
168
  while (index < buffer.length) {
169
169
  if (rawTextTag) {
170
170
  const closingIndex = findRawTextClosingTagStart(lowerBuffer, rawTextTag, index);
@@ -227,7 +227,10 @@ export function addNonceToHtmlStream(stream, nonce) {
227
227
  rawTextTag = tagName;
228
228
  }
229
229
  }
230
- buffer = buffer.slice(index);
230
+ if (index > 0) {
231
+ buffer = buffer.slice(index);
232
+ lowerBuffer = buffer.toLowerCase();
233
+ }
231
234
  return result;
232
235
  }
233
236
  const reader = stream.getReader();
@@ -244,7 +247,9 @@ export function addNonceToHtmlStream(stream, nonce) {
244
247
  while (true) {
245
248
  const { done, value } = await reader.read();
246
249
  if (done) {
247
- buffer += decoder.decode();
250
+ const decoded = decoder.decode();
251
+ buffer += decoded;
252
+ lowerBuffer += decoded.toLowerCase();
248
253
  const transformed = transformBuffer(true);
249
254
  if (transformed)
250
255
  controller.enqueue(encoder.encode(transformed));
@@ -252,7 +257,9 @@ export function addNonceToHtmlStream(stream, nonce) {
252
257
  releaseReaderLock();
253
258
  return;
254
259
  }
255
- buffer += decoder.decode(value, { stream: true });
260
+ const decoded = decoder.decode(value, { stream: true });
261
+ buffer += decoded;
262
+ lowerBuffer += decoded.toLowerCase();
256
263
  const transformed = transformBuffer(false);
257
264
  if (transformed) {
258
265
  controller.enqueue(encoder.encode(transformed));
@@ -1,11 +1,16 @@
1
1
  import type { VeryfrontConfig } from "../config/index.js";
2
2
  export declare function buildRootAttributes(slug: string, mode: string, noLayout: boolean, ssrHash?: string): string;
3
+ export interface BuiltImportMap {
4
+ imports: Record<string, string>;
5
+ json: string;
6
+ }
3
7
  interface BuildImportMapOptions {
4
8
  projectDir?: string;
5
9
  config?: VeryfrontConfig;
6
10
  customImports?: Record<string, string>;
7
11
  pretty?: boolean;
8
12
  }
13
+ export declare function buildImportMap(options?: BuildImportMapOptions | Record<string, string>): Promise<BuiltImportMap>;
9
14
  export declare function buildImportMapJson(options?: BuildImportMapOptions | Record<string, string>): Promise<string>;
10
15
  export declare function clearImportMapCache(): void;
11
16
  export declare function shouldDisableLayout(frontmatter?: Record<string, unknown>): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/src/html/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAc1D,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAQR;AAsOD,UAAU,qBAAqB;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAWD,wBAAsB,kBAAkB,CACtC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACvD,OAAO,CAAC,MAAM,CAAC,CA+DjB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAElF"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/src/html/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAc1D,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAQR;AAaD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AA4ND,UAAU,qBAAqB;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqBD,wBAAsB,cAAc,CAClC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACvD,OAAO,CAAC,cAAc,CAAC,CA2DzB;AAED,wBAAsB,kBAAkB,CACtC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACvD,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAElF"}
@@ -178,19 +178,25 @@ async function resolveVersions(projectDir, config) {
178
178
  }
179
179
  return { react: await reactVersion, veryfront: veryfrontVersion };
180
180
  }
181
+ function stringifyImportMap(imports, pretty = true) {
182
+ return JSON.stringify({ imports }, null, pretty ? 2 : undefined);
183
+ }
184
+ function stableMapKey(imports) {
185
+ return imports
186
+ ? JSON.stringify(Object.entries(imports).sort(([a], [b]) => a.localeCompare(b)))
187
+ : "";
188
+ }
181
189
  function isImportMapOnlyOptions(options) {
182
190
  return !("projectDir" in options) &&
183
191
  !("config" in options) &&
184
192
  !("customImports" in options) &&
185
193
  !("pretty" in options);
186
194
  }
187
- export async function buildImportMapJson(options) {
188
- const stringifyImportMap = (imports, pretty = true) => JSON.stringify({ imports }, null, pretty ? 2 : undefined);
189
- const stableMapKey = (imports) => imports ? JSON.stringify(Object.entries(imports).sort(([a], [b]) => a.localeCompare(b))) : "";
195
+ export async function buildImportMap(options) {
190
196
  if (options && isImportMapOnlyOptions(options)) {
191
- const imports = options;
197
+ const imports = { ...options };
192
198
  if (Object.keys(imports).length > 0) {
193
- return stringifyImportMap(imports);
199
+ return { imports, json: stringifyImportMap(imports) };
194
200
  }
195
201
  }
196
202
  const { projectDir, config, customImports, pretty = true } = (options ?? {});
@@ -206,7 +212,7 @@ export async function buildImportMapJson(options) {
206
212
  "react/jsx-dev-runtime": reactTemplates.jsxDevRuntime(versions.react),
207
213
  ...customImports,
208
214
  };
209
- return stringifyImportMap(imports, pretty);
215
+ return { imports, json: stringifyImportMap(imports, pretty) };
210
216
  }
211
217
  let imports;
212
218
  if (mode === "self-hosted") {
@@ -230,10 +236,14 @@ export async function buildImportMapJson(options) {
230
236
  });
231
237
  const cached = importMapJsonCache.get(cacheKey);
232
238
  if (cached)
233
- return cached.json;
239
+ return cached;
234
240
  const json = stringifyImportMap(imports, pretty);
235
- importMapJsonCache.set(cacheKey, { cacheKey, json });
236
- return json;
241
+ const built = { cacheKey, imports, json };
242
+ importMapJsonCache.set(cacheKey, built);
243
+ return built;
244
+ }
245
+ export async function buildImportMapJson(options) {
246
+ return (await buildImportMap(options)).json;
237
247
  }
238
248
  export function clearImportMapCache() {
239
249
  importMapJsonCache.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"import-rewriter.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/import-rewriter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,MAAM,CAyBR"}
1
+ {"version":3,"file":"import-rewriter.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/import-rewriter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,MAAM,CA8BR"}
@@ -34,16 +34,23 @@ export function rewriteLocalImports(transformed, localImportPaths, fromFilePath,
34
34
  const fromRelativeDir = fromFileDir.startsWith(normalizedProjectDir)
35
35
  ? fromFileDir.substring(normalizedProjectDir.length + 1)
36
36
  : fromFileDir;
37
- let result = transformed;
37
+ const replacementByPattern = new Map();
38
38
  for (const [specifierOrPath, tempPath] of localImportPaths) {
39
39
  const patterns = buildImportPatterns(specifierOrPath, fromRelativeDir, normalizedProjectDir);
40
40
  for (const pattern of patterns) {
41
- const escapedPattern = escapeRegExp(pattern);
42
- const regex = new RegExp(`${IMPORT_FROM_PREFIX}(${escapedPattern})${IMPORT_FROM_SUFFIX}`, "g");
43
- result = result.replace(regex, `from "file://${tempPath}"`);
41
+ if (!replacementByPattern.has(pattern)) {
42
+ replacementByPattern.set(pattern, tempPath);
43
+ }
44
44
  }
45
45
  }
46
- return result;
46
+ if (replacementByPattern.size === 0)
47
+ return transformed;
48
+ const importPattern = Array.from(replacementByPattern.keys()).map(escapeRegExp).join("|");
49
+ const regex = new RegExp(`${IMPORT_FROM_PREFIX}(${importPattern})${IMPORT_FROM_SUFFIX}`, "g");
50
+ return transformed.replace(regex, (match, specifier) => {
51
+ const tempPath = replacementByPattern.get(specifier);
52
+ return tempPath === undefined ? match : `from "file://${tempPath}"`;
53
+ });
47
54
  }
48
55
  /**
49
56
  * Build import patterns for a given specifier to match in transformed code.