@harborclient/team-hub 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -404,6 +404,7 @@ var ENVIRONMENTS_COLLECTION = "environments";
404
404
  var SNIPPETS_COLLECTION = "snippets";
405
405
  var FOLDERS_COLLECTION = "folders";
406
406
  var REQUESTS_COLLECTION = "requests";
407
+ var DOCUMENTS_COLLECTION = "documents";
407
408
  var AUDIT_LOG_COLLECTION = "auditLog";
408
409
  var LLM_USAGE_COLLECTION = "llmUsage";
409
410
  var LLM_USAGE_LOG_COLLECTION = "llmUsageLog";
@@ -442,9 +443,25 @@ var firestoreConfigSchema = z2.object({
442
443
  keyFilename: z2.string().trim().min(1).optional()
443
444
  });
444
445
 
446
+ // src/db/sidebarMarker.ts
447
+ function serializeSidebarMarker(marker) {
448
+ if (marker == null) {
449
+ return null;
450
+ }
451
+ const trimmed = marker.trim();
452
+ return trimmed.length > 0 ? trimmed : null;
453
+ }
454
+ function readSidebarMarker(value) {
455
+ if (typeof value !== "string") {
456
+ return null;
457
+ }
458
+ const trimmed = value.trim();
459
+ return trimmed.length > 0 ? trimmed : null;
460
+ }
461
+
445
462
  // src/db/firestore/utils.ts
446
463
  function parseAuditEntityType(value) {
447
- if (value === "user" || value === "api_token" || value === "invitation" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request" || value === "run_result") {
464
+ if (value === "user" || value === "api_token" || value === "invitation" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request" || value === "document" || value === "run_result") {
448
465
  return value;
449
466
  }
450
467
  throw new Error(`Invalid audit entity type: ${value}`);
@@ -538,7 +555,8 @@ function mapFirestoreCollection(id, data) {
538
555
  updatedAt: data.updatedAt ?? data.createdAt,
539
556
  createdByUserId: data.createdByUserId ?? null,
540
557
  updatedByUserId: data.updatedByUserId ?? null,
541
- deletionLocked: data.deletionLocked ?? false
558
+ deletionLocked: data.deletionLocked ?? false,
559
+ marker: readSidebarMarker(data.marker)
542
560
  };
543
561
  }
544
562
  function mapFirestoreEnvironment(id, data) {
@@ -550,7 +568,8 @@ function mapFirestoreEnvironment(id, data) {
550
568
  updatedAt: data.updatedAt ?? data.createdAt,
551
569
  createdByUserId: data.createdByUserId ?? null,
552
570
  updatedByUserId: data.updatedByUserId ?? null,
553
- deletionLocked: data.deletionLocked ?? false
571
+ deletionLocked: data.deletionLocked ?? false,
572
+ marker: readSidebarMarker(data.marker)
554
573
  };
555
574
  }
556
575
  function mapFirestoreSnippet(id, data) {
@@ -571,12 +590,14 @@ function mapFirestoreFolder(id, data) {
571
590
  return {
572
591
  id,
573
592
  collectionId: data.collectionId,
593
+ parentFolderId: data.parentFolderId ?? null,
574
594
  name: data.name,
575
595
  sortOrder: data.sortOrder,
576
596
  createdAt: data.createdAt,
577
597
  updatedAt: data.updatedAt ?? data.createdAt,
578
598
  createdByUserId: data.createdByUserId ?? null,
579
- updatedByUserId: data.updatedByUserId ?? null
599
+ updatedByUserId: data.updatedByUserId ?? null,
600
+ marker: readSidebarMarker(data.marker)
580
601
  };
581
602
  }
582
603
  function mapFirestoreRequest(id, data) {
@@ -599,7 +620,23 @@ function mapFirestoreRequest(id, data) {
599
620
  createdAt: data.createdAt,
600
621
  updatedAt: data.updatedAt,
601
622
  createdByUserId: data.createdByUserId ?? null,
602
- updatedByUserId: data.updatedByUserId ?? null
623
+ updatedByUserId: data.updatedByUserId ?? null,
624
+ marker: readSidebarMarker(data.marker)
625
+ };
626
+ }
627
+ function mapFirestoreDocument(id, data) {
628
+ return {
629
+ id,
630
+ collectionId: data.collectionId,
631
+ folderId: data.folderId,
632
+ name: data.name,
633
+ content: data.content,
634
+ sortOrder: data.sortOrder,
635
+ createdAt: data.createdAt,
636
+ updatedAt: data.updatedAt,
637
+ createdByUserId: data.createdByUserId ?? null,
638
+ updatedByUserId: data.updatedByUserId ?? null,
639
+ marker: readSidebarMarker(data.marker)
603
640
  };
604
641
  }
605
642
  function mapFirestoreRunResult(id, data) {
@@ -1450,7 +1487,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1450
1487
  *
1451
1488
  * @param actingUserId - User performing the update action.
1452
1489
  */
1453
- async updateCollection(id, name, variables, headers, preRequestScript, postRequestScript, auth, actingUserId) {
1490
+ async updateCollection(id, name, variables, headers, preRequestScript, postRequestScript, auth, actingUserId, marker) {
1454
1491
  const trimmedName = trimRequiredName(name, "Collection name");
1455
1492
  const updatedAt = /* @__PURE__ */ new Date();
1456
1493
  const docRef = this.requireClient().collection(COLLECTIONS_COLLECTION).doc(id);
@@ -1459,6 +1496,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1459
1496
  throw new Error("Collection not found");
1460
1497
  }
1461
1498
  const existing = snapshot.data();
1499
+ const serializedMarker = marker !== void 0 ? serializeSidebarMarker(marker) : existing.marker;
1462
1500
  const updated = {
1463
1501
  ...existing,
1464
1502
  name: trimmedName,
@@ -1468,7 +1506,8 @@ var FirestoreDatabase = class _FirestoreDatabase {
1468
1506
  preRequestScript,
1469
1507
  postRequestScript,
1470
1508
  updatedAt,
1471
- updatedByUserId: actingUserId
1509
+ updatedByUserId: actingUserId,
1510
+ ...marker !== void 0 ? { marker: serializedMarker } : {}
1472
1511
  };
1473
1512
  await docRef.update({
1474
1513
  name: trimmedName,
@@ -1478,7 +1517,8 @@ var FirestoreDatabase = class _FirestoreDatabase {
1478
1517
  preRequestScript,
1479
1518
  postRequestScript,
1480
1519
  updatedAt,
1481
- updatedByUserId: actingUserId
1520
+ updatedByUserId: actingUserId,
1521
+ ...marker !== void 0 ? { marker: serializedMarker } : {}
1482
1522
  });
1483
1523
  await this.recordAuditEntry(actingUserId, "update", "collection", id);
1484
1524
  return mapFirestoreCollection(id, updated);
@@ -1493,9 +1533,11 @@ var FirestoreDatabase = class _FirestoreDatabase {
1493
1533
  await this.recordAuditEntry(actingUserId, "delete", "collection", id);
1494
1534
  const client = this.requireClient();
1495
1535
  const requestsSnap = await client.collection(REQUESTS_COLLECTION).where("collectionId", "==", id).get();
1536
+ const documentsSnap = await client.collection(DOCUMENTS_COLLECTION).where("collectionId", "==", id).get();
1496
1537
  const foldersSnap = await client.collection(FOLDERS_COLLECTION).where("collectionId", "==", id).get();
1497
1538
  const refs = [
1498
1539
  ...requestsSnap.docs.map((requestDoc) => requestDoc.ref),
1540
+ ...documentsSnap.docs.map((documentDoc) => documentDoc.ref),
1499
1541
  ...foldersSnap.docs.map((folderDoc) => folderDoc.ref),
1500
1542
  client.collection(COLLECTIONS_COLLECTION).doc(id)
1501
1543
  ];
@@ -1578,7 +1620,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1578
1620
  *
1579
1621
  * @param actingUserId - User performing the update action.
1580
1622
  */
1581
- async updateEnvironment(id, name, variables, actingUserId) {
1623
+ async updateEnvironment(id, name, variables, actingUserId, marker) {
1582
1624
  const trimmedName = trimRequiredName(name, "Environment name");
1583
1625
  const updatedAt = /* @__PURE__ */ new Date();
1584
1626
  const docRef = this.requireClient().collection(ENVIRONMENTS_COLLECTION).doc(id);
@@ -1587,18 +1629,21 @@ var FirestoreDatabase = class _FirestoreDatabase {
1587
1629
  throw new Error("Environment not found");
1588
1630
  }
1589
1631
  const existing = snapshot.data();
1632
+ const serializedMarker = marker !== void 0 ? serializeSidebarMarker(marker) : existing.marker;
1590
1633
  const updated = {
1591
1634
  ...existing,
1592
1635
  name: trimmedName,
1593
1636
  variables,
1594
1637
  updatedAt,
1595
- updatedByUserId: actingUserId
1638
+ updatedByUserId: actingUserId,
1639
+ ...marker !== void 0 ? { marker: serializedMarker } : {}
1596
1640
  };
1597
1641
  await docRef.update({
1598
1642
  name: trimmedName,
1599
1643
  variables,
1600
1644
  updatedAt,
1601
- updatedByUserId: actingUserId
1645
+ updatedByUserId: actingUserId,
1646
+ ...marker !== void 0 ? { marker: serializedMarker } : {}
1602
1647
  });
1603
1648
  await this.recordAuditEntry(actingUserId, "update", "environment", id);
1604
1649
  return mapFirestoreEnvironment(id, updated);
@@ -1811,6 +1856,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1811
1856
  async saveRequest(input, actingUserId) {
1812
1857
  const trimmedName = trimRequiredName(input.name, "Request name");
1813
1858
  const folderId = input.folderId ?? null;
1859
+ const serializedMarker = serializeSidebarMarker(input.marker ?? null);
1814
1860
  const now = /* @__PURE__ */ new Date();
1815
1861
  const client = this.requireClient();
1816
1862
  if (folderId != null) {
@@ -1843,6 +1889,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1843
1889
  preRequestScript: input.preRequestScript,
1844
1890
  postRequestScript: input.postRequestScript,
1845
1891
  comment: input.comment,
1892
+ marker: serializedMarker,
1846
1893
  updatedAt: now,
1847
1894
  updatedByUserId: actingUserId
1848
1895
  };
@@ -1860,6 +1907,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1860
1907
  preRequestScript: input.preRequestScript,
1861
1908
  postRequestScript: input.postRequestScript,
1862
1909
  comment: input.comment,
1910
+ marker: serializedMarker,
1863
1911
  updatedAt: now,
1864
1912
  updatedByUserId: actingUserId
1865
1913
  });
@@ -1884,6 +1932,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1884
1932
  preRequestScript: input.preRequestScript,
1885
1933
  postRequestScript: input.postRequestScript,
1886
1934
  comment: input.comment,
1935
+ marker: serializedMarker,
1887
1936
  sortOrder: maxOrder + 1,
1888
1937
  createdAt: now,
1889
1938
  updatedAt: now,
@@ -1937,14 +1986,21 @@ var FirestoreDatabase = class _FirestoreDatabase {
1937
1986
  * @param name - Display name for the folder.
1938
1987
  * @param actingUserId - User performing the create action.
1939
1988
  */
1940
- async createFolder(collectionId, name, actingUserId) {
1989
+ async createFolder(collectionId, name, actingUserId, parentFolderId = null) {
1941
1990
  const trimmedName = trimRequiredName(name, "Folder name");
1991
+ if (parentFolderId != null) {
1992
+ const parent = await this.findFolderById(parentFolderId);
1993
+ if (!parent || parent.collectionId !== collectionId) {
1994
+ throw new Error("Parent folder not found in collection");
1995
+ }
1996
+ }
1942
1997
  const id = randomUUID3();
1943
1998
  const now = /* @__PURE__ */ new Date();
1944
1999
  const existingFolders = await this.listFolders(collectionId);
1945
- const maxOrder = existingFolders.reduce((max, folder) => Math.max(max, folder.sortOrder), -1);
2000
+ const maxOrder = existingFolders.filter((folder) => folder.parentFolderId === parentFolderId).reduce((max, folder) => Math.max(max, folder.sortOrder), -1);
1946
2001
  const data = {
1947
2002
  collectionId,
2003
+ parentFolderId,
1948
2004
  name: trimmedName,
1949
2005
  sortOrder: maxOrder + 1,
1950
2006
  createdAt: now,
@@ -1963,7 +2019,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
1963
2019
  * @param name - New display name.
1964
2020
  * @param actingUserId - User performing the rename action.
1965
2021
  */
1966
- async renameFolder(id, name, actingUserId) {
2022
+ async renameFolder(id, name, actingUserId, marker) {
1967
2023
  const trimmedName = trimRequiredName(name, "Folder name");
1968
2024
  const updatedAt = /* @__PURE__ */ new Date();
1969
2025
  const docRef = this.requireClient().collection(FOLDERS_COLLECTION).doc(id);
@@ -1972,17 +2028,24 @@ var FirestoreDatabase = class _FirestoreDatabase {
1972
2028
  throw new Error("Folder not found");
1973
2029
  }
1974
2030
  const existing = snapshot.data();
1975
- await docRef.update({ name: trimmedName, updatedAt, updatedByUserId: actingUserId });
2031
+ const serializedMarker = marker !== void 0 ? serializeSidebarMarker(marker) : existing.marker;
2032
+ await docRef.update({
2033
+ name: trimmedName,
2034
+ updatedAt,
2035
+ updatedByUserId: actingUserId,
2036
+ ...marker !== void 0 ? { marker: serializedMarker } : {}
2037
+ });
1976
2038
  await this.recordAuditEntry(actingUserId, "update", "folder", id);
1977
2039
  return mapFirestoreFolder(id, {
1978
2040
  ...existing,
1979
2041
  name: trimmedName,
1980
2042
  updatedAt,
1981
- updatedByUserId: actingUserId
2043
+ updatedByUserId: actingUserId,
2044
+ ...marker !== void 0 ? { marker: serializedMarker } : {}
1982
2045
  });
1983
2046
  }
1984
2047
  /**
1985
- * Deletes a folder and all requests inside it.
2048
+ * Deletes a folder, its descendants, and their contents.
1986
2049
  *
1987
2050
  * @param id - Folder ID to delete.
1988
2051
  * @param actingUserId - User performing the delete action.
@@ -1990,21 +2053,101 @@ var FirestoreDatabase = class _FirestoreDatabase {
1990
2053
  async deleteFolder(id, actingUserId) {
1991
2054
  await this.recordAuditEntry(actingUserId, "delete", "folder", id);
1992
2055
  const client = this.requireClient();
1993
- const requestsSnap = await client.collection(REQUESTS_COLLECTION).where("folderId", "==", id).get();
2056
+ const root = await this.findFolderById(id);
2057
+ if (!root) {
2058
+ return;
2059
+ }
2060
+ const folders = await this.listFolders(root.collectionId);
2061
+ const descendantIds = /* @__PURE__ */ new Set([id]);
2062
+ let changed = true;
2063
+ while (changed) {
2064
+ changed = false;
2065
+ for (const folder of folders) {
2066
+ if (folder.parentFolderId != null && descendantIds.has(folder.parentFolderId) && !descendantIds.has(folder.id)) {
2067
+ descendantIds.add(folder.id);
2068
+ changed = true;
2069
+ }
2070
+ }
2071
+ }
2072
+ const ids = [...descendantIds];
2073
+ const requestSnapshots = await Promise.all(
2074
+ ids.map(
2075
+ (folderId) => client.collection(REQUESTS_COLLECTION).where("folderId", "==", folderId).get()
2076
+ )
2077
+ );
2078
+ const documentSnapshots = await Promise.all(
2079
+ ids.map(
2080
+ (folderId) => client.collection(DOCUMENTS_COLLECTION).where("folderId", "==", folderId).get()
2081
+ )
2082
+ );
1994
2083
  const refs = [
1995
- ...requestsSnap.docs.map((requestDoc) => requestDoc.ref),
1996
- client.collection(FOLDERS_COLLECTION).doc(id)
2084
+ ...requestSnapshots.flatMap((snapshot) => snapshot.docs.map((requestDoc) => requestDoc.ref)),
2085
+ ...documentSnapshots.flatMap(
2086
+ (snapshot) => snapshot.docs.map((documentDoc) => documentDoc.ref)
2087
+ ),
2088
+ ...ids.map((folderId) => client.collection(FOLDERS_COLLECTION).doc(folderId))
1997
2089
  ];
1998
2090
  await this.commitBatchedDeletes(refs);
1999
2091
  }
2000
2092
  /**
2001
- * Reorders folders within a collection.
2093
+ * Moves a folder to a new parent and optional sibling position.
2094
+ *
2095
+ * @param id - Folder ID to move.
2096
+ * @param parentFolderId - Destination parent, or null for collection root.
2097
+ * @param sortOrder - Optional zero-based destination sibling index.
2098
+ * @param actingUserId - User performing the move action.
2099
+ */
2100
+ async moveFolder(id, parentFolderId, sortOrder, actingUserId) {
2101
+ const folder = await this.findFolderById(id);
2102
+ if (!folder) {
2103
+ throw new Error("Folder not found");
2104
+ }
2105
+ const folders = await this.listFolders(folder.collectionId);
2106
+ if (parentFolderId != null) {
2107
+ let ancestor = folders.find((entry) => entry.id === parentFolderId);
2108
+ if (!ancestor) {
2109
+ throw new Error("Parent folder not found in collection");
2110
+ }
2111
+ while (ancestor) {
2112
+ if (ancestor.id === id) {
2113
+ throw new Error("Cannot move a folder inside itself or its descendants");
2114
+ }
2115
+ ancestor = ancestor.parentFolderId == null ? void 0 : folders.find((entry) => entry.id === ancestor?.parentFolderId);
2116
+ }
2117
+ }
2118
+ const siblings = folders.filter((entry) => entry.id !== id && entry.parentFolderId === parentFolderId).sort(
2119
+ (left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name)
2120
+ );
2121
+ const index = Math.max(0, Math.min(sortOrder ?? siblings.length, siblings.length));
2122
+ siblings.splice(index, 0, { ...folder, parentFolderId });
2123
+ const updatedAt = /* @__PURE__ */ new Date();
2124
+ await this.requireClient().collection(FOLDERS_COLLECTION).doc(id).update({
2125
+ parentFolderId,
2126
+ updatedAt,
2127
+ updatedByUserId: actingUserId
2128
+ });
2129
+ await this.reorderFolders(
2130
+ folder.collectionId,
2131
+ parentFolderId,
2132
+ siblings.map((entry) => entry.id),
2133
+ actingUserId
2134
+ );
2135
+ return {
2136
+ ...folder,
2137
+ parentFolderId,
2138
+ sortOrder: index,
2139
+ updatedAt,
2140
+ updatedByUserId: actingUserId
2141
+ };
2142
+ }
2143
+ /**
2144
+ * Reorders sibling folders within a collection.
2002
2145
  *
2003
2146
  * @param collectionId - Collection containing the folders.
2004
2147
  * @param orderedFolderIds - Folder IDs in desired order.
2005
2148
  * @param actingUserId - User performing the reorder action.
2006
2149
  */
2007
- async reorderFolders(collectionId, orderedFolderIds, actingUserId) {
2150
+ async reorderFolders(collectionId, parentFolderId, orderedFolderIds, actingUserId) {
2008
2151
  const client = this.requireClient();
2009
2152
  const updatedAt = /* @__PURE__ */ new Date();
2010
2153
  const batch = client.batch();
@@ -2019,6 +2162,7 @@ var FirestoreDatabase = class _FirestoreDatabase {
2019
2162
  }
2020
2163
  await batch.commit();
2021
2164
  await this.recordAuditEntry(actingUserId, "reorder", "folder", collectionId, {
2165
+ parentFolderId,
2022
2166
  orderedFolderIds
2023
2167
  });
2024
2168
  }
@@ -2117,6 +2261,206 @@ var FirestoreDatabase = class _FirestoreDatabase {
2117
2261
  index
2118
2262
  });
2119
2263
  }
2264
+ /**
2265
+ * Lists all documents in a collection.
2266
+ *
2267
+ * @param collectionId - Collection to query.
2268
+ */
2269
+ async listDocuments(collectionId) {
2270
+ const snapshot = await this.requireClient().collection(DOCUMENTS_COLLECTION).where("collectionId", "==", collectionId).get();
2271
+ return snapshot.docs.map((doc) => mapFirestoreDocument(doc.id, doc.data())).sort((left, right) => {
2272
+ if (left.sortOrder !== right.sortOrder) {
2273
+ return left.sortOrder - right.sortOrder;
2274
+ }
2275
+ return left.name.localeCompare(right.name);
2276
+ });
2277
+ }
2278
+ /**
2279
+ * Finds a document by id.
2280
+ *
2281
+ * @param id - Document identifier to look up.
2282
+ */
2283
+ async findDocumentById(id) {
2284
+ const snapshot = await this.requireClient().collection(DOCUMENTS_COLLECTION).doc(id).get();
2285
+ if (!snapshot.exists) {
2286
+ return null;
2287
+ }
2288
+ return mapFirestoreDocument(id, snapshot.data());
2289
+ }
2290
+ /**
2291
+ * Inserts a new document or updates an existing one.
2292
+ *
2293
+ * @param input - Document fields to persist.
2294
+ * @param actingUserId - User performing the save action.
2295
+ */
2296
+ async saveDocument(input, actingUserId) {
2297
+ const trimmedName = trimRequiredName(input.name, "Document name");
2298
+ const folderId = input.folderId ?? null;
2299
+ const serializedMarker = serializeSidebarMarker(input.marker ?? null);
2300
+ const now = /* @__PURE__ */ new Date();
2301
+ const client = this.requireClient();
2302
+ if (folderId != null) {
2303
+ const folderSnap = await client.collection(FOLDERS_COLLECTION).doc(folderId).get();
2304
+ if (!folderSnap.exists) {
2305
+ throw new Error("Folder not found");
2306
+ }
2307
+ const folder = folderSnap.data();
2308
+ if (folder.collectionId !== input.collectionId) {
2309
+ throw new Error("Folder not found");
2310
+ }
2311
+ }
2312
+ if (input.id) {
2313
+ const docRef = client.collection(DOCUMENTS_COLLECTION).doc(input.id);
2314
+ const snapshot = await docRef.get();
2315
+ if (snapshot.exists) {
2316
+ const existing = snapshot.data();
2317
+ const updated = {
2318
+ ...existing,
2319
+ collectionId: input.collectionId,
2320
+ folderId,
2321
+ name: trimmedName,
2322
+ content: input.content,
2323
+ marker: serializedMarker,
2324
+ updatedAt: now,
2325
+ updatedByUserId: actingUserId
2326
+ };
2327
+ await docRef.update({
2328
+ collectionId: input.collectionId,
2329
+ folderId,
2330
+ name: trimmedName,
2331
+ content: input.content,
2332
+ marker: serializedMarker,
2333
+ updatedAt: now,
2334
+ updatedByUserId: actingUserId
2335
+ });
2336
+ await this.recordAuditEntry(actingUserId, "update", "document", input.id);
2337
+ return mapFirestoreDocument(input.id, updated);
2338
+ }
2339
+ }
2340
+ const existingDocuments = await this.listDocuments(input.collectionId);
2341
+ const maxOrder = existingDocuments.filter((document) => document.folderId === folderId).reduce((max, document) => Math.max(max, document.sortOrder), -1);
2342
+ const id = randomUUID3();
2343
+ const data = {
2344
+ collectionId: input.collectionId,
2345
+ folderId,
2346
+ name: trimmedName,
2347
+ content: input.content,
2348
+ marker: serializedMarker,
2349
+ sortOrder: maxOrder + 1,
2350
+ createdAt: now,
2351
+ updatedAt: now,
2352
+ createdByUserId: actingUserId,
2353
+ updatedByUserId: actingUserId
2354
+ };
2355
+ await client.collection(DOCUMENTS_COLLECTION).doc(id).set(data);
2356
+ await this.recordAuditEntry(actingUserId, "create", "document", id);
2357
+ return mapFirestoreDocument(id, data);
2358
+ }
2359
+ /**
2360
+ * Deletes a document by ID.
2361
+ *
2362
+ * @param id - Document ID to delete.
2363
+ * @param actingUserId - User performing the delete action.
2364
+ */
2365
+ async deleteDocument(id, actingUserId) {
2366
+ await this.recordAuditEntry(actingUserId, "delete", "document", id);
2367
+ await this.requireClient().collection(DOCUMENTS_COLLECTION).doc(id).delete();
2368
+ }
2369
+ /**
2370
+ * Reorders documents within a folder or at collection root.
2371
+ *
2372
+ * @param actingUserId - User performing the reorder action.
2373
+ */
2374
+ async reorderDocuments(collectionId, folderId, orderedDocumentIds, actingUserId) {
2375
+ const client = this.requireClient();
2376
+ const updatedAt = /* @__PURE__ */ new Date();
2377
+ const batch = client.batch();
2378
+ for (let index = 0; index < orderedDocumentIds.length; index++) {
2379
+ const docRef = client.collection(DOCUMENTS_COLLECTION).doc(orderedDocumentIds[index]);
2380
+ batch.update(docRef, {
2381
+ sortOrder: index,
2382
+ folderId,
2383
+ collectionId,
2384
+ updatedAt,
2385
+ updatedByUserId: actingUserId
2386
+ });
2387
+ }
2388
+ await batch.commit();
2389
+ await this.recordAuditEntry(actingUserId, "reorder", "document", collectionId, {
2390
+ folderId,
2391
+ orderedDocumentIds
2392
+ });
2393
+ }
2394
+ /**
2395
+ * Moves a document to another folder or collection root at a given index.
2396
+ *
2397
+ * @param actingUserId - User performing the move action.
2398
+ */
2399
+ async moveDocument(documentId, folderId, index, actingUserId) {
2400
+ const client = this.requireClient();
2401
+ const updatedAt = /* @__PURE__ */ new Date();
2402
+ const documentSnap = await client.collection(DOCUMENTS_COLLECTION).doc(documentId).get();
2403
+ if (!documentSnap.exists) {
2404
+ throw new Error("Document not found");
2405
+ }
2406
+ const document = mapFirestoreDocument(
2407
+ documentSnap.id,
2408
+ documentSnap.data()
2409
+ );
2410
+ const collectionId = document.collectionId;
2411
+ const oldFolderId = document.folderId;
2412
+ if (folderId != null) {
2413
+ const folderSnap = await client.collection(FOLDERS_COLLECTION).doc(folderId).get();
2414
+ if (!folderSnap.exists) {
2415
+ throw new Error("Folder not found");
2416
+ }
2417
+ const folder = folderSnap.data();
2418
+ if (folder.collectionId !== collectionId) {
2419
+ throw new Error("Folder not found");
2420
+ }
2421
+ }
2422
+ const listInContainer = async (targetFolderId) => {
2423
+ const documents = await this.listDocuments(collectionId);
2424
+ return documents.filter((item) => item.folderId === targetFolderId).sort((left, right) => {
2425
+ if (left.sortOrder !== right.sortOrder) {
2426
+ return left.sortOrder - right.sortOrder;
2427
+ }
2428
+ return left.name.localeCompare(right.name);
2429
+ }).map((item) => item.id);
2430
+ };
2431
+ const reindexContainer = async (targetFolderId, orderedIds) => {
2432
+ const batch = client.batch();
2433
+ for (let sortIndex = 0; sortIndex < orderedIds.length; sortIndex++) {
2434
+ const docRef = client.collection(DOCUMENTS_COLLECTION).doc(orderedIds[sortIndex]);
2435
+ batch.update(docRef, {
2436
+ sortOrder: sortIndex,
2437
+ folderId: targetFolderId,
2438
+ updatedAt,
2439
+ updatedByUserId: actingUserId
2440
+ });
2441
+ }
2442
+ await batch.commit();
2443
+ };
2444
+ if (oldFolderId === folderId) {
2445
+ const siblings = (await listInContainer(folderId)).filter((id) => id !== documentId);
2446
+ siblings.splice(index, 0, documentId);
2447
+ await reindexContainer(folderId, siblings);
2448
+ await this.recordAuditEntry(actingUserId, "move", "document", documentId, {
2449
+ folderId,
2450
+ index
2451
+ });
2452
+ return;
2453
+ }
2454
+ const oldIds = (await listInContainer(oldFolderId)).filter((id) => id !== documentId);
2455
+ await reindexContainer(oldFolderId, oldIds);
2456
+ const newIds = (await listInContainer(folderId)).filter((id) => id !== documentId);
2457
+ newIds.splice(index, 0, documentId);
2458
+ await reindexContainer(folderId, newIds);
2459
+ await this.recordAuditEntry(actingUserId, "move", "document", documentId, {
2460
+ folderId,
2461
+ index
2462
+ });
2463
+ }
2120
2464
  /**
2121
2465
  * Returns monthly LLM usage for a user, or null when no usage has been recorded.
2122
2466
  *
@@ -2441,7 +2785,7 @@ function parseAuditAction(value) {
2441
2785
  throw new Error(`Invalid audit action: ${value}`);
2442
2786
  }
2443
2787
  function parseAuditEntityType2(value) {
2444
- if (value === "user" || value === "api_token" || value === "invitation" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request" || value === "run_result") {
2788
+ if (value === "user" || value === "api_token" || value === "invitation" || value === "collection" || value === "environment" || value === "snippet" || value === "folder" || value === "request" || value === "document" || value === "run_result") {
2445
2789
  return value;
2446
2790
  }
2447
2791
  throw new Error(`Invalid audit entity type: ${value}`);
@@ -2503,7 +2847,8 @@ function mapCollectionSqlRow(row) {
2503
2847
  updatedAt: row.updated_at ?? row.created_at,
2504
2848
  createdByUserId: row.created_by_user_id ?? null,
2505
2849
  updatedByUserId: row.updated_by_user_id ?? null,
2506
- deletionLocked: Boolean(row.deletion_locked)
2850
+ deletionLocked: Boolean(row.deletion_locked),
2851
+ marker: readSidebarMarker(row.marker)
2507
2852
  };
2508
2853
  }
2509
2854
  function mapEnvironmentSqlRow(row) {
@@ -2515,7 +2860,8 @@ function mapEnvironmentSqlRow(row) {
2515
2860
  updatedAt: row.updated_at ?? row.created_at,
2516
2861
  createdByUserId: row.created_by_user_id ?? null,
2517
2862
  updatedByUserId: row.updated_by_user_id ?? null,
2518
- deletionLocked: Boolean(row.deletion_locked)
2863
+ deletionLocked: Boolean(row.deletion_locked),
2864
+ marker: readSidebarMarker(row.marker)
2519
2865
  };
2520
2866
  }
2521
2867
  function parseSnippetScope(value) {
@@ -2559,12 +2905,14 @@ function mapFolderSqlRow(row) {
2559
2905
  return {
2560
2906
  id: row.id,
2561
2907
  collectionId: row.collection_id,
2908
+ parentFolderId: row.parent_folder_id,
2562
2909
  name: row.name,
2563
2910
  sortOrder: row.sort_order,
2564
2911
  createdAt: row.created_at,
2565
2912
  updatedAt: row.updated_at ?? row.created_at,
2566
2913
  createdByUserId: row.created_by_user_id ?? null,
2567
- updatedByUserId: row.updated_by_user_id ?? null
2914
+ updatedByUserId: row.updated_by_user_id ?? null,
2915
+ marker: readSidebarMarker(row.marker)
2568
2916
  };
2569
2917
  }
2570
2918
  function mapRequestSqlRow(row) {
@@ -2587,7 +2935,23 @@ function mapRequestSqlRow(row) {
2587
2935
  createdAt: row.created_at,
2588
2936
  updatedAt: row.updated_at,
2589
2937
  createdByUserId: row.created_by_user_id ?? null,
2590
- updatedByUserId: row.updated_by_user_id ?? null
2938
+ updatedByUserId: row.updated_by_user_id ?? null,
2939
+ marker: readSidebarMarker(row.marker)
2940
+ };
2941
+ }
2942
+ function mapDocumentSqlRow(row) {
2943
+ return {
2944
+ id: row.id,
2945
+ collectionId: row.collection_id,
2946
+ folderId: row.folder_id,
2947
+ name: row.name,
2948
+ content: row.content,
2949
+ sortOrder: row.sort_order,
2950
+ createdAt: row.created_at,
2951
+ updatedAt: row.updated_at,
2952
+ createdByUserId: row.created_by_user_id ?? null,
2953
+ updatedByUserId: row.updated_by_user_id ?? null,
2954
+ marker: readSidebarMarker(row.marker)
2591
2955
  };
2592
2956
  }
2593
2957
 
@@ -2659,6 +3023,7 @@ var FOLDERS_MIGRATION_SQL = `
2659
3023
  CREATE TABLE IF NOT EXISTS folders (
2660
3024
  id VARCHAR(36) PRIMARY KEY,
2661
3025
  collection_id VARCHAR(36) NOT NULL,
3026
+ parent_folder_id VARCHAR(36) NULL,
2662
3027
  name VARCHAR(255) NOT NULL,
2663
3028
  sort_order INT NOT NULL DEFAULT 0,
2664
3029
  created_at DATETIME NOT NULL,
@@ -2666,6 +3031,7 @@ CREATE TABLE IF NOT EXISTS folders (
2666
3031
  created_by_user_id VARCHAR(36) NULL,
2667
3032
  updated_by_user_id VARCHAR(36) NULL,
2668
3033
  FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
3034
+ FOREIGN KEY (parent_folder_id) REFERENCES folders(id) ON DELETE CASCADE,
2669
3035
  FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
2670
3036
  FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
2671
3037
  )
@@ -2697,6 +3063,24 @@ CREATE TABLE IF NOT EXISTS requests (
2697
3063
  FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
2698
3064
  )
2699
3065
  `.trim();
3066
+ var DOCUMENTS_MIGRATION_SQL = `
3067
+ CREATE TABLE IF NOT EXISTS documents (
3068
+ id VARCHAR(36) PRIMARY KEY,
3069
+ collection_id VARCHAR(36) NOT NULL,
3070
+ folder_id VARCHAR(36) NULL,
3071
+ name VARCHAR(255) NOT NULL,
3072
+ content LONGTEXT NOT NULL,
3073
+ sort_order INT NOT NULL DEFAULT 0,
3074
+ created_at DATETIME NOT NULL,
3075
+ updated_at DATETIME NOT NULL,
3076
+ created_by_user_id VARCHAR(36) NULL,
3077
+ updated_by_user_id VARCHAR(36) NULL,
3078
+ FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
3079
+ FOREIGN KEY (folder_id) REFERENCES folders(id) ON DELETE CASCADE,
3080
+ FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
3081
+ FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
3082
+ )
3083
+ `.trim();
2700
3084
  var USERS_MIGRATION_SQL = `
2701
3085
  CREATE TABLE IF NOT EXISTS users (
2702
3086
  id VARCHAR(36) PRIMARY KEY,
@@ -2752,6 +3136,10 @@ ALTER TABLE folders
2752
3136
  ADD COLUMN IF NOT EXISTS created_by_user_id VARCHAR(36) NULL,
2753
3137
  ADD COLUMN IF NOT EXISTS updated_by_user_id VARCHAR(36) NULL
2754
3138
  `.trim();
3139
+ var FOLDERS_PARENT_MIGRATION_SQL = `
3140
+ ALTER TABLE folders
3141
+ ADD COLUMN IF NOT EXISTS parent_folder_id VARCHAR(36) NULL
3142
+ `.trim();
2755
3143
  var REQUESTS_ATTRIBUTION_MIGRATION_SQL = `
2756
3144
  ALTER TABLE requests
2757
3145
  ADD COLUMN IF NOT EXISTS created_by_user_id VARCHAR(36) NULL,
@@ -2866,6 +3254,17 @@ CREATE TABLE IF NOT EXISTS user_invitations (
2866
3254
  CONSTRAINT user_invitations_updated_by_fk FOREIGN KEY (updated_by_user_id) REFERENCES users(id) ON DELETE SET NULL
2867
3255
  )
2868
3256
  `.trim();
3257
+ var MARKER_TABLES = ["collections", "folders", "requests", "documents", "environments"];
3258
+ function buildMarkerRenameSql(table) {
3259
+ return `ALTER TABLE ${table} RENAME COLUMN IF EXISTS color TO marker`;
3260
+ }
3261
+ function buildMarkerAddSql(table) {
3262
+ return `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS marker TEXT`;
3263
+ }
3264
+ var MARKER_MIGRATIONS_SQL = [
3265
+ ...MARKER_TABLES.map(buildMarkerRenameSql),
3266
+ ...MARKER_TABLES.map(buildMarkerAddSql)
3267
+ ];
2869
3268
  var MYSQL_MIGRATIONS = [
2870
3269
  USERS_MIGRATION_SQL,
2871
3270
  API_TOKENS_MIGRATION_SQL,
@@ -2874,12 +3273,14 @@ var MYSQL_MIGRATIONS = [
2874
3273
  SNIPPETS_MIGRATION_SQL,
2875
3274
  FOLDERS_MIGRATION_SQL,
2876
3275
  REQUESTS_MIGRATION_SQL,
3276
+ DOCUMENTS_MIGRATION_SQL,
2877
3277
  AUDIT_LOG_MIGRATION_SQL,
2878
3278
  API_TOKENS_USER_ID_MIGRATION_SQL,
2879
3279
  API_TOKENS_ATTRIBUTION_MIGRATION_SQL,
2880
3280
  COLLECTIONS_ATTRIBUTION_MIGRATION_SQL,
2881
3281
  ENVIRONMENTS_ATTRIBUTION_MIGRATION_SQL,
2882
3282
  FOLDERS_ATTRIBUTION_MIGRATION_SQL,
3283
+ FOLDERS_PARENT_MIGRATION_SQL,
2883
3284
  REQUESTS_ATTRIBUTION_MIGRATION_SQL,
2884
3285
  USERS_ATTRIBUTION_MIGRATION_SQL,
2885
3286
  COLLECTIONS_BACKFILL_UPDATED_AT_SQL,
@@ -2893,7 +3294,8 @@ var MYSQL_MIGRATIONS = [
2893
3294
  USERS_SNIPPET_ACCESS_MIGRATION_SQL,
2894
3295
  USERS_SNIPPET_ACCESS_BACKFILL_SQL,
2895
3296
  RUN_RESULTS_MIGRATION_SQL,
2896
- USER_INVITATIONS_MIGRATION_SQL
3297
+ USER_INVITATIONS_MIGRATION_SQL,
3298
+ ...MARKER_MIGRATIONS_SQL
2897
3299
  ];
2898
3300
 
2899
3301
  // src/db/mysql/schemas.ts
@@ -2951,11 +3353,12 @@ function serializeAccessList(access) {
2951
3353
  return JSON.stringify(access);
2952
3354
  }
2953
3355
  var USER_SELECT_COLUMNS = `id, name, role, collection_access, environment_access, snippet_access, llm_access, llm_models, llm_monthly_token_limit, created_at, updated_at, created_by_user_id, updated_by_user_id`;
2954
- var COLLECTION_SELECT_COLUMNS = `id, name, variables, headers, auth, pre_request_script, post_request_script, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked`;
2955
- var ENVIRONMENT_SELECT_COLUMNS = `id, name, variables, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked`;
3356
+ var COLLECTION_SELECT_COLUMNS = `id, name, variables, headers, auth, pre_request_script, post_request_script, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked, marker`;
3357
+ var ENVIRONMENT_SELECT_COLUMNS = `id, name, variables, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked, marker`;
2956
3358
  var SNIPPET_SELECT_COLUMNS = `id, name, code, scope, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id, deletion_locked`;
2957
- var FOLDER_SELECT_COLUMNS = `id, collection_id, name, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id`;
2958
- var REQUEST_SELECT_COLUMNS = `id, collection_id, folder_id, name, method, url, headers, params, auth, body, body_type, pre_request_script, post_request_script, comment, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id`;
3359
+ var FOLDER_SELECT_COLUMNS = `id, collection_id, parent_folder_id, name, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id, marker`;
3360
+ var REQUEST_SELECT_COLUMNS = `id, collection_id, folder_id, name, method, url, headers, params, auth, body, body_type, pre_request_script, post_request_script, comment, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id, marker`;
3361
+ var DOCUMENT_SELECT_COLUMNS = `id, collection_id, folder_id, name, content, sort_order, created_at, updated_at, created_by_user_id, updated_by_user_id, marker`;
2959
3362
  var API_TOKEN_SELECT_COLUMNS = `id, user_id, name, token_hash, token_prefix, created_at, last_used_at, revoked_at, created_by_user_id, updated_by_user_id`;
2960
3363
  var AUDIT_LOG_SELECT_COLUMNS = `id, user_id, user_name, action, entity_type, entity_id, created_at, metadata`;
2961
3364
 
@@ -3003,6 +3406,7 @@ var USER_SELECT = `SELECT ${USER_SELECT_COLUMNS} FROM users`;
3003
3406
  var API_TOKEN_SELECT = `SELECT ${API_TOKEN_SELECT_COLUMNS} FROM api_tokens`;
3004
3407
  var FOLDER_SELECT = `SELECT ${FOLDER_SELECT_COLUMNS} FROM folders`;
3005
3408
  var REQUEST_SELECT = `SELECT ${REQUEST_SELECT_COLUMNS} FROM requests`;
3409
+ var DOCUMENT_SELECT = `SELECT ${DOCUMENT_SELECT_COLUMNS} FROM documents`;
3006
3410
  var AUDIT_LOG_SELECT = `SELECT ${AUDIT_LOG_SELECT_COLUMNS} FROM audit_log`;
3007
3411
  var LLM_USAGE_SELECT = `SELECT ${LLM_USAGE_SELECT_COLUMNS} FROM llm_usage`;
3008
3412
  var LLM_USAGE_LOG_SELECT = `SELECT ${LLM_USAGE_LOG_SELECT_COLUMNS} FROM llm_usage_log`;
@@ -3794,10 +4198,10 @@ var MysqlDatabase = class _MysqlDatabase {
3794
4198
  *
3795
4199
  * @param actingUserId - User performing the update action.
3796
4200
  */
3797
- async updateCollection(id, name, variables, headers, preRequestScript, postRequestScript, auth, actingUserId) {
4201
+ async updateCollection(id, name, variables, headers, preRequestScript, postRequestScript, auth, actingUserId, marker) {
3798
4202
  const trimmedName = trimRequiredName(name, "Collection name");
3799
4203
  const updatedAt = /* @__PURE__ */ new Date();
3800
- const result = await this.executeStatement(
4204
+ const result = marker !== void 0 ? await this.executeStatement(
3801
4205
  `UPDATE collections
3802
4206
  SET name = ?,
3803
4207
  variables = ?,
@@ -3806,7 +4210,8 @@ var MysqlDatabase = class _MysqlDatabase {
3806
4210
  pre_request_script = ?,
3807
4211
  post_request_script = ?,
3808
4212
  updated_at = ?,
3809
- updated_by_user_id = ?
4213
+ updated_by_user_id = ?,
4214
+ marker = ?
3810
4215
  WHERE id = ?`,
3811
4216
  [
3812
4217
  trimmedName,
@@ -3817,9 +4222,32 @@ var MysqlDatabase = class _MysqlDatabase {
3817
4222
  postRequestScript,
3818
4223
  updatedAt,
3819
4224
  actingUserId,
4225
+ serializeSidebarMarker(marker),
3820
4226
  id
3821
4227
  ]
3822
- );
4228
+ ) : await this.executeStatement(
4229
+ `UPDATE collections
4230
+ SET name = ?,
4231
+ variables = ?,
4232
+ headers = ?,
4233
+ auth = ?,
4234
+ pre_request_script = ?,
4235
+ post_request_script = ?,
4236
+ updated_at = ?,
4237
+ updated_by_user_id = ?
4238
+ WHERE id = ?`,
4239
+ [
4240
+ trimmedName,
4241
+ JSON.stringify(variables),
4242
+ JSON.stringify(headers),
4243
+ JSON.stringify(auth),
4244
+ preRequestScript,
4245
+ postRequestScript,
4246
+ updatedAt,
4247
+ actingUserId,
4248
+ id
4249
+ ]
4250
+ );
3823
4251
  if ((result.affectedRows ?? 0) === 0) {
3824
4252
  throw new Error("Collection not found");
3825
4253
  }
@@ -3935,10 +4363,26 @@ var MysqlDatabase = class _MysqlDatabase {
3935
4363
  *
3936
4364
  * @param actingUserId - User performing the update action.
3937
4365
  */
3938
- async updateEnvironment(id, name, variables, actingUserId) {
4366
+ async updateEnvironment(id, name, variables, actingUserId, marker) {
3939
4367
  const trimmedName = trimRequiredName(name, "Environment name");
3940
4368
  const updatedAt = /* @__PURE__ */ new Date();
3941
- const result = await this.executeStatement(
4369
+ const result = marker !== void 0 ? await this.executeStatement(
4370
+ `UPDATE environments
4371
+ SET name = ?,
4372
+ variables = ?,
4373
+ updated_at = ?,
4374
+ updated_by_user_id = ?,
4375
+ marker = ?
4376
+ WHERE id = ?`,
4377
+ [
4378
+ trimmedName,
4379
+ JSON.stringify(variables),
4380
+ updatedAt,
4381
+ actingUserId,
4382
+ serializeSidebarMarker(marker),
4383
+ id
4384
+ ]
4385
+ ) : await this.executeStatement(
3942
4386
  `UPDATE environments
3943
4387
  SET name = ?,
3944
4388
  variables = ?,
@@ -4189,6 +4633,7 @@ var MysqlDatabase = class _MysqlDatabase {
4189
4633
  const params = JSON.stringify(input.params);
4190
4634
  const auth = JSON.stringify(input.auth);
4191
4635
  const folderId = input.folderId ?? null;
4636
+ const serializedMarker = serializeSidebarMarker(input.marker ?? null);
4192
4637
  const now = /* @__PURE__ */ new Date();
4193
4638
  if (folderId != null) {
4194
4639
  const folderRows = await this.queryRows(
@@ -4216,6 +4661,7 @@ var MysqlDatabase = class _MysqlDatabase {
4216
4661
  pre_request_script = ?,
4217
4662
  post_request_script = ?,
4218
4663
  comment = ?,
4664
+ marker = ?,
4219
4665
  updated_at = ?,
4220
4666
  updated_by_user_id = ?
4221
4667
  WHERE id = ?`,
@@ -4233,6 +4679,7 @@ var MysqlDatabase = class _MysqlDatabase {
4233
4679
  input.preRequestScript,
4234
4680
  input.postRequestScript,
4235
4681
  input.comment,
4682
+ serializedMarker,
4236
4683
  now,
4237
4684
  actingUserId,
4238
4685
  input.id
@@ -4274,12 +4721,13 @@ var MysqlDatabase = class _MysqlDatabase {
4274
4721
  pre_request_script,
4275
4722
  post_request_script,
4276
4723
  comment,
4724
+ marker,
4277
4725
  sort_order,
4278
4726
  created_at,
4279
4727
  updated_at,
4280
4728
  created_by_user_id,
4281
4729
  updated_by_user_id
4282
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4730
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4283
4731
  [
4284
4732
  id,
4285
4733
  input.collectionId,
@@ -4295,6 +4743,7 @@ var MysqlDatabase = class _MysqlDatabase {
4295
4743
  input.preRequestScript,
4296
4744
  input.postRequestScript,
4297
4745
  input.comment,
4746
+ serializedMarker,
4298
4747
  maxOrder + 1,
4299
4748
  now,
4300
4749
  now,
@@ -4355,27 +4804,46 @@ var MysqlDatabase = class _MysqlDatabase {
4355
4804
  * @param name - Display name for the folder.
4356
4805
  * @param actingUserId - User performing the create action.
4357
4806
  */
4358
- async createFolder(collectionId, name, actingUserId) {
4807
+ async createFolder(collectionId, name, actingUserId, parentFolderId = null) {
4359
4808
  const trimmedName = trimRequiredName(name, "Folder name");
4809
+ if (parentFolderId != null) {
4810
+ const parent = await this.findFolderById(parentFolderId);
4811
+ if (!parent || parent.collectionId !== collectionId) {
4812
+ throw new Error("Parent folder not found in collection");
4813
+ }
4814
+ }
4360
4815
  const id = randomUUID4();
4361
4816
  const now = /* @__PURE__ */ new Date();
4362
4817
  const maxRows = await this.queryRows(
4363
- "SELECT COALESCE(MAX(sort_order), -1) AS max_order FROM folders WHERE collection_id = ?",
4364
- [collectionId]
4818
+ `SELECT COALESCE(MAX(sort_order), -1) AS max_order
4819
+ FROM folders
4820
+ WHERE collection_id = ? AND parent_folder_id <=> ?`,
4821
+ [collectionId, parentFolderId]
4365
4822
  );
4366
4823
  const maxOrder = maxRows[0]?.max_order ?? -1;
4367
4824
  await this.executeStatement(
4368
4825
  `INSERT INTO folders (
4369
4826
  id,
4370
4827
  collection_id,
4828
+ parent_folder_id,
4371
4829
  name,
4372
4830
  sort_order,
4373
4831
  created_at,
4374
4832
  updated_at,
4375
4833
  created_by_user_id,
4376
4834
  updated_by_user_id
4377
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
4378
- [id, collectionId, trimmedName, maxOrder + 1, now, now, actingUserId, actingUserId]
4835
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4836
+ [
4837
+ id,
4838
+ collectionId,
4839
+ parentFolderId,
4840
+ trimmedName,
4841
+ maxOrder + 1,
4842
+ now,
4843
+ now,
4844
+ actingUserId,
4845
+ actingUserId
4846
+ ]
4379
4847
  );
4380
4848
  await this.recordAuditEntry(actingUserId, "create", "folder", id);
4381
4849
  const rows = await this.queryRows(
@@ -4395,10 +4863,18 @@ var MysqlDatabase = class _MysqlDatabase {
4395
4863
  * @param name - New display name.
4396
4864
  * @param actingUserId - User performing the rename action.
4397
4865
  */
4398
- async renameFolder(id, name, actingUserId) {
4866
+ async renameFolder(id, name, actingUserId, marker) {
4399
4867
  const trimmedName = trimRequiredName(name, "Folder name");
4400
4868
  const updatedAt = /* @__PURE__ */ new Date();
4401
- const result = await this.executeStatement(
4869
+ const result = marker !== void 0 ? await this.executeStatement(
4870
+ `UPDATE folders
4871
+ SET name = ?,
4872
+ updated_at = ?,
4873
+ updated_by_user_id = ?,
4874
+ marker = ?
4875
+ WHERE id = ?`,
4876
+ [trimmedName, updatedAt, actingUserId, serializeSidebarMarker(marker), id]
4877
+ ) : await this.executeStatement(
4402
4878
  `UPDATE folders
4403
4879
  SET name = ?,
4404
4880
  updated_at = ?,
@@ -4421,18 +4897,39 @@ var MysqlDatabase = class _MysqlDatabase {
4421
4897
  return mapFolderSqlRow(row);
4422
4898
  }
4423
4899
  /**
4424
- * Deletes a folder and all requests inside it.
4900
+ * Deletes a folder, its descendants, and their contents.
4425
4901
  *
4426
4902
  * @param id - Folder ID to delete.
4427
4903
  * @param actingUserId - User performing the delete action.
4428
4904
  */
4429
4905
  async deleteFolder(id, actingUserId) {
4430
4906
  await this.recordAuditEntry(actingUserId, "delete", "folder", id);
4907
+ const root = await this.findFolderById(id);
4908
+ if (!root) {
4909
+ return;
4910
+ }
4911
+ const folders = await this.listFolders(root.collectionId);
4912
+ const descendantIds = /* @__PURE__ */ new Set([id]);
4913
+ let changed = true;
4914
+ while (changed) {
4915
+ changed = false;
4916
+ for (const folder of folders) {
4917
+ if (folder.parentFolderId != null && descendantIds.has(folder.parentFolderId) && !descendantIds.has(folder.id)) {
4918
+ descendantIds.add(folder.id);
4919
+ changed = true;
4920
+ }
4921
+ }
4922
+ }
4923
+ const ids = [...descendantIds];
4924
+ const placeholders = ids.map(() => "?").join(", ");
4431
4925
  const connection = await this.requirePool().getConnection();
4432
4926
  try {
4433
4927
  await connection.beginTransaction();
4434
- await connection.execute("DELETE FROM requests WHERE folder_id = ?", [id]);
4435
- await connection.execute("DELETE FROM folders WHERE id = ?", [id]);
4928
+ await connection.execute(`DELETE FROM documents WHERE folder_id IN (${placeholders})`, ids);
4929
+ await connection.execute(`DELETE FROM requests WHERE folder_id IN (${placeholders})`, ids);
4930
+ for (const folderId of ids.reverse()) {
4931
+ await connection.execute("DELETE FROM folders WHERE id = ?", [folderId]);
4932
+ }
4436
4933
  await connection.commit();
4437
4934
  } catch (err) {
4438
4935
  await connection.rollback();
@@ -4442,13 +4939,62 @@ var MysqlDatabase = class _MysqlDatabase {
4442
4939
  }
4443
4940
  }
4444
4941
  /**
4445
- * Reorders folders within a collection.
4942
+ * Moves a folder to a new parent and optional sibling position.
4943
+ *
4944
+ * @param id - Folder ID to move.
4945
+ * @param parentFolderId - Destination parent, or null for collection root.
4946
+ * @param sortOrder - Optional zero-based destination sibling index.
4947
+ * @param actingUserId - User performing the move action.
4948
+ */
4949
+ async moveFolder(id, parentFolderId, sortOrder, actingUserId) {
4950
+ const folder = await this.findFolderById(id);
4951
+ if (!folder) {
4952
+ throw new Error("Folder not found");
4953
+ }
4954
+ const folders = await this.listFolders(folder.collectionId);
4955
+ if (parentFolderId != null) {
4956
+ let ancestor = folders.find((entry) => entry.id === parentFolderId);
4957
+ if (!ancestor) {
4958
+ throw new Error("Parent folder not found in collection");
4959
+ }
4960
+ while (ancestor) {
4961
+ if (ancestor.id === id) {
4962
+ throw new Error("Cannot move a folder inside itself or its descendants");
4963
+ }
4964
+ ancestor = ancestor.parentFolderId == null ? void 0 : folders.find((entry) => entry.id === ancestor?.parentFolderId);
4965
+ }
4966
+ }
4967
+ const siblings = folders.filter((entry) => entry.id !== id && entry.parentFolderId === parentFolderId).sort(
4968
+ (left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name)
4969
+ );
4970
+ const index = Math.max(0, Math.min(sortOrder ?? siblings.length, siblings.length));
4971
+ siblings.splice(index, 0, { ...folder, parentFolderId });
4972
+ await this.executeStatement(
4973
+ `UPDATE folders
4974
+ SET parent_folder_id = ?, updated_at = ?, updated_by_user_id = ?
4975
+ WHERE id = ?`,
4976
+ [parentFolderId, /* @__PURE__ */ new Date(), actingUserId, id]
4977
+ );
4978
+ await this.reorderFolders(
4979
+ folder.collectionId,
4980
+ parentFolderId,
4981
+ siblings.map((entry) => entry.id),
4982
+ actingUserId
4983
+ );
4984
+ const moved = await this.findFolderById(id);
4985
+ if (!moved) {
4986
+ throw new Error("Folder not found after move");
4987
+ }
4988
+ return moved;
4989
+ }
4990
+ /**
4991
+ * Reorders sibling folders within a collection.
4446
4992
  *
4447
4993
  * @param collectionId - Collection containing the folders.
4448
4994
  * @param orderedFolderIds - Folder IDs in desired order.
4449
4995
  * @param actingUserId - User performing the reorder action.
4450
4996
  */
4451
- async reorderFolders(collectionId, orderedFolderIds, actingUserId) {
4997
+ async reorderFolders(collectionId, parentFolderId, orderedFolderIds, actingUserId) {
4452
4998
  const connection = await this.requirePool().getConnection();
4453
4999
  const updatedAt = /* @__PURE__ */ new Date();
4454
5000
  try {
@@ -4459,8 +5005,8 @@ var MysqlDatabase = class _MysqlDatabase {
4459
5005
  SET sort_order = ?,
4460
5006
  updated_at = ?,
4461
5007
  updated_by_user_id = ?
4462
- WHERE id = ? AND collection_id = ?`,
4463
- [index, updatedAt, actingUserId, orderedFolderIds[index], collectionId]
5008
+ WHERE id = ? AND collection_id = ? AND parent_folder_id <=> ?`,
5009
+ [index, updatedAt, actingUserId, orderedFolderIds[index], collectionId, parentFolderId]
4464
5010
  );
4465
5011
  }
4466
5012
  await connection.commit();
@@ -4471,6 +5017,7 @@ var MysqlDatabase = class _MysqlDatabase {
4471
5017
  connection.release();
4472
5018
  }
4473
5019
  await this.recordAuditEntry(actingUserId, "reorder", "collection", collectionId, {
5020
+ parentFolderId,
4474
5021
  orderedFolderIds
4475
5022
  });
4476
5023
  }
@@ -4587,122 +5134,374 @@ var MysqlDatabase = class _MysqlDatabase {
4587
5134
  });
4588
5135
  }
4589
5136
  /**
4590
- * Returns monthly LLM usage for a user, or null when no usage has been recorded.
5137
+ * Lists all documents in a collection.
4591
5138
  *
4592
- * @param userId - Owning user identifier.
4593
- * @param period - UTC calendar month key (`YYYY-MM`).
5139
+ * @param collectionId - Collection to query.
4594
5140
  */
4595
- async getLlmUsage(userId, period) {
4596
- const [rows] = await this.requirePool().execute(
4597
- `${LLM_USAGE_SELECT} WHERE user_id = ? AND period = ? LIMIT 1`,
4598
- [userId, period]
5141
+ async listDocuments(collectionId) {
5142
+ const rows = await this.queryRows(
5143
+ `${DOCUMENT_SELECT} WHERE collection_id = ? ORDER BY sort_order ASC, name ASC`,
5144
+ [collectionId]
4599
5145
  );
4600
- const row = rows[0];
4601
- return row ? mapLlmUsageSqlRow(row) : null;
5146
+ return rows.map(mapDocumentSqlRow);
4602
5147
  }
4603
5148
  /**
4604
- * Atomically increments monthly LLM token usage for a user.
5149
+ * Finds a document by id.
4605
5150
  *
4606
- * @param userId - Owning user identifier.
4607
- * @param period - UTC calendar month key (`YYYY-MM`).
4608
- * @param promptTokens - Prompt tokens to add.
4609
- * @param completionTokens - Completion tokens to add.
5151
+ * @param id - Document identifier to look up.
4610
5152
  */
4611
- async addLlmUsage(userId, period, promptTokens, completionTokens) {
4612
- const totalDelta = promptTokens + completionTokens;
4613
- const now = /* @__PURE__ */ new Date();
4614
- const id = randomUUID4();
4615
- await this.executeStatement(
4616
- `INSERT INTO llm_usage (
4617
- id,
4618
- user_id,
4619
- period,
4620
- prompt_tokens,
4621
- completion_tokens,
4622
- total_tokens,
4623
- updated_at
4624
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
4625
- ON DUPLICATE KEY UPDATE
4626
- prompt_tokens = prompt_tokens + VALUES(prompt_tokens),
4627
- completion_tokens = completion_tokens + VALUES(completion_tokens),
4628
- total_tokens = total_tokens + VALUES(total_tokens),
4629
- updated_at = VALUES(updated_at)`,
4630
- [id, userId, period, promptTokens, completionTokens, totalDelta, now]
5153
+ async findDocumentById(id) {
5154
+ const rows = await this.queryRows(
5155
+ `${DOCUMENT_SELECT} WHERE id = ? LIMIT 1`,
5156
+ [id]
4631
5157
  );
4632
- const usage = await this.getLlmUsage(userId, period);
4633
- if (!usage) {
4634
- throw new Error("LLM usage not found after upsert");
4635
- }
4636
- return usage;
5158
+ const row = rows[0];
5159
+ return row ? mapDocumentSqlRow(row) : null;
4637
5160
  }
4638
5161
  /**
4639
- * Inserts a per-request LLM usage log entry.
5162
+ * Inserts a new document or updates an existing one.
4640
5163
  *
4641
- * @param input - Usage details for one successful completion step.
5164
+ * @param input - Document fields to persist.
5165
+ * @param actingUserId - User performing the save action.
4642
5166
  */
4643
- async createLlmUsageLog(input) {
4644
- const id = randomUUID4();
5167
+ async saveDocument(input, actingUserId) {
5168
+ const trimmedName = trimRequiredName(input.name, "Document name");
5169
+ const folderId = input.folderId ?? null;
5170
+ const serializedMarker = serializeSidebarMarker(input.marker ?? null);
4645
5171
  const now = /* @__PURE__ */ new Date();
5172
+ if (folderId != null) {
5173
+ const folderRows = await this.queryRows(
5174
+ "SELECT collection_id FROM folders WHERE id = ?",
5175
+ [folderId]
5176
+ );
5177
+ const folderRow = folderRows[0];
5178
+ if (!folderRow || folderRow.collection_id !== input.collectionId) {
5179
+ throw new Error("Folder not found");
5180
+ }
5181
+ }
5182
+ if (input.id) {
5183
+ const result = await this.executeStatement(
5184
+ `UPDATE documents SET
5185
+ collection_id = ?,
5186
+ folder_id = ?,
5187
+ name = ?,
5188
+ content = ?,
5189
+ marker = ?,
5190
+ updated_at = ?,
5191
+ updated_by_user_id = ?
5192
+ WHERE id = ?`,
5193
+ [
5194
+ input.collectionId,
5195
+ folderId,
5196
+ trimmedName,
5197
+ input.content,
5198
+ serializedMarker,
5199
+ now,
5200
+ actingUserId,
5201
+ input.id
5202
+ ]
5203
+ );
5204
+ if ((result.affectedRows ?? 0) > 0) {
5205
+ await this.recordAuditEntry(actingUserId, "update", "document", input.id);
5206
+ const rows2 = await this.queryRows(
5207
+ `${DOCUMENT_SELECT} WHERE id = ?`,
5208
+ [input.id]
5209
+ );
5210
+ const row2 = rows2[0];
5211
+ if (row2) {
5212
+ return mapDocumentSqlRow(row2);
5213
+ }
5214
+ }
5215
+ }
5216
+ const maxRows = await this.queryRows(
5217
+ `SELECT COALESCE(MAX(sort_order), -1) AS max_order FROM documents
5218
+ WHERE collection_id = ?
5219
+ AND ((? IS NULL AND folder_id IS NULL) OR folder_id = ?)`,
5220
+ [input.collectionId, folderId, folderId]
5221
+ );
5222
+ const maxOrder = maxRows[0]?.max_order ?? -1;
5223
+ const id = randomUUID4();
4646
5224
  await this.executeStatement(
4647
- `INSERT INTO llm_usage_log (
5225
+ `INSERT INTO documents (
4648
5226
  id,
4649
- user_id,
4650
- api_token_id,
4651
- period,
4652
- model,
4653
- provider,
4654
- prompt_tokens,
4655
- completion_tokens,
4656
- total_tokens,
4657
- is_new_turn,
4658
- had_tool_calls,
4659
- message_count,
4660
- created_at
4661
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5227
+ collection_id,
5228
+ folder_id,
5229
+ name,
5230
+ content,
5231
+ marker,
5232
+ sort_order,
5233
+ created_at,
5234
+ updated_at,
5235
+ created_by_user_id,
5236
+ updated_by_user_id
5237
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4662
5238
  [
4663
5239
  id,
4664
- input.userId,
4665
- input.apiTokenId,
4666
- input.period,
4667
- input.model,
4668
- input.provider,
4669
- input.promptTokens,
4670
- input.completionTokens,
4671
- input.totalTokens,
4672
- input.isNewTurn ? 1 : 0,
4673
- input.hadToolCalls ? 1 : 0,
4674
- input.messageCount,
4675
- now
5240
+ input.collectionId,
5241
+ folderId,
5242
+ trimmedName,
5243
+ input.content,
5244
+ serializedMarker,
5245
+ maxOrder + 1,
5246
+ now,
5247
+ now,
5248
+ actingUserId,
5249
+ actingUserId
4676
5250
  ]
4677
5251
  );
5252
+ await this.recordAuditEntry(actingUserId, "create", "document", id);
4678
5253
  const rows = await this.queryRows(
4679
- `${LLM_USAGE_LOG_SELECT} WHERE id = ?`,
5254
+ `${DOCUMENT_SELECT} WHERE id = ?`,
4680
5255
  [id]
4681
5256
  );
4682
5257
  const row = rows[0];
4683
5258
  if (!row) {
4684
- throw new Error("LLM usage log not found after insert");
5259
+ throw new Error("Document not found after insert");
4685
5260
  }
4686
- return mapLlmUsageLogSqlRow(row);
5261
+ return mapDocumentSqlRow(row);
4687
5262
  }
4688
5263
  /**
4689
- * Lists all per-request LLM usage log entries, newest first.
5264
+ * Deletes a document by ID.
5265
+ *
5266
+ * @param id - Document ID to delete.
5267
+ * @param actingUserId - User performing the delete action.
4690
5268
  */
4691
- async listLlmUsageLogs() {
4692
- const rows = await this.queryRows(
4693
- `${LLM_USAGE_LOG_SELECT} ORDER BY created_at DESC`
4694
- );
4695
- return rows.map(mapLlmUsageLogSqlRow);
5269
+ async deleteDocument(id, actingUserId) {
5270
+ await this.recordAuditEntry(actingUserId, "delete", "document", id);
5271
+ await this.executeStatement("DELETE FROM documents WHERE id = ?", [id]);
4696
5272
  }
4697
5273
  /**
4698
- * Lists run results saved by the given user, newest first.
5274
+ * Reorders documents within a folder or at collection root.
5275
+ *
5276
+ * @param actingUserId - User performing the reorder action.
4699
5277
  */
4700
- async listRunResultsForUser(userId) {
4701
- const rows = await this.queryRows(
4702
- `${RUN_RESULT_SELECT} WHERE created_by_user_id = ? ORDER BY created_at DESC`,
4703
- [userId]
4704
- );
4705
- return rows.map(mapRunResultSqlRow);
5278
+ async reorderDocuments(collectionId, folderId, orderedDocumentIds, actingUserId) {
5279
+ const connection = await this.requirePool().getConnection();
5280
+ const updatedAt = /* @__PURE__ */ new Date();
5281
+ try {
5282
+ await connection.beginTransaction();
5283
+ for (let index = 0; index < orderedDocumentIds.length; index++) {
5284
+ await connection.execute(
5285
+ `UPDATE documents
5286
+ SET sort_order = ?,
5287
+ folder_id = ?,
5288
+ updated_at = ?,
5289
+ updated_by_user_id = ?
5290
+ WHERE id = ? AND collection_id = ?`,
5291
+ [index, folderId, updatedAt, actingUserId, orderedDocumentIds[index], collectionId]
5292
+ );
5293
+ }
5294
+ await connection.commit();
5295
+ } catch (err) {
5296
+ await connection.rollback();
5297
+ throw err;
5298
+ } finally {
5299
+ connection.release();
5300
+ }
5301
+ await this.recordAuditEntry(actingUserId, "reorder", "document", collectionId, {
5302
+ folderId,
5303
+ orderedDocumentIds
5304
+ });
5305
+ }
5306
+ /**
5307
+ * Moves a document to another folder or collection root at a given index.
5308
+ *
5309
+ * @param actingUserId - User performing the move action.
5310
+ */
5311
+ async moveDocument(documentId, folderId, index, actingUserId) {
5312
+ const connection = await this.requirePool().getConnection();
5313
+ const updatedAt = /* @__PURE__ */ new Date();
5314
+ const listInContainer = async (collectionId, targetFolderId) => {
5315
+ const rows = await this.queryRows(
5316
+ `SELECT id FROM documents WHERE collection_id = ?
5317
+ AND ((? IS NULL AND folder_id IS NULL) OR folder_id = ?)
5318
+ ORDER BY sort_order ASC, name ASC`,
5319
+ [collectionId, targetFolderId, targetFolderId]
5320
+ );
5321
+ return rows.map((row) => row.id);
5322
+ };
5323
+ const reindexContainer = async (targetFolderId, orderedIds) => {
5324
+ for (let sortIndex = 0; sortIndex < orderedIds.length; sortIndex++) {
5325
+ await connection.execute(
5326
+ `UPDATE documents
5327
+ SET sort_order = ?,
5328
+ folder_id = ?,
5329
+ updated_at = ?,
5330
+ updated_by_user_id = ?
5331
+ WHERE id = ?`,
5332
+ [sortIndex, targetFolderId, updatedAt, actingUserId, orderedIds[sortIndex]]
5333
+ );
5334
+ }
5335
+ };
5336
+ try {
5337
+ await connection.beginTransaction();
5338
+ const documentRows = await this.queryRows(
5339
+ `${DOCUMENT_SELECT} WHERE id = ?`,
5340
+ [documentId]
5341
+ );
5342
+ const documentRow = documentRows[0];
5343
+ if (!documentRow) {
5344
+ throw new Error("Document not found");
5345
+ }
5346
+ const document = mapDocumentSqlRow(documentRow);
5347
+ const collectionId = document.collectionId;
5348
+ const oldFolderId = document.folderId;
5349
+ if (folderId != null) {
5350
+ const folderRows = await this.queryRows(
5351
+ "SELECT collection_id FROM folders WHERE id = ?",
5352
+ [folderId]
5353
+ );
5354
+ const folderRow = folderRows[0];
5355
+ if (!folderRow || folderRow.collection_id !== collectionId) {
5356
+ throw new Error("Folder not found");
5357
+ }
5358
+ }
5359
+ if (oldFolderId === folderId) {
5360
+ const siblings = (await listInContainer(collectionId, folderId)).filter(
5361
+ (id) => id !== documentId
5362
+ );
5363
+ siblings.splice(index, 0, documentId);
5364
+ await reindexContainer(folderId, siblings);
5365
+ } else {
5366
+ const oldIds = (await listInContainer(collectionId, oldFolderId)).filter(
5367
+ (id) => id !== documentId
5368
+ );
5369
+ await reindexContainer(oldFolderId, oldIds);
5370
+ const newIds = (await listInContainer(collectionId, folderId)).filter(
5371
+ (id) => id !== documentId
5372
+ );
5373
+ newIds.splice(index, 0, documentId);
5374
+ await reindexContainer(folderId, newIds);
5375
+ }
5376
+ await connection.commit();
5377
+ } catch (err) {
5378
+ await connection.rollback();
5379
+ throw err;
5380
+ } finally {
5381
+ connection.release();
5382
+ }
5383
+ await this.recordAuditEntry(actingUserId, "move", "document", documentId, {
5384
+ folderId,
5385
+ index
5386
+ });
5387
+ }
5388
+ /**
5389
+ * Returns monthly LLM usage for a user, or null when no usage has been recorded.
5390
+ *
5391
+ * @param userId - Owning user identifier.
5392
+ * @param period - UTC calendar month key (`YYYY-MM`).
5393
+ */
5394
+ async getLlmUsage(userId, period) {
5395
+ const [rows] = await this.requirePool().execute(
5396
+ `${LLM_USAGE_SELECT} WHERE user_id = ? AND period = ? LIMIT 1`,
5397
+ [userId, period]
5398
+ );
5399
+ const row = rows[0];
5400
+ return row ? mapLlmUsageSqlRow(row) : null;
5401
+ }
5402
+ /**
5403
+ * Atomically increments monthly LLM token usage for a user.
5404
+ *
5405
+ * @param userId - Owning user identifier.
5406
+ * @param period - UTC calendar month key (`YYYY-MM`).
5407
+ * @param promptTokens - Prompt tokens to add.
5408
+ * @param completionTokens - Completion tokens to add.
5409
+ */
5410
+ async addLlmUsage(userId, period, promptTokens, completionTokens) {
5411
+ const totalDelta = promptTokens + completionTokens;
5412
+ const now = /* @__PURE__ */ new Date();
5413
+ const id = randomUUID4();
5414
+ await this.executeStatement(
5415
+ `INSERT INTO llm_usage (
5416
+ id,
5417
+ user_id,
5418
+ period,
5419
+ prompt_tokens,
5420
+ completion_tokens,
5421
+ total_tokens,
5422
+ updated_at
5423
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
5424
+ ON DUPLICATE KEY UPDATE
5425
+ prompt_tokens = prompt_tokens + VALUES(prompt_tokens),
5426
+ completion_tokens = completion_tokens + VALUES(completion_tokens),
5427
+ total_tokens = total_tokens + VALUES(total_tokens),
5428
+ updated_at = VALUES(updated_at)`,
5429
+ [id, userId, period, promptTokens, completionTokens, totalDelta, now]
5430
+ );
5431
+ const usage = await this.getLlmUsage(userId, period);
5432
+ if (!usage) {
5433
+ throw new Error("LLM usage not found after upsert");
5434
+ }
5435
+ return usage;
5436
+ }
5437
+ /**
5438
+ * Inserts a per-request LLM usage log entry.
5439
+ *
5440
+ * @param input - Usage details for one successful completion step.
5441
+ */
5442
+ async createLlmUsageLog(input) {
5443
+ const id = randomUUID4();
5444
+ const now = /* @__PURE__ */ new Date();
5445
+ await this.executeStatement(
5446
+ `INSERT INTO llm_usage_log (
5447
+ id,
5448
+ user_id,
5449
+ api_token_id,
5450
+ period,
5451
+ model,
5452
+ provider,
5453
+ prompt_tokens,
5454
+ completion_tokens,
5455
+ total_tokens,
5456
+ is_new_turn,
5457
+ had_tool_calls,
5458
+ message_count,
5459
+ created_at
5460
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5461
+ [
5462
+ id,
5463
+ input.userId,
5464
+ input.apiTokenId,
5465
+ input.period,
5466
+ input.model,
5467
+ input.provider,
5468
+ input.promptTokens,
5469
+ input.completionTokens,
5470
+ input.totalTokens,
5471
+ input.isNewTurn ? 1 : 0,
5472
+ input.hadToolCalls ? 1 : 0,
5473
+ input.messageCount,
5474
+ now
5475
+ ]
5476
+ );
5477
+ const rows = await this.queryRows(
5478
+ `${LLM_USAGE_LOG_SELECT} WHERE id = ?`,
5479
+ [id]
5480
+ );
5481
+ const row = rows[0];
5482
+ if (!row) {
5483
+ throw new Error("LLM usage log not found after insert");
5484
+ }
5485
+ return mapLlmUsageLogSqlRow(row);
5486
+ }
5487
+ /**
5488
+ * Lists all per-request LLM usage log entries, newest first.
5489
+ */
5490
+ async listLlmUsageLogs() {
5491
+ const rows = await this.queryRows(
5492
+ `${LLM_USAGE_LOG_SELECT} ORDER BY created_at DESC`
5493
+ );
5494
+ return rows.map(mapLlmUsageLogSqlRow);
5495
+ }
5496
+ /**
5497
+ * Lists run results saved by the given user, newest first.
5498
+ */
5499
+ async listRunResultsForUser(userId) {
5500
+ const rows = await this.queryRows(
5501
+ `${RUN_RESULT_SELECT} WHERE created_by_user_id = ? ORDER BY created_at DESC`,
5502
+ [userId]
5503
+ );
5504
+ return rows.map(mapRunResultSqlRow);
4706
5505
  }
4707
5506
  /**
4708
5507
  * Lists all run results for admin inspection, newest first.
@@ -4978,6 +5777,7 @@ var FOLDERS_MIGRATION_SQL2 = `
4978
5777
  CREATE TABLE IF NOT EXISTS folders (
4979
5778
  id TEXT PRIMARY KEY,
4980
5779
  collection_id TEXT NOT NULL,
5780
+ parent_folder_id TEXT REFERENCES folders(id) ON DELETE CASCADE,
4981
5781
  name TEXT NOT NULL,
4982
5782
  sort_order INT NOT NULL DEFAULT 0,
4983
5783
  created_at TIMESTAMPTZ NOT NULL,
@@ -5012,6 +5812,22 @@ CREATE TABLE IF NOT EXISTS requests (
5012
5812
  FOREIGN KEY (folder_id) REFERENCES folders(id) ON DELETE CASCADE
5013
5813
  );
5014
5814
  `.trim();
5815
+ var DOCUMENTS_MIGRATION_SQL2 = `
5816
+ CREATE TABLE IF NOT EXISTS documents (
5817
+ id TEXT PRIMARY KEY,
5818
+ collection_id TEXT NOT NULL,
5819
+ folder_id TEXT,
5820
+ name TEXT NOT NULL,
5821
+ content TEXT NOT NULL DEFAULT '',
5822
+ sort_order INT NOT NULL DEFAULT 0,
5823
+ created_at TIMESTAMPTZ NOT NULL,
5824
+ updated_at TIMESTAMPTZ NOT NULL,
5825
+ created_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
5826
+ updated_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
5827
+ FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
5828
+ FOREIGN KEY (folder_id) REFERENCES folders(id) ON DELETE CASCADE
5829
+ );
5830
+ `.trim();
5015
5831
  var USERS_MIGRATION_SQL2 = `
5016
5832
  CREATE TABLE IF NOT EXISTS users (
5017
5833
  id TEXT PRIMARY KEY,
@@ -5065,6 +5881,10 @@ ALTER TABLE folders
5065
5881
  ADD COLUMN IF NOT EXISTS created_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
5066
5882
  ADD COLUMN IF NOT EXISTS updated_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL;
5067
5883
  `.trim();
5884
+ var FOLDERS_PARENT_MIGRATION_SQL2 = `
5885
+ ALTER TABLE folders
5886
+ ADD COLUMN IF NOT EXISTS parent_folder_id TEXT REFERENCES folders(id) ON DELETE CASCADE;
5887
+ `.trim();
5068
5888
  var REQUESTS_ATTRIBUTION_MIGRATION_SQL2 = `
5069
5889
  ALTER TABLE requests
5070
5890
  ADD COLUMN IF NOT EXISTS created_by_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
@@ -5174,6 +5994,34 @@ CREATE TABLE IF NOT EXISTS user_invitations (
5174
5994
  CREATE INDEX IF NOT EXISTS user_invitations_user_id_idx ON user_invitations (user_id);
5175
5995
  CREATE INDEX IF NOT EXISTS user_invitations_expires_at_idx ON user_invitations (expires_at);
5176
5996
  `.trim();
5997
+ function buildMarkerMigrationSql(table) {
5998
+ return `
5999
+ DO $$
6000
+ BEGIN
6001
+ IF EXISTS (
6002
+ SELECT 1 FROM information_schema.columns
6003
+ WHERE table_schema = current_schema() AND table_name = '${table}' AND column_name = 'color'
6004
+ ) AND NOT EXISTS (
6005
+ SELECT 1 FROM information_schema.columns
6006
+ WHERE table_schema = current_schema() AND table_name = '${table}' AND column_name = 'marker'
6007
+ ) THEN
6008
+ ALTER TABLE ${table} RENAME COLUMN color TO marker;
6009
+ END IF;
6010
+
6011
+ IF NOT EXISTS (
6012
+ SELECT 1 FROM information_schema.columns
6013
+ WHERE table_schema = current_schema() AND table_name = '${table}' AND column_name = 'marker'
6014
+ ) THEN
6015
+ ALTER TABLE ${table} ADD COLUMN marker TEXT;
6016
+ END IF;
6017
+ END $$;
6018
+ `.trim();
6019
+ }
6020
+ var COLLECTIONS_MARKER_MIGRATION_SQL = buildMarkerMigrationSql("collections");
6021
+ var FOLDERS_MARKER_MIGRATION_SQL = buildMarkerMigrationSql("folders");
6022
+ var REQUESTS_MARKER_MIGRATION_SQL = buildMarkerMigrationSql("requests");
6023
+ var DOCUMENTS_MARKER_MIGRATION_SQL = buildMarkerMigrationSql("documents");
6024
+ var ENVIRONMENTS_MARKER_MIGRATION_SQL = buildMarkerMigrationSql("environments");
5177
6025
  var POSTGRES_MIGRATIONS = [
5178
6026
  USERS_MIGRATION_SQL2,
5179
6027
  API_TOKENS_MIGRATION_SQL2,
@@ -5182,12 +6030,14 @@ var POSTGRES_MIGRATIONS = [
5182
6030
  SNIPPETS_MIGRATION_SQL2,
5183
6031
  FOLDERS_MIGRATION_SQL2,
5184
6032
  REQUESTS_MIGRATION_SQL2,
6033
+ DOCUMENTS_MIGRATION_SQL2,
5185
6034
  AUDIT_LOG_MIGRATION_SQL2,
5186
6035
  API_TOKENS_USER_ID_MIGRATION_SQL2,
5187
6036
  API_TOKENS_ATTRIBUTION_MIGRATION_SQL2,
5188
6037
  COLLECTIONS_ATTRIBUTION_MIGRATION_SQL2,
5189
6038
  ENVIRONMENTS_ATTRIBUTION_MIGRATION_SQL2,
5190
6039
  FOLDERS_ATTRIBUTION_MIGRATION_SQL2,
6040
+ FOLDERS_PARENT_MIGRATION_SQL2,
5191
6041
  REQUESTS_ATTRIBUTION_MIGRATION_SQL2,
5192
6042
  USERS_ATTRIBUTION_MIGRATION_SQL2,
5193
6043
  COLLECTIONS_BACKFILL_UPDATED_AT_SQL2,
@@ -5201,7 +6051,12 @@ var POSTGRES_MIGRATIONS = [
5201
6051
  USERS_SNIPPET_ACCESS_MIGRATION_SQL2,
5202
6052
  USERS_SNIPPET_ACCESS_BACKFILL_SQL2,
5203
6053
  RUN_RESULTS_MIGRATION_SQL2,
5204
- USER_INVITATIONS_MIGRATION_SQL2
6054
+ USER_INVITATIONS_MIGRATION_SQL2,
6055
+ COLLECTIONS_MARKER_MIGRATION_SQL,
6056
+ FOLDERS_MARKER_MIGRATION_SQL,
6057
+ REQUESTS_MARKER_MIGRATION_SQL,
6058
+ DOCUMENTS_MARKER_MIGRATION_SQL,
6059
+ ENVIRONMENTS_MARKER_MIGRATION_SQL
5205
6060
  ];
5206
6061
 
5207
6062
  // src/db/postgres/schemas.ts
@@ -5232,6 +6087,7 @@ var USER_SELECT2 = `SELECT ${USER_SELECT_COLUMNS} FROM users`;
5232
6087
  var API_TOKEN_SELECT2 = `SELECT ${API_TOKEN_SELECT_COLUMNS} FROM api_tokens`;
5233
6088
  var FOLDER_SELECT2 = `SELECT ${FOLDER_SELECT_COLUMNS} FROM folders`;
5234
6089
  var REQUEST_SELECT2 = `SELECT ${REQUEST_SELECT_COLUMNS} FROM requests`;
6090
+ var DOCUMENT_SELECT2 = `SELECT ${DOCUMENT_SELECT_COLUMNS} FROM documents`;
5235
6091
  var LLM_USAGE_SELECT2 = `SELECT ${LLM_USAGE_SELECT_COLUMNS} FROM llm_usage`;
5236
6092
  var LLM_USAGE_LOG_SELECT2 = `SELECT ${LLM_USAGE_LOG_SELECT_COLUMNS} FROM llm_usage_log`;
5237
6093
  var PostgresDatabase = class _PostgresDatabase {
@@ -6003,10 +6859,34 @@ var PostgresDatabase = class _PostgresDatabase {
6003
6859
  *
6004
6860
  * @param actingUserId - User performing the update action.
6005
6861
  */
6006
- async updateCollection(id, name, variables, headers, preRequestScript, postRequestScript, auth, actingUserId) {
6862
+ async updateCollection(id, name, variables, headers, preRequestScript, postRequestScript, auth, actingUserId, marker) {
6007
6863
  const trimmedName = trimRequiredName(name, "Collection name");
6008
6864
  const updatedAt = /* @__PURE__ */ new Date();
6009
- const result = await this.query(
6865
+ const result = marker !== void 0 ? await this.query(
6866
+ `UPDATE collections
6867
+ SET name = $1,
6868
+ variables = $2,
6869
+ headers = $3,
6870
+ auth = $4,
6871
+ pre_request_script = $5,
6872
+ post_request_script = $6,
6873
+ updated_at = $7,
6874
+ updated_by_user_id = $8,
6875
+ marker = $9
6876
+ WHERE id = $10`,
6877
+ [
6878
+ trimmedName,
6879
+ JSON.stringify(variables),
6880
+ JSON.stringify(headers),
6881
+ JSON.stringify(auth),
6882
+ preRequestScript,
6883
+ postRequestScript,
6884
+ updatedAt,
6885
+ actingUserId,
6886
+ serializeSidebarMarker(marker),
6887
+ id
6888
+ ]
6889
+ ) : await this.query(
6010
6890
  `UPDATE collections
6011
6891
  SET name = $1,
6012
6892
  variables = $2,
@@ -6134,10 +7014,26 @@ var PostgresDatabase = class _PostgresDatabase {
6134
7014
  *
6135
7015
  * @param actingUserId - User performing the update action.
6136
7016
  */
6137
- async updateEnvironment(id, name, variables, actingUserId) {
7017
+ async updateEnvironment(id, name, variables, actingUserId, marker) {
6138
7018
  const trimmedName = trimRequiredName(name, "Environment name");
6139
7019
  const updatedAt = /* @__PURE__ */ new Date();
6140
- const result = await this.query(
7020
+ const result = marker !== void 0 ? await this.query(
7021
+ `UPDATE environments
7022
+ SET name = $1,
7023
+ variables = $2,
7024
+ updated_at = $3,
7025
+ updated_by_user_id = $4,
7026
+ marker = $5
7027
+ WHERE id = $6`,
7028
+ [
7029
+ trimmedName,
7030
+ JSON.stringify(variables),
7031
+ updatedAt,
7032
+ actingUserId,
7033
+ serializeSidebarMarker(marker),
7034
+ id
7035
+ ]
7036
+ ) : await this.query(
6141
7037
  `UPDATE environments
6142
7038
  SET name = $1,
6143
7039
  variables = $2,
@@ -6369,6 +7265,7 @@ var PostgresDatabase = class _PostgresDatabase {
6369
7265
  const params = JSON.stringify(input.params);
6370
7266
  const auth = JSON.stringify(input.auth);
6371
7267
  const folderId = input.folderId ?? null;
7268
+ const serializedMarker = serializeSidebarMarker(input.marker ?? null);
6372
7269
  const now = /* @__PURE__ */ new Date();
6373
7270
  if (folderId != null) {
6374
7271
  const folderResult = await this.query(
@@ -6396,9 +7293,10 @@ var PostgresDatabase = class _PostgresDatabase {
6396
7293
  pre_request_script = $11,
6397
7294
  post_request_script = $12,
6398
7295
  comment = $13,
6399
- updated_at = $14,
6400
- updated_by_user_id = $15
6401
- WHERE id = $16`,
7296
+ marker = $14,
7297
+ updated_at = $15,
7298
+ updated_by_user_id = $16
7299
+ WHERE id = $17`,
6402
7300
  [
6403
7301
  input.collectionId,
6404
7302
  folderId,
@@ -6413,6 +7311,7 @@ var PostgresDatabase = class _PostgresDatabase {
6413
7311
  input.preRequestScript,
6414
7312
  input.postRequestScript,
6415
7313
  input.comment,
7314
+ serializedMarker,
6416
7315
  now,
6417
7316
  actingUserId,
6418
7317
  input.id
@@ -6453,12 +7352,13 @@ var PostgresDatabase = class _PostgresDatabase {
6453
7352
  pre_request_script,
6454
7353
  post_request_script,
6455
7354
  comment,
7355
+ marker,
6456
7356
  sort_order,
6457
7357
  created_at,
6458
7358
  updated_at,
6459
7359
  created_by_user_id,
6460
7360
  updated_by_user_id
6461
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
7361
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
6462
7362
  RETURNING ${REQUEST_SELECT_COLUMNS}`,
6463
7363
  [
6464
7364
  id,
@@ -6475,6 +7375,7 @@ var PostgresDatabase = class _PostgresDatabase {
6475
7375
  input.preRequestScript,
6476
7376
  input.postRequestScript,
6477
7377
  input.comment,
7378
+ serializedMarker,
6478
7379
  maxOrder + 1,
6479
7380
  now,
6480
7381
  now,
@@ -6500,163 +7401,479 @@ var PostgresDatabase = class _PostgresDatabase {
6500
7401
  await this.query("DELETE FROM requests WHERE id = $1", [id]);
6501
7402
  }
6502
7403
  /**
6503
- * Lists all folders in a collection.
7404
+ * Lists all folders in a collection.
7405
+ *
7406
+ * @param collectionId - Collection to query.
7407
+ */
7408
+ async listFolders(collectionId) {
7409
+ const result = await this.query(
7410
+ `${FOLDER_SELECT2} WHERE collection_id = $1 ORDER BY sort_order ASC, name ASC`,
7411
+ [collectionId]
7412
+ );
7413
+ return result.rows.map(mapFolderSqlRow);
7414
+ }
7415
+ /**
7416
+ * Finds a folder by id.
7417
+ *
7418
+ * @param id - Folder identifier to look up.
7419
+ */
7420
+ async findFolderById(id) {
7421
+ const result = await this.query(`${FOLDER_SELECT2} WHERE id = $1 LIMIT 1`, [id]);
7422
+ const row = result.rows[0];
7423
+ return row ? mapFolderSqlRow(row) : null;
7424
+ }
7425
+ /**
7426
+ * Creates a new folder in a collection.
7427
+ *
7428
+ * @param collectionId - Collection to add the folder to.
7429
+ * @param name - Display name for the folder.
7430
+ * @param actingUserId - User performing the create action.
7431
+ */
7432
+ async createFolder(collectionId, name, actingUserId, parentFolderId = null) {
7433
+ const trimmedName = trimRequiredName(name, "Folder name");
7434
+ if (parentFolderId != null) {
7435
+ const parent = await this.findFolderById(parentFolderId);
7436
+ if (!parent || parent.collectionId !== collectionId) {
7437
+ throw new Error("Parent folder not found in collection");
7438
+ }
7439
+ }
7440
+ const id = randomUUID5();
7441
+ const now = /* @__PURE__ */ new Date();
7442
+ const maxResult = await this.query(
7443
+ `SELECT COALESCE(MAX(sort_order), -1) AS max_order
7444
+ FROM folders
7445
+ WHERE collection_id = $1 AND parent_folder_id IS NOT DISTINCT FROM $2`,
7446
+ [collectionId, parentFolderId]
7447
+ );
7448
+ const maxOrder = maxResult.rows[0]?.max_order ?? -1;
7449
+ const result = await this.query(
7450
+ `INSERT INTO folders (
7451
+ id,
7452
+ collection_id,
7453
+ parent_folder_id,
7454
+ name,
7455
+ sort_order,
7456
+ created_at,
7457
+ updated_at,
7458
+ created_by_user_id,
7459
+ updated_by_user_id
7460
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
7461
+ RETURNING ${FOLDER_SELECT_COLUMNS}`,
7462
+ [
7463
+ id,
7464
+ collectionId,
7465
+ parentFolderId,
7466
+ trimmedName,
7467
+ maxOrder + 1,
7468
+ now,
7469
+ now,
7470
+ actingUserId,
7471
+ actingUserId
7472
+ ]
7473
+ );
7474
+ const row = result.rows[0];
7475
+ if (!row) {
7476
+ throw new Error("Folder not found after insert");
7477
+ }
7478
+ await this.recordAuditEntry(actingUserId, "create", "folder", id);
7479
+ return mapFolderSqlRow(row);
7480
+ }
7481
+ /**
7482
+ * Renames a folder.
7483
+ *
7484
+ * @param id - Folder ID to rename.
7485
+ * @param name - New display name.
7486
+ * @param actingUserId - User performing the rename action.
7487
+ */
7488
+ async renameFolder(id, name, actingUserId, marker) {
7489
+ const trimmedName = trimRequiredName(name, "Folder name");
7490
+ const updatedAt = /* @__PURE__ */ new Date();
7491
+ const result = marker !== void 0 ? await this.query(
7492
+ `UPDATE folders
7493
+ SET name = $1,
7494
+ updated_at = $2,
7495
+ updated_by_user_id = $3,
7496
+ marker = $4
7497
+ WHERE id = $5
7498
+ RETURNING ${FOLDER_SELECT_COLUMNS}`,
7499
+ [trimmedName, updatedAt, actingUserId, serializeSidebarMarker(marker), id]
7500
+ ) : await this.query(
7501
+ `UPDATE folders
7502
+ SET name = $1,
7503
+ updated_at = $2,
7504
+ updated_by_user_id = $3
7505
+ WHERE id = $4
7506
+ RETURNING ${FOLDER_SELECT_COLUMNS}`,
7507
+ [trimmedName, updatedAt, actingUserId, id]
7508
+ );
7509
+ const row = result.rows[0];
7510
+ if (!row) {
7511
+ throw new Error("Folder not found");
7512
+ }
7513
+ await this.recordAuditEntry(actingUserId, "update", "folder", id);
7514
+ return mapFolderSqlRow(row);
7515
+ }
7516
+ /**
7517
+ * Deletes a folder and its descendants.
7518
+ *
7519
+ * @param id - Folder ID to delete.
7520
+ * @param actingUserId - User performing the delete action.
7521
+ */
7522
+ async deleteFolder(id, actingUserId) {
7523
+ await this.recordAuditEntry(actingUserId, "delete", "folder", id);
7524
+ await this.query("DELETE FROM folders WHERE id = $1", [id]);
7525
+ }
7526
+ /**
7527
+ * Moves a folder to a new parent and optional sibling position.
7528
+ *
7529
+ * @param id - Folder ID to move.
7530
+ * @param parentFolderId - Destination parent, or null for collection root.
7531
+ * @param sortOrder - Optional zero-based destination sibling index.
7532
+ * @param actingUserId - User performing the move action.
7533
+ */
7534
+ async moveFolder(id, parentFolderId, sortOrder, actingUserId) {
7535
+ const folder = await this.findFolderById(id);
7536
+ if (!folder) {
7537
+ throw new Error("Folder not found");
7538
+ }
7539
+ const folders = await this.listFolders(folder.collectionId);
7540
+ if (parentFolderId != null) {
7541
+ const parent = folders.find((entry) => entry.id === parentFolderId);
7542
+ if (!parent) {
7543
+ throw new Error("Parent folder not found in collection");
7544
+ }
7545
+ let ancestor = parent;
7546
+ while (ancestor) {
7547
+ if (ancestor.id === id) {
7548
+ throw new Error("Cannot move a folder inside itself or its descendants");
7549
+ }
7550
+ ancestor = ancestor.parentFolderId == null ? void 0 : folders.find((entry) => entry.id === ancestor?.parentFolderId);
7551
+ }
7552
+ }
7553
+ const siblings = folders.filter((entry) => entry.id !== id && entry.parentFolderId === parentFolderId).sort(
7554
+ (left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name)
7555
+ );
7556
+ const index = Math.max(0, Math.min(sortOrder ?? siblings.length, siblings.length));
7557
+ siblings.splice(index, 0, { ...folder, parentFolderId });
7558
+ const updatedAt = /* @__PURE__ */ new Date();
7559
+ await this.query(
7560
+ `UPDATE folders SET parent_folder_id = $1, updated_at = $2, updated_by_user_id = $3
7561
+ WHERE id = $4`,
7562
+ [parentFolderId, updatedAt, actingUserId, id]
7563
+ );
7564
+ await this.reorderFolders(
7565
+ folder.collectionId,
7566
+ parentFolderId,
7567
+ siblings.map((entry) => entry.id),
7568
+ actingUserId
7569
+ );
7570
+ const moved = await this.findFolderById(id);
7571
+ if (!moved) {
7572
+ throw new Error("Folder not found after move");
7573
+ }
7574
+ return moved;
7575
+ }
7576
+ /**
7577
+ * Reorders sibling folders within a collection.
7578
+ *
7579
+ * @param collectionId - Collection containing the folders.
7580
+ * @param orderedFolderIds - Folder IDs in desired order.
7581
+ * @param actingUserId - User performing the reorder action.
7582
+ */
7583
+ async reorderFolders(collectionId, parentFolderId, orderedFolderIds, actingUserId) {
7584
+ const client = await this.requirePool().connect();
7585
+ const updatedAt = /* @__PURE__ */ new Date();
7586
+ try {
7587
+ await client.query("BEGIN");
7588
+ for (let index = 0; index < orderedFolderIds.length; index++) {
7589
+ await client.query(
7590
+ `UPDATE folders
7591
+ SET sort_order = $1,
7592
+ updated_at = $2,
7593
+ updated_by_user_id = $3
7594
+ WHERE id = $4
7595
+ AND collection_id = $5
7596
+ AND parent_folder_id IS NOT DISTINCT FROM $6`,
7597
+ [index, updatedAt, actingUserId, orderedFolderIds[index], collectionId, parentFolderId]
7598
+ );
7599
+ }
7600
+ await client.query("COMMIT");
7601
+ } catch (err) {
7602
+ await client.query("ROLLBACK");
7603
+ throw err;
7604
+ } finally {
7605
+ client.release();
7606
+ }
7607
+ await this.recordAuditEntry(actingUserId, "reorder", "folder", collectionId, {
7608
+ parentFolderId,
7609
+ orderedFolderIds
7610
+ });
7611
+ }
7612
+ /**
7613
+ * Reorders requests within a folder or at collection root.
7614
+ *
7615
+ * @param actingUserId - User performing the reorder action.
7616
+ */
7617
+ async reorderRequests(collectionId, folderId, orderedRequestIds, actingUserId) {
7618
+ const client = await this.requirePool().connect();
7619
+ const updatedAt = /* @__PURE__ */ new Date();
7620
+ try {
7621
+ await client.query("BEGIN");
7622
+ for (let index = 0; index < orderedRequestIds.length; index++) {
7623
+ await client.query(
7624
+ `UPDATE requests
7625
+ SET sort_order = $1,
7626
+ folder_id = $2,
7627
+ updated_at = $3,
7628
+ updated_by_user_id = $4
7629
+ WHERE id = $5 AND collection_id = $6`,
7630
+ [index, folderId, updatedAt, actingUserId, orderedRequestIds[index], collectionId]
7631
+ );
7632
+ }
7633
+ await client.query("COMMIT");
7634
+ } catch (err) {
7635
+ await client.query("ROLLBACK");
7636
+ throw err;
7637
+ } finally {
7638
+ client.release();
7639
+ }
7640
+ await this.recordAuditEntry(actingUserId, "reorder", "request", collectionId, {
7641
+ folderId,
7642
+ orderedRequestIds
7643
+ });
7644
+ }
7645
+ /**
7646
+ * Moves a request to another folder or collection root at a given index.
7647
+ *
7648
+ * @param actingUserId - User performing the move action.
7649
+ */
7650
+ async moveRequest(requestId, folderId, index, actingUserId) {
7651
+ const client = await this.requirePool().connect();
7652
+ const updatedAt = /* @__PURE__ */ new Date();
7653
+ const listInContainer = async (collectionId, targetFolderId) => {
7654
+ const result = await client.query(
7655
+ `SELECT id FROM requests WHERE collection_id = $1
7656
+ AND (($2::text IS NULL AND folder_id IS NULL) OR folder_id = $2)
7657
+ ORDER BY sort_order ASC, name ASC`,
7658
+ [collectionId, targetFolderId]
7659
+ );
7660
+ return result.rows.map((row) => row.id);
7661
+ };
7662
+ const reindexContainer = async (targetFolderId, orderedIds) => {
7663
+ for (let sortIndex = 0; sortIndex < orderedIds.length; sortIndex++) {
7664
+ await client.query(
7665
+ `UPDATE requests
7666
+ SET sort_order = $1,
7667
+ folder_id = $2,
7668
+ updated_at = $3,
7669
+ updated_by_user_id = $4
7670
+ WHERE id = $5`,
7671
+ [sortIndex, targetFolderId, updatedAt, actingUserId, orderedIds[sortIndex]]
7672
+ );
7673
+ }
7674
+ };
7675
+ try {
7676
+ await client.query("BEGIN");
7677
+ const requestResult = await client.query(`${REQUEST_SELECT2} WHERE id = $1`, [
7678
+ requestId
7679
+ ]);
7680
+ const requestRow = requestResult.rows[0];
7681
+ if (!requestRow) {
7682
+ throw new Error("Request not found");
7683
+ }
7684
+ const request = mapRequestSqlRow(requestRow);
7685
+ const collectionId = request.collectionId;
7686
+ const oldFolderId = request.folderId;
7687
+ if (folderId != null) {
7688
+ const folderResult = await client.query(
7689
+ "SELECT collection_id FROM folders WHERE id = $1",
7690
+ [folderId]
7691
+ );
7692
+ const folderRow = folderResult.rows[0];
7693
+ if (!folderRow || folderRow.collection_id !== collectionId) {
7694
+ throw new Error("Folder not found");
7695
+ }
7696
+ }
7697
+ if (oldFolderId === folderId) {
7698
+ const siblings = (await listInContainer(collectionId, folderId)).filter(
7699
+ (id) => id !== requestId
7700
+ );
7701
+ siblings.splice(index, 0, requestId);
7702
+ await reindexContainer(folderId, siblings);
7703
+ } else {
7704
+ const oldIds = (await listInContainer(collectionId, oldFolderId)).filter(
7705
+ (id) => id !== requestId
7706
+ );
7707
+ await reindexContainer(oldFolderId, oldIds);
7708
+ const newIds = (await listInContainer(collectionId, folderId)).filter(
7709
+ (id) => id !== requestId
7710
+ );
7711
+ newIds.splice(index, 0, requestId);
7712
+ await reindexContainer(folderId, newIds);
7713
+ }
7714
+ await client.query("COMMIT");
7715
+ } catch (err) {
7716
+ await client.query("ROLLBACK");
7717
+ throw err;
7718
+ } finally {
7719
+ client.release();
7720
+ }
7721
+ await this.recordAuditEntry(actingUserId, "move", "request", requestId, {
7722
+ folderId,
7723
+ index
7724
+ });
7725
+ }
7726
+ /**
7727
+ * Lists all documents in a collection.
6504
7728
  *
6505
7729
  * @param collectionId - Collection to query.
6506
7730
  */
6507
- async listFolders(collectionId) {
7731
+ async listDocuments(collectionId) {
6508
7732
  const result = await this.query(
6509
- `${FOLDER_SELECT2} WHERE collection_id = $1 ORDER BY sort_order ASC, name ASC`,
7733
+ `${DOCUMENT_SELECT2} WHERE collection_id = $1 ORDER BY sort_order ASC, name ASC`,
6510
7734
  [collectionId]
6511
7735
  );
6512
- return result.rows.map(mapFolderSqlRow);
7736
+ return result.rows.map(mapDocumentSqlRow);
6513
7737
  }
6514
7738
  /**
6515
- * Finds a folder by id.
7739
+ * Finds a document by id.
6516
7740
  *
6517
- * @param id - Folder identifier to look up.
7741
+ * @param id - Document identifier to look up.
6518
7742
  */
6519
- async findFolderById(id) {
6520
- const result = await this.query(`${FOLDER_SELECT2} WHERE id = $1 LIMIT 1`, [id]);
7743
+ async findDocumentById(id) {
7744
+ const result = await this.query(`${DOCUMENT_SELECT2} WHERE id = $1 LIMIT 1`, [
7745
+ id
7746
+ ]);
6521
7747
  const row = result.rows[0];
6522
- return row ? mapFolderSqlRow(row) : null;
7748
+ return row ? mapDocumentSqlRow(row) : null;
6523
7749
  }
6524
7750
  /**
6525
- * Creates a new folder in a collection.
7751
+ * Inserts a new document or updates an existing one.
6526
7752
  *
6527
- * @param collectionId - Collection to add the folder to.
6528
- * @param name - Display name for the folder.
6529
- * @param actingUserId - User performing the create action.
7753
+ * @param input - Document fields to persist.
7754
+ * @param actingUserId - User performing the save action.
6530
7755
  */
6531
- async createFolder(collectionId, name, actingUserId) {
6532
- const trimmedName = trimRequiredName(name, "Folder name");
6533
- const id = randomUUID5();
7756
+ async saveDocument(input, actingUserId) {
7757
+ const trimmedName = trimRequiredName(input.name, "Document name");
7758
+ const folderId = input.folderId ?? null;
7759
+ const serializedMarker = serializeSidebarMarker(input.marker ?? null);
6534
7760
  const now = /* @__PURE__ */ new Date();
7761
+ if (folderId != null) {
7762
+ const folderResult = await this.query(
7763
+ "SELECT collection_id FROM folders WHERE id = $1",
7764
+ [folderId]
7765
+ );
7766
+ const folderRow = folderResult.rows[0];
7767
+ if (!folderRow || folderRow.collection_id !== input.collectionId) {
7768
+ throw new Error("Folder not found");
7769
+ }
7770
+ }
7771
+ if (input.id) {
7772
+ const result2 = await this.query(
7773
+ `UPDATE documents SET
7774
+ collection_id = $1,
7775
+ folder_id = $2,
7776
+ name = $3,
7777
+ content = $4,
7778
+ marker = $5,
7779
+ updated_at = $6,
7780
+ updated_by_user_id = $7
7781
+ WHERE id = $8`,
7782
+ [
7783
+ input.collectionId,
7784
+ folderId,
7785
+ trimmedName,
7786
+ input.content,
7787
+ serializedMarker,
7788
+ now,
7789
+ actingUserId,
7790
+ input.id
7791
+ ]
7792
+ );
7793
+ if ((result2.rowCount ?? 0) > 0) {
7794
+ await this.recordAuditEntry(actingUserId, "update", "document", input.id);
7795
+ const selectResult = await this.query(`${DOCUMENT_SELECT2} WHERE id = $1`, [
7796
+ input.id
7797
+ ]);
7798
+ const row2 = selectResult.rows[0];
7799
+ if (row2) {
7800
+ return mapDocumentSqlRow(row2);
7801
+ }
7802
+ }
7803
+ }
6535
7804
  const maxResult = await this.query(
6536
- "SELECT COALESCE(MAX(sort_order), -1) AS max_order FROM folders WHERE collection_id = $1",
6537
- [collectionId]
7805
+ `SELECT COALESCE(MAX(sort_order), -1) AS max_order FROM documents
7806
+ WHERE collection_id = $1
7807
+ AND (($2::text IS NULL AND folder_id IS NULL) OR folder_id = $2)`,
7808
+ [input.collectionId, folderId]
6538
7809
  );
6539
7810
  const maxOrder = maxResult.rows[0]?.max_order ?? -1;
7811
+ const id = randomUUID5();
6540
7812
  const result = await this.query(
6541
- `INSERT INTO folders (
7813
+ `INSERT INTO documents (
6542
7814
  id,
6543
7815
  collection_id,
7816
+ folder_id,
6544
7817
  name,
7818
+ content,
7819
+ marker,
6545
7820
  sort_order,
6546
7821
  created_at,
6547
7822
  updated_at,
6548
7823
  created_by_user_id,
6549
7824
  updated_by_user_id
6550
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
6551
- RETURNING ${FOLDER_SELECT_COLUMNS}`,
6552
- [id, collectionId, trimmedName, maxOrder + 1, now, now, actingUserId, actingUserId]
6553
- );
6554
- const row = result.rows[0];
6555
- if (!row) {
6556
- throw new Error("Folder not found after insert");
6557
- }
6558
- await this.recordAuditEntry(actingUserId, "create", "folder", id);
6559
- return mapFolderSqlRow(row);
6560
- }
6561
- /**
6562
- * Renames a folder.
6563
- *
6564
- * @param id - Folder ID to rename.
6565
- * @param name - New display name.
6566
- * @param actingUserId - User performing the rename action.
6567
- */
6568
- async renameFolder(id, name, actingUserId) {
6569
- const trimmedName = trimRequiredName(name, "Folder name");
6570
- const updatedAt = /* @__PURE__ */ new Date();
6571
- const result = await this.query(
6572
- `UPDATE folders
6573
- SET name = $1,
6574
- updated_at = $2,
6575
- updated_by_user_id = $3
6576
- WHERE id = $4
6577
- RETURNING ${FOLDER_SELECT_COLUMNS}`,
6578
- [trimmedName, updatedAt, actingUserId, id]
7825
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
7826
+ RETURNING ${DOCUMENT_SELECT_COLUMNS}`,
7827
+ [
7828
+ id,
7829
+ input.collectionId,
7830
+ folderId,
7831
+ trimmedName,
7832
+ input.content,
7833
+ serializedMarker,
7834
+ maxOrder + 1,
7835
+ now,
7836
+ now,
7837
+ actingUserId,
7838
+ actingUserId
7839
+ ]
6579
7840
  );
6580
7841
  const row = result.rows[0];
6581
7842
  if (!row) {
6582
- throw new Error("Folder not found");
7843
+ throw new Error("Document not found after insert");
6583
7844
  }
6584
- await this.recordAuditEntry(actingUserId, "update", "folder", id);
6585
- return mapFolderSqlRow(row);
7845
+ await this.recordAuditEntry(actingUserId, "create", "document", id);
7846
+ return mapDocumentSqlRow(row);
6586
7847
  }
6587
7848
  /**
6588
- * Deletes a folder and all requests inside it.
7849
+ * Deletes a document by ID.
6589
7850
  *
6590
- * @param id - Folder ID to delete.
7851
+ * @param id - Document ID to delete.
6591
7852
  * @param actingUserId - User performing the delete action.
6592
7853
  */
6593
- async deleteFolder(id, actingUserId) {
6594
- await this.recordAuditEntry(actingUserId, "delete", "folder", id);
6595
- const client = await this.requirePool().connect();
6596
- try {
6597
- await client.query("BEGIN");
6598
- await client.query("DELETE FROM requests WHERE folder_id = $1", [id]);
6599
- await client.query("DELETE FROM folders WHERE id = $1", [id]);
6600
- await client.query("COMMIT");
6601
- } catch (err) {
6602
- await client.query("ROLLBACK");
6603
- throw err;
6604
- } finally {
6605
- client.release();
6606
- }
6607
- }
6608
- /**
6609
- * Reorders folders within a collection.
6610
- *
6611
- * @param collectionId - Collection containing the folders.
6612
- * @param orderedFolderIds - Folder IDs in desired order.
6613
- * @param actingUserId - User performing the reorder action.
6614
- */
6615
- async reorderFolders(collectionId, orderedFolderIds, actingUserId) {
6616
- const client = await this.requirePool().connect();
6617
- const updatedAt = /* @__PURE__ */ new Date();
6618
- try {
6619
- await client.query("BEGIN");
6620
- for (let index = 0; index < orderedFolderIds.length; index++) {
6621
- await client.query(
6622
- `UPDATE folders
6623
- SET sort_order = $1,
6624
- updated_at = $2,
6625
- updated_by_user_id = $3
6626
- WHERE id = $4 AND collection_id = $5`,
6627
- [index, updatedAt, actingUserId, orderedFolderIds[index], collectionId]
6628
- );
6629
- }
6630
- await client.query("COMMIT");
6631
- } catch (err) {
6632
- await client.query("ROLLBACK");
6633
- throw err;
6634
- } finally {
6635
- client.release();
6636
- }
6637
- await this.recordAuditEntry(actingUserId, "reorder", "folder", collectionId, {
6638
- orderedFolderIds
6639
- });
7854
+ async deleteDocument(id, actingUserId) {
7855
+ await this.recordAuditEntry(actingUserId, "delete", "document", id);
7856
+ await this.query("DELETE FROM documents WHERE id = $1", [id]);
6640
7857
  }
6641
7858
  /**
6642
- * Reorders requests within a folder or at collection root.
7859
+ * Reorders documents within a folder or at collection root.
6643
7860
  *
6644
7861
  * @param actingUserId - User performing the reorder action.
6645
7862
  */
6646
- async reorderRequests(collectionId, folderId, orderedRequestIds, actingUserId) {
7863
+ async reorderDocuments(collectionId, folderId, orderedDocumentIds, actingUserId) {
6647
7864
  const client = await this.requirePool().connect();
6648
7865
  const updatedAt = /* @__PURE__ */ new Date();
6649
7866
  try {
6650
7867
  await client.query("BEGIN");
6651
- for (let index = 0; index < orderedRequestIds.length; index++) {
7868
+ for (let index = 0; index < orderedDocumentIds.length; index++) {
6652
7869
  await client.query(
6653
- `UPDATE requests
7870
+ `UPDATE documents
6654
7871
  SET sort_order = $1,
6655
7872
  folder_id = $2,
6656
7873
  updated_at = $3,
6657
7874
  updated_by_user_id = $4
6658
7875
  WHERE id = $5 AND collection_id = $6`,
6659
- [index, folderId, updatedAt, actingUserId, orderedRequestIds[index], collectionId]
7876
+ [index, folderId, updatedAt, actingUserId, orderedDocumentIds[index], collectionId]
6660
7877
  );
6661
7878
  }
6662
7879
  await client.query("COMMIT");
@@ -6666,22 +7883,22 @@ var PostgresDatabase = class _PostgresDatabase {
6666
7883
  } finally {
6667
7884
  client.release();
6668
7885
  }
6669
- await this.recordAuditEntry(actingUserId, "reorder", "request", collectionId, {
7886
+ await this.recordAuditEntry(actingUserId, "reorder", "document", collectionId, {
6670
7887
  folderId,
6671
- orderedRequestIds
7888
+ orderedDocumentIds
6672
7889
  });
6673
7890
  }
6674
7891
  /**
6675
- * Moves a request to another folder or collection root at a given index.
7892
+ * Moves a document to another folder or collection root at a given index.
6676
7893
  *
6677
7894
  * @param actingUserId - User performing the move action.
6678
7895
  */
6679
- async moveRequest(requestId, folderId, index, actingUserId) {
7896
+ async moveDocument(documentId, folderId, index, actingUserId) {
6680
7897
  const client = await this.requirePool().connect();
6681
7898
  const updatedAt = /* @__PURE__ */ new Date();
6682
7899
  const listInContainer = async (collectionId, targetFolderId) => {
6683
7900
  const result = await client.query(
6684
- `SELECT id FROM requests WHERE collection_id = $1
7901
+ `SELECT id FROM documents WHERE collection_id = $1
6685
7902
  AND (($2::text IS NULL AND folder_id IS NULL) OR folder_id = $2)
6686
7903
  ORDER BY sort_order ASC, name ASC`,
6687
7904
  [collectionId, targetFolderId]
@@ -6691,7 +7908,7 @@ var PostgresDatabase = class _PostgresDatabase {
6691
7908
  const reindexContainer = async (targetFolderId, orderedIds) => {
6692
7909
  for (let sortIndex = 0; sortIndex < orderedIds.length; sortIndex++) {
6693
7910
  await client.query(
6694
- `UPDATE requests
7911
+ `UPDATE documents
6695
7912
  SET sort_order = $1,
6696
7913
  folder_id = $2,
6697
7914
  updated_at = $3,
@@ -6703,16 +7920,17 @@ var PostgresDatabase = class _PostgresDatabase {
6703
7920
  };
6704
7921
  try {
6705
7922
  await client.query("BEGIN");
6706
- const requestResult = await client.query(`${REQUEST_SELECT2} WHERE id = $1`, [
6707
- requestId
6708
- ]);
6709
- const requestRow = requestResult.rows[0];
6710
- if (!requestRow) {
6711
- throw new Error("Request not found");
7923
+ const documentResult = await client.query(
7924
+ `${DOCUMENT_SELECT2} WHERE id = $1`,
7925
+ [documentId]
7926
+ );
7927
+ const documentRow = documentResult.rows[0];
7928
+ if (!documentRow) {
7929
+ throw new Error("Document not found");
6712
7930
  }
6713
- const request = mapRequestSqlRow(requestRow);
6714
- const collectionId = request.collectionId;
6715
- const oldFolderId = request.folderId;
7931
+ const document = mapDocumentSqlRow(documentRow);
7932
+ const collectionId = document.collectionId;
7933
+ const oldFolderId = document.folderId;
6716
7934
  if (folderId != null) {
6717
7935
  const folderResult = await client.query(
6718
7936
  "SELECT collection_id FROM folders WHERE id = $1",
@@ -6725,19 +7943,19 @@ var PostgresDatabase = class _PostgresDatabase {
6725
7943
  }
6726
7944
  if (oldFolderId === folderId) {
6727
7945
  const siblings = (await listInContainer(collectionId, folderId)).filter(
6728
- (id) => id !== requestId
7946
+ (id) => id !== documentId
6729
7947
  );
6730
- siblings.splice(index, 0, requestId);
7948
+ siblings.splice(index, 0, documentId);
6731
7949
  await reindexContainer(folderId, siblings);
6732
7950
  } else {
6733
7951
  const oldIds = (await listInContainer(collectionId, oldFolderId)).filter(
6734
- (id) => id !== requestId
7952
+ (id) => id !== documentId
6735
7953
  );
6736
7954
  await reindexContainer(oldFolderId, oldIds);
6737
7955
  const newIds = (await listInContainer(collectionId, folderId)).filter(
6738
- (id) => id !== requestId
7956
+ (id) => id !== documentId
6739
7957
  );
6740
- newIds.splice(index, 0, requestId);
7958
+ newIds.splice(index, 0, documentId);
6741
7959
  await reindexContainer(folderId, newIds);
6742
7960
  }
6743
7961
  await client.query("COMMIT");
@@ -6747,7 +7965,7 @@ var PostgresDatabase = class _PostgresDatabase {
6747
7965
  } finally {
6748
7966
  client.release();
6749
7967
  }
6750
- await this.recordAuditEntry(actingUserId, "move", "request", requestId, {
7968
+ await this.recordAuditEntry(actingUserId, "move", "document", documentId, {
6751
7969
  folderId,
6752
7970
  index
6753
7971
  });
@@ -8160,6 +9378,9 @@ function canDeleteCollection(user, collection) {
8160
9378
  function canDeleteRequest(user, request) {
8161
9379
  return canUseDataApi(user) && canAccessCollection(user, request.collectionId) && request.createdByUserId === user.id;
8162
9380
  }
9381
+ function canDeleteDocument(user, document) {
9382
+ return canUseDataApi(user) && canAccessCollection(user, document.collectionId) && document.createdByUserId === user.id;
9383
+ }
8163
9384
  function canDeleteRunResult(user, runResult) {
8164
9385
  return canUseDataApi(user) && runResult.createdByUserId === user.id;
8165
9386
  }
@@ -8418,6 +9639,7 @@ var llmUsageSummaryResponseSchema = z7.object({
8418
9639
 
8419
9640
  // src/server/routes/schemas/entities.ts
8420
9641
  import { z as z8 } from "zod/v4";
9642
+ var sidebarMarkerBodySchema = z8.union([z8.string().trim().min(1), z8.null()]).optional();
8421
9643
  var collectionRecordSchema = z8.object({
8422
9644
  id: z8.string(),
8423
9645
  name: z8.string(),
@@ -8430,7 +9652,8 @@ var collectionRecordSchema = z8.object({
8430
9652
  updatedAt: timestampSchema,
8431
9653
  createdByUserId: z8.string().nullable(),
8432
9654
  updatedByUserId: z8.string().nullable(),
8433
- deletionLocked: z8.boolean()
9655
+ deletionLocked: z8.boolean(),
9656
+ marker: z8.string().nullable()
8434
9657
  });
8435
9658
  var environmentRecordSchema = z8.object({
8436
9659
  id: z8.string(),
@@ -8440,7 +9663,8 @@ var environmentRecordSchema = z8.object({
8440
9663
  updatedAt: timestampSchema,
8441
9664
  createdByUserId: z8.string().nullable(),
8442
9665
  updatedByUserId: z8.string().nullable(),
8443
- deletionLocked: z8.boolean()
9666
+ deletionLocked: z8.boolean(),
9667
+ marker: z8.string().nullable()
8444
9668
  });
8445
9669
  var snippetScopeSchema = z8.enum(["pre-request", "post-request", "any"]);
8446
9670
  var snippetRecordSchema = z8.object({
@@ -8458,12 +9682,14 @@ var snippetRecordSchema = z8.object({
8458
9682
  var folderRecordSchema = z8.object({
8459
9683
  id: z8.string(),
8460
9684
  collectionId: z8.string(),
9685
+ parentFolderId: z8.string().nullable(),
8461
9686
  name: z8.string(),
8462
9687
  sortOrder: z8.number().int(),
8463
9688
  createdAt: timestampSchema,
8464
9689
  updatedAt: timestampSchema,
8465
9690
  createdByUserId: z8.string().nullable(),
8466
- updatedByUserId: z8.string().nullable()
9691
+ updatedByUserId: z8.string().nullable(),
9692
+ marker: z8.string().nullable()
8467
9693
  });
8468
9694
  var savedRequestRecordSchema = z8.object({
8469
9695
  id: z8.string(),
@@ -8484,10 +9710,12 @@ var savedRequestRecordSchema = z8.object({
8484
9710
  createdAt: timestampSchema,
8485
9711
  updatedAt: timestampSchema,
8486
9712
  createdByUserId: z8.string().nullable(),
8487
- updatedByUserId: z8.string().nullable()
9713
+ updatedByUserId: z8.string().nullable(),
9714
+ marker: z8.string().nullable()
8488
9715
  });
8489
9716
  var createCollectionBodySchema = z8.object({
8490
- name: z8.string().trim().min(1)
9717
+ name: z8.string().trim().min(1),
9718
+ marker: sidebarMarkerBodySchema
8491
9719
  });
8492
9720
  var updateCollectionBodySchema = z8.object({
8493
9721
  name: z8.string().trim().min(1),
@@ -8495,14 +9723,17 @@ var updateCollectionBodySchema = z8.object({
8495
9723
  headers: z8.array(keyValueSchema),
8496
9724
  preRequestScript: z8.string(),
8497
9725
  postRequestScript: z8.string(),
8498
- auth: authConfigSchema
9726
+ auth: authConfigSchema,
9727
+ marker: sidebarMarkerBodySchema
8499
9728
  });
8500
9729
  var createEnvironmentBodySchema = z8.object({
8501
- name: z8.string().trim().min(1)
9730
+ name: z8.string().trim().min(1),
9731
+ marker: sidebarMarkerBodySchema
8502
9732
  });
8503
9733
  var updateEnvironmentBodySchema = z8.object({
8504
9734
  name: z8.string().trim().min(1),
8505
- variables: z8.array(variableSchema)
9735
+ variables: z8.array(variableSchema),
9736
+ marker: sidebarMarkerBodySchema
8506
9737
  });
8507
9738
  var createSnippetBodySchema = z8.object({
8508
9739
  name: z8.string().trim().min(1),
@@ -8515,14 +9746,22 @@ var updateSnippetBodySchema = z8.object({
8515
9746
  scope: snippetScopeSchema
8516
9747
  });
8517
9748
  var createFolderBodySchema = z8.object({
8518
- name: z8.string().trim().min(1)
9749
+ name: z8.string().trim().min(1),
9750
+ parentFolderId: z8.string().nullable().optional(),
9751
+ marker: sidebarMarkerBodySchema
8519
9752
  });
8520
9753
  var renameFolderBodySchema = z8.object({
8521
- name: z8.string().trim().min(1)
9754
+ name: z8.string().trim().min(1),
9755
+ marker: sidebarMarkerBodySchema
8522
9756
  });
8523
9757
  var reorderFoldersBodySchema = z8.object({
9758
+ parentFolderId: z8.string().nullable(),
8524
9759
  orderedFolderIds: z8.array(z8.string().trim().min(1))
8525
9760
  });
9761
+ var moveFolderBodySchema = z8.object({
9762
+ parentFolderId: z8.string().nullable(),
9763
+ sortOrder: z8.number().int().min(0).optional()
9764
+ });
8526
9765
  var saveRequestBodySchema = z8.object({
8527
9766
  name: z8.string().trim().min(1),
8528
9767
  method: httpMethodSchema,
@@ -8535,7 +9774,8 @@ var saveRequestBodySchema = z8.object({
8535
9774
  preRequestScript: z8.string(),
8536
9775
  postRequestScript: z8.string(),
8537
9776
  comment: z8.string(),
8538
- folderId: z8.string().nullable().optional()
9777
+ folderId: z8.string().nullable().optional(),
9778
+ marker: sidebarMarkerBodySchema
8539
9779
  });
8540
9780
  var updateSaveRequestBodySchema = saveRequestBodySchema.extend({
8541
9781
  collectionId: z8.string().trim().min(1)
@@ -8563,6 +9803,39 @@ var listFoldersResponseSchema = z8.object({
8563
9803
  var listRequestsResponseSchema = z8.object({
8564
9804
  requests: z8.array(savedRequestRecordSchema)
8565
9805
  });
9806
+ var documentRecordSchema = z8.object({
9807
+ id: z8.string(),
9808
+ collectionId: z8.string(),
9809
+ name: z8.string(),
9810
+ content: z8.string(),
9811
+ folderId: z8.string().nullable(),
9812
+ sortOrder: z8.number().int(),
9813
+ createdAt: timestampSchema,
9814
+ updatedAt: timestampSchema,
9815
+ createdByUserId: z8.string().nullable(),
9816
+ updatedByUserId: z8.string().nullable(),
9817
+ marker: z8.string().nullable()
9818
+ });
9819
+ var saveDocumentBodySchema = z8.object({
9820
+ name: z8.string().trim().min(1),
9821
+ content: z8.string(),
9822
+ folderId: z8.string().nullable().optional(),
9823
+ marker: sidebarMarkerBodySchema
9824
+ });
9825
+ var updateSaveDocumentBodySchema = saveDocumentBodySchema.extend({
9826
+ collectionId: z8.string().trim().min(1)
9827
+ });
9828
+ var reorderDocumentsBodySchema = z8.object({
9829
+ folderId: z8.string().nullable(),
9830
+ orderedDocumentIds: z8.array(z8.string().trim().min(1))
9831
+ });
9832
+ var moveDocumentBodySchema = z8.object({
9833
+ folderId: z8.string().nullable(),
9834
+ index: z8.number().int().min(0)
9835
+ });
9836
+ var listDocumentsResponseSchema = z8.object({
9837
+ documents: z8.array(documentRecordSchema)
9838
+ });
8566
9839
  var emptyResponseSchema = z8.null();
8567
9840
  function serializeCollection(record) {
8568
9841
  return {
@@ -8599,6 +9872,13 @@ function serializeSavedRequest(record) {
8599
9872
  updatedAt: record.updatedAt.toISOString()
8600
9873
  };
8601
9874
  }
9875
+ function serializeDocument(record) {
9876
+ return {
9877
+ ...record,
9878
+ createdAt: record.createdAt.toISOString(),
9879
+ updatedAt: record.updatedAt.toISOString()
9880
+ };
9881
+ }
8602
9882
  var runResultRecordSchema = z8.object({
8603
9883
  id: z8.string(),
8604
9884
  kind: z8.enum(["collection-run-results", "request-run-results"]),
@@ -9193,13 +10473,50 @@ async function registerAdminRoutes(app, options) {
9193
10473
  if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
9194
10474
  return;
9195
10475
  }
9196
- const collections = await db.listCollections();
10476
+ const collections = await db.listCollections();
10477
+ return reply.send({
10478
+ collections: collections.map((collection) => ({
10479
+ id: collection.id,
10480
+ name: collection.name,
10481
+ deletionLocked: collection.deletionLocked
10482
+ }))
10483
+ });
10484
+ } catch (error) {
10485
+ if (handleDbError(reply, error)) {
10486
+ return;
10487
+ }
10488
+ throw error;
10489
+ }
10490
+ }
10491
+ });
10492
+ routes.route({
10493
+ method: "GET",
10494
+ url: "/admin/collections/:collectionId/folders",
10495
+ schema: {
10496
+ params: collectionIdParamSchema,
10497
+ response: {
10498
+ 200: listFoldersResponseSchema,
10499
+ 403: errorResponseSchema,
10500
+ 404: errorResponseSchema
10501
+ }
10502
+ },
10503
+ /**
10504
+ * Lists folders in a collection for operator inspection.
10505
+ */
10506
+ handler: async (request, reply) => {
10507
+ try {
10508
+ const user = requireAuthenticatedUser(request);
10509
+ if (denyUnlessAllowed(reply, canUseManagementApi(user))) {
10510
+ return;
10511
+ }
10512
+ const collection = await db.findCollectionById(request.params.collectionId);
10513
+ if (!collection) {
10514
+ void reply.code(404).send({ error: "Collection not found" });
10515
+ return;
10516
+ }
10517
+ const folders = await db.listFolders(request.params.collectionId);
9197
10518
  return reply.send({
9198
- collections: collections.map((collection) => ({
9199
- id: collection.id,
9200
- name: collection.name,
9201
- deletionLocked: collection.deletionLocked
9202
- }))
10519
+ folders: folders.map((folder) => serializeFolder(folder))
9203
10520
  });
9204
10521
  } catch (error) {
9205
10522
  if (handleDbError(reply, error)) {
@@ -9211,17 +10528,17 @@ async function registerAdminRoutes(app, options) {
9211
10528
  });
9212
10529
  routes.route({
9213
10530
  method: "GET",
9214
- url: "/admin/collections/:collectionId/folders",
10531
+ url: "/admin/collections/:collectionId/requests",
9215
10532
  schema: {
9216
10533
  params: collectionIdParamSchema,
9217
10534
  response: {
9218
- 200: listFoldersResponseSchema,
10535
+ 200: listRequestsResponseSchema,
9219
10536
  403: errorResponseSchema,
9220
10537
  404: errorResponseSchema
9221
10538
  }
9222
10539
  },
9223
10540
  /**
9224
- * Lists folders in a collection for operator inspection.
10541
+ * Lists saved requests in a collection for operator inspection.
9225
10542
  */
9226
10543
  handler: async (request, reply) => {
9227
10544
  try {
@@ -9234,9 +10551,9 @@ async function registerAdminRoutes(app, options) {
9234
10551
  void reply.code(404).send({ error: "Collection not found" });
9235
10552
  return;
9236
10553
  }
9237
- const folders = await db.listFolders(request.params.collectionId);
10554
+ const requests = await db.listRequests(request.params.collectionId);
9238
10555
  return reply.send({
9239
- folders: folders.map((folder) => serializeFolder(folder))
10556
+ requests: requests.map((savedRequest) => serializeSavedRequest(savedRequest))
9240
10557
  });
9241
10558
  } catch (error) {
9242
10559
  if (handleDbError(reply, error)) {
@@ -9248,17 +10565,17 @@ async function registerAdminRoutes(app, options) {
9248
10565
  });
9249
10566
  routes.route({
9250
10567
  method: "GET",
9251
- url: "/admin/collections/:collectionId/requests",
10568
+ url: "/admin/collections/:collectionId/documents",
9252
10569
  schema: {
9253
10570
  params: collectionIdParamSchema,
9254
10571
  response: {
9255
- 200: listRequestsResponseSchema,
10572
+ 200: listDocumentsResponseSchema,
9256
10573
  403: errorResponseSchema,
9257
10574
  404: errorResponseSchema
9258
10575
  }
9259
10576
  },
9260
10577
  /**
9261
- * Lists saved requests in a collection for operator inspection.
10578
+ * Lists collection documents in a collection for operator inspection.
9262
10579
  */
9263
10580
  handler: async (request, reply) => {
9264
10581
  try {
@@ -9271,9 +10588,9 @@ async function registerAdminRoutes(app, options) {
9271
10588
  void reply.code(404).send({ error: "Collection not found" });
9272
10589
  return;
9273
10590
  }
9274
- const requests = await db.listRequests(request.params.collectionId);
10591
+ const documents = await db.listDocuments(request.params.collectionId);
9275
10592
  return reply.send({
9276
- requests: requests.map((savedRequest) => serializeSavedRequest(savedRequest))
10593
+ documents: documents.map((document) => serializeDocument(document))
9277
10594
  });
9278
10595
  } catch (error) {
9279
10596
  if (handleDbError(reply, error)) {
@@ -10316,7 +11633,8 @@ async function registerCollectionRoutes(app, db) {
10316
11633
  request.body.preRequestScript,
10317
11634
  request.body.postRequestScript,
10318
11635
  request.body.auth,
10319
- user.id
11636
+ user.id,
11637
+ request.body.marker
10320
11638
  );
10321
11639
  return reply.send(serializeCollection(collection));
10322
11640
  } catch (error) {
@@ -10457,7 +11775,8 @@ async function registerEnvironmentRoutes(app, db) {
10457
11775
  request.params.id,
10458
11776
  request.body.name,
10459
11777
  request.body.variables,
10460
- user.id
11778
+ user.id,
11779
+ request.body.marker
10461
11780
  );
10462
11781
  return reply.send(serializeEnvironment(environment));
10463
11782
  } catch (error) {
@@ -10716,6 +12035,50 @@ async function registerFolderRoutes(app, db) {
10716
12035
  const folder = await db.createFolder(
10717
12036
  request.params.collectionId,
10718
12037
  request.body.name,
12038
+ user.id,
12039
+ request.body.parentFolderId
12040
+ );
12041
+ return reply.send(serializeFolder(folder));
12042
+ } catch (error) {
12043
+ if (handleDbError(reply, error)) {
12044
+ return;
12045
+ }
12046
+ throw error;
12047
+ }
12048
+ }
12049
+ });
12050
+ routes.route({
12051
+ method: "PUT",
12052
+ url: "/folders/:id/move",
12053
+ schema: {
12054
+ params: idParamSchema,
12055
+ body: moveFolderBodySchema,
12056
+ response: {
12057
+ 200: folderRecordSchema,
12058
+ 400: errorResponseSchema,
12059
+ 404: errorResponseSchema
12060
+ }
12061
+ },
12062
+ /**
12063
+ * Moves a folder to another parent or collection root.
12064
+ */
12065
+ handler: async (request, reply) => {
12066
+ try {
12067
+ const user = requireAuthenticatedUser(request);
12068
+ const existingFolder = await db.findFolderById(request.params.id);
12069
+ if (!existingFolder) {
12070
+ return reply.code(404).send({ error: "Folder not found" });
12071
+ }
12072
+ if (denyUnlessAllowed(
12073
+ reply,
12074
+ canUseDataApi(user) && canAccessCollection(user, existingFolder.collectionId)
12075
+ )) {
12076
+ return;
12077
+ }
12078
+ const folder = await db.moveFolder(
12079
+ request.params.id,
12080
+ request.body.parentFolderId,
12081
+ request.body.sortOrder,
10719
12082
  user.id
10720
12083
  );
10721
12084
  return reply.send(serializeFolder(folder));
@@ -10755,7 +12118,12 @@ async function registerFolderRoutes(app, db) {
10755
12118
  )) {
10756
12119
  return;
10757
12120
  }
10758
- const folder = await db.renameFolder(request.params.id, request.body.name, user.id);
12121
+ const folder = await db.renameFolder(
12122
+ request.params.id,
12123
+ request.body.name,
12124
+ user.id,
12125
+ request.body.marker
12126
+ );
10759
12127
  return reply.send(serializeFolder(folder));
10760
12128
  } catch (error) {
10761
12129
  if (handleDbError(reply, error)) {
@@ -10826,6 +12194,7 @@ async function registerFolderRoutes(app, db) {
10826
12194
  }
10827
12195
  await db.reorderFolders(
10828
12196
  request.params.collectionId,
12197
+ request.body.parentFolderId,
10829
12198
  request.body.orderedFolderIds,
10830
12199
  user.id
10831
12200
  );
@@ -10840,6 +12209,244 @@ async function registerFolderRoutes(app, db) {
10840
12209
  });
10841
12210
  }
10842
12211
 
12212
+ // src/server/routes/documents.ts
12213
+ async function registerDocumentRoutes(app, db) {
12214
+ const routes = app.withTypeProvider();
12215
+ routes.route({
12216
+ method: "GET",
12217
+ url: "/collections/:collectionId/documents",
12218
+ schema: {
12219
+ params: collectionIdParamSchema,
12220
+ response: {
12221
+ 200: listDocumentsResponseSchema
12222
+ }
12223
+ },
12224
+ /**
12225
+ * Lists collection documents in a collection.
12226
+ */
12227
+ handler: async (request, reply) => {
12228
+ try {
12229
+ const user = requireAuthenticatedUser(request);
12230
+ if (denyUnlessAllowed(
12231
+ reply,
12232
+ canUseDataApi(user) && canAccessCollection(user, request.params.collectionId)
12233
+ )) {
12234
+ return;
12235
+ }
12236
+ const documents = await db.listDocuments(request.params.collectionId);
12237
+ return reply.send({
12238
+ documents: documents.map((document) => serializeDocument(document))
12239
+ });
12240
+ } catch (error) {
12241
+ if (handleDbError(reply, error)) {
12242
+ return;
12243
+ }
12244
+ throw error;
12245
+ }
12246
+ }
12247
+ });
12248
+ routes.route({
12249
+ method: "POST",
12250
+ url: "/collections/:collectionId/documents",
12251
+ schema: {
12252
+ params: collectionIdParamSchema,
12253
+ body: saveDocumentBodySchema,
12254
+ response: {
12255
+ 200: documentRecordSchema,
12256
+ 400: errorResponseSchema,
12257
+ 404: errorResponseSchema
12258
+ }
12259
+ },
12260
+ /**
12261
+ * Creates a new collection document in a collection.
12262
+ */
12263
+ handler: async (request, reply) => {
12264
+ try {
12265
+ const user = requireAuthenticatedUser(request);
12266
+ if (denyUnlessAllowed(
12267
+ reply,
12268
+ canUseDataApi(user) && canAccessCollection(user, request.params.collectionId)
12269
+ )) {
12270
+ return;
12271
+ }
12272
+ const document = await db.saveDocument(
12273
+ {
12274
+ collectionId: request.params.collectionId,
12275
+ name: request.body.name,
12276
+ content: request.body.content,
12277
+ folderId: request.body.folderId ?? null,
12278
+ marker: request.body.marker
12279
+ },
12280
+ user.id
12281
+ );
12282
+ return reply.send(serializeDocument(document));
12283
+ } catch (error) {
12284
+ if (handleDbError(reply, error)) {
12285
+ return;
12286
+ }
12287
+ throw error;
12288
+ }
12289
+ }
12290
+ });
12291
+ routes.route({
12292
+ method: "PUT",
12293
+ url: "/documents/:id",
12294
+ schema: {
12295
+ params: idParamSchema,
12296
+ body: updateSaveDocumentBodySchema,
12297
+ response: {
12298
+ 200: documentRecordSchema,
12299
+ 400: errorResponseSchema,
12300
+ 404: errorResponseSchema
12301
+ }
12302
+ },
12303
+ /**
12304
+ * Updates an existing collection document by id.
12305
+ */
12306
+ handler: async (request, reply) => {
12307
+ try {
12308
+ const user = requireAuthenticatedUser(request);
12309
+ if (denyUnlessAllowed(
12310
+ reply,
12311
+ canUseDataApi(user) && canAccessCollection(user, request.body.collectionId)
12312
+ )) {
12313
+ return;
12314
+ }
12315
+ const document = await db.saveDocument(
12316
+ {
12317
+ id: request.params.id,
12318
+ collectionId: request.body.collectionId,
12319
+ name: request.body.name,
12320
+ content: request.body.content,
12321
+ folderId: request.body.folderId ?? null,
12322
+ marker: request.body.marker
12323
+ },
12324
+ user.id
12325
+ );
12326
+ return reply.send(serializeDocument(document));
12327
+ } catch (error) {
12328
+ if (handleDbError(reply, error)) {
12329
+ return;
12330
+ }
12331
+ throw error;
12332
+ }
12333
+ }
12334
+ });
12335
+ routes.route({
12336
+ method: "DELETE",
12337
+ url: "/documents/:id",
12338
+ schema: {
12339
+ params: idParamSchema,
12340
+ response: {
12341
+ 204: emptyResponseSchema,
12342
+ 404: errorResponseSchema
12343
+ }
12344
+ },
12345
+ /**
12346
+ * Deletes a collection document by id.
12347
+ */
12348
+ handler: async (request, reply) => {
12349
+ try {
12350
+ const user = requireAuthenticatedUser(request);
12351
+ const existingDocument = await db.findDocumentById(request.params.id);
12352
+ if (!existingDocument) {
12353
+ return reply.code(404).send({ error: "Document not found" });
12354
+ }
12355
+ if (denyUnlessAllowed(reply, canDeleteDocument(user, existingDocument))) {
12356
+ return;
12357
+ }
12358
+ await db.deleteDocument(request.params.id, user.id);
12359
+ return reply.code(204).send(null);
12360
+ } catch (error) {
12361
+ if (handleDbError(reply, error)) {
12362
+ return;
12363
+ }
12364
+ throw error;
12365
+ }
12366
+ }
12367
+ });
12368
+ routes.route({
12369
+ method: "PUT",
12370
+ url: "/collections/:collectionId/documents/reorder",
12371
+ schema: {
12372
+ params: collectionIdParamSchema,
12373
+ body: reorderDocumentsBodySchema,
12374
+ response: {
12375
+ 204: emptyResponseSchema,
12376
+ 404: errorResponseSchema
12377
+ }
12378
+ },
12379
+ /**
12380
+ * Reorders collection documents within a folder or collection root.
12381
+ */
12382
+ handler: async (request, reply) => {
12383
+ try {
12384
+ const user = requireAuthenticatedUser(request);
12385
+ if (denyUnlessAllowed(
12386
+ reply,
12387
+ canUseDataApi(user) && canAccessCollection(user, request.params.collectionId)
12388
+ )) {
12389
+ return;
12390
+ }
12391
+ await db.reorderDocuments(
12392
+ request.params.collectionId,
12393
+ request.body.folderId,
12394
+ request.body.orderedDocumentIds,
12395
+ user.id
12396
+ );
12397
+ return reply.code(204).send(null);
12398
+ } catch (error) {
12399
+ if (handleDbError(reply, error)) {
12400
+ return;
12401
+ }
12402
+ throw error;
12403
+ }
12404
+ }
12405
+ });
12406
+ routes.route({
12407
+ method: "PUT",
12408
+ url: "/documents/:id/move",
12409
+ schema: {
12410
+ params: idParamSchema,
12411
+ body: moveDocumentBodySchema,
12412
+ response: {
12413
+ 204: emptyResponseSchema,
12414
+ 404: errorResponseSchema
12415
+ }
12416
+ },
12417
+ /**
12418
+ * Moves a collection document to another folder or collection root index.
12419
+ */
12420
+ handler: async (request, reply) => {
12421
+ try {
12422
+ const user = requireAuthenticatedUser(request);
12423
+ const existingDocument = await db.findDocumentById(request.params.id);
12424
+ if (!existingDocument) {
12425
+ return reply.code(404).send({ error: "Document not found" });
12426
+ }
12427
+ if (denyUnlessAllowed(
12428
+ reply,
12429
+ canUseDataApi(user) && canAccessCollection(user, existingDocument.collectionId)
12430
+ )) {
12431
+ return;
12432
+ }
12433
+ await db.moveDocument(
12434
+ request.params.id,
12435
+ request.body.folderId,
12436
+ request.body.index,
12437
+ user.id
12438
+ );
12439
+ return reply.code(204).send(null);
12440
+ } catch (error) {
12441
+ if (handleDbError(reply, error)) {
12442
+ return;
12443
+ }
12444
+ throw error;
12445
+ }
12446
+ }
12447
+ });
12448
+ }
12449
+
10843
12450
  // src/server/routes/health.ts
10844
12451
  import { z as z11 } from "zod/v4";
10845
12452
  var healthResponseSchema = z11.object({
@@ -11286,7 +12893,8 @@ async function registerRequestRoutes(app, db) {
11286
12893
  preRequestScript: request.body.preRequestScript,
11287
12894
  postRequestScript: request.body.postRequestScript,
11288
12895
  comment: request.body.comment,
11289
- folderId: request.body.folderId ?? null
12896
+ folderId: request.body.folderId ?? null,
12897
+ marker: request.body.marker
11290
12898
  },
11291
12899
  user.id
11292
12900
  );
@@ -11338,7 +12946,8 @@ async function registerRequestRoutes(app, db) {
11338
12946
  preRequestScript: request.body.preRequestScript,
11339
12947
  postRequestScript: request.body.postRequestScript,
11340
12948
  comment: request.body.comment,
11341
- folderId: request.body.folderId ?? null
12949
+ folderId: request.body.folderId ?? null,
12950
+ marker: request.body.marker
11342
12951
  },
11343
12952
  user.id
11344
12953
  );
@@ -12426,6 +14035,7 @@ async function registerProtectedRoutes(app, options) {
12426
14035
  await registerSnippetRoutes(app, options.db);
12427
14036
  await registerFolderRoutes(app, options.db);
12428
14037
  await registerRequestRoutes(app, options.db);
14038
+ await registerDocumentRoutes(app, options.db);
12429
14039
  await registerRunResultRoutes(app, options.db);
12430
14040
  await registerLlmRoutes(app, {
12431
14041
  db: options.db,