@rapidmx/mapi-plugin 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
  4. package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
  5. package/dist/lib/BaseMapiNspiRoute.js +114 -0
  6. package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
  7. package/dist/lib/MapiSessionManager.js +475 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RegexPatternUtils.js +30 -0
  10. package/dist/lib/RegexPatternUtils.js.map +1 -0
  11. package/dist/lib/RopDispatcher.js +145 -0
  12. package/dist/lib/RopDispatcher.js.map +1 -0
  13. package/dist/lib/codec/AppointmentRecurrence.js +315 -0
  14. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  15. package/dist/lib/codec/BufferCursor.js +242 -0
  16. package/dist/lib/codec/BufferCursor.js.map +1 -0
  17. package/dist/lib/codec/GlobalObjectId.js +98 -0
  18. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  19. package/dist/lib/codec/MapiGuid.js +41 -0
  20. package/dist/lib/codec/MapiGuid.js.map +1 -0
  21. package/dist/lib/codec/MapiTimeZone.js +126 -0
  22. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  23. package/dist/lib/codec/PropertyValue.js +240 -0
  24. package/dist/lib/codec/PropertyValue.js.map +1 -0
  25. package/dist/lib/codec/RopBuffer.js +38 -0
  26. package/dist/lib/codec/RopBuffer.js.map +1 -0
  27. package/dist/lib/codec/TypedString.js +54 -0
  28. package/dist/lib/codec/TypedString.js.map +1 -0
  29. package/dist/lib/index.js +99 -0
  30. package/dist/lib/index.js.map +1 -0
  31. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
  32. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  34. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  35. package/dist/lib/mongo/index.js +7 -0
  36. package/dist/lib/mongo/index.js.map +1 -0
  37. package/dist/lib/mongo.js +6 -0
  38. package/dist/lib/mongo.js.map +1 -0
  39. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  40. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  41. package/dist/lib/nspi/NspiCodec.js +106 -0
  42. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  43. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  44. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  45. package/dist/lib/rop/AddressList.js +114 -0
  46. package/dist/lib/rop/AddressList.js.map +1 -0
  47. package/dist/lib/rop/CalendarEventTarget.js +30 -0
  48. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  49. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  50. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  51. package/dist/lib/rop/ContactTarget.js +19 -0
  52. package/dist/lib/rop/ContactTarget.js.map +1 -0
  53. package/dist/lib/rop/ContentsTable.js +60 -0
  54. package/dist/lib/rop/ContentsTable.js.map +1 -0
  55. package/dist/lib/rop/ExecuteBudget.js +82 -0
  56. package/dist/lib/rop/ExecuteBudget.js.map +1 -0
  57. package/dist/lib/rop/FastTransferStream.js +195 -0
  58. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  59. package/dist/lib/rop/FolderTarget.js +118 -0
  60. package/dist/lib/rop/FolderTarget.js.map +1 -0
  61. package/dist/lib/rop/HandleDataCache.js +412 -0
  62. package/dist/lib/rop/HandleDataCache.js.map +1 -0
  63. package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
  64. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  65. package/dist/lib/rop/MessageBodyStream.js +68 -0
  66. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  67. package/dist/lib/rop/MessageTarget.js +57 -0
  68. package/dist/lib/rop/MessageTarget.js.map +1 -0
  69. package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
  70. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  71. package/dist/lib/rop/PropertyResolvers.js +353 -0
  72. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  73. package/dist/lib/rop/RepoPaging.js +54 -0
  74. package/dist/lib/rop/RepoPaging.js.map +1 -0
  75. package/dist/lib/rop/RestapiRules.js +52 -0
  76. package/dist/lib/rop/RestapiRules.js.map +1 -0
  77. package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
  78. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  79. package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
  80. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  81. package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
  82. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  84. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  85. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
  86. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  87. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
  88. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  89. package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
  90. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  91. package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
  92. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  93. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
  94. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  95. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  96. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  97. package/dist/lib/rop/RopHandler.js +6 -0
  98. package/dist/lib/rop/RopHandler.js.map +1 -0
  99. package/dist/lib/rop/RopLogonHandler.js +135 -0
  100. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  101. package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
  102. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  103. package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
  104. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  105. package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
  106. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  107. package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
  108. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  109. package/dist/lib/rop/RopReadStreamHandler.js +73 -0
  110. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  111. package/dist/lib/rop/RopReleaseHandler.js +23 -0
  112. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  113. package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
  114. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  115. package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
  116. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  117. package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
  118. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  119. package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
  120. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  121. package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
  122. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  123. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  124. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  125. package/dist/lib/rop/TaskTarget.js +15 -0
  126. package/dist/lib/rop/TaskTarget.js.map +1 -0
  127. package/dist/lib/rop/TransportSend.js +16 -0
  128. package/dist/lib/rop/TransportSend.js.map +1 -0
  129. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
  130. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  131. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  132. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  133. package/dist/lib/sql/index.js +7 -0
  134. package/dist/lib/sql/index.js.map +1 -0
  135. package/dist/lib/sql.js +6 -0
  136. package/dist/lib/sql.js.map +1 -0
  137. package/dist/types/BaseMapiEmsmdbRoute.d.ts +139 -0
  138. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  139. package/dist/types/MapiSessionManager.d.ts +285 -0
  140. package/dist/types/RegexPatternUtils.d.ts +14 -0
  141. package/dist/types/RopDispatcher.d.ts +53 -0
  142. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  143. package/dist/types/codec/BufferCursor.d.ts +92 -0
  144. package/dist/types/codec/GlobalObjectId.d.ts +15 -0
  145. package/dist/types/codec/MapiGuid.d.ts +6 -0
  146. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  147. package/dist/types/codec/PropertyValue.d.ts +87 -0
  148. package/dist/types/codec/RopBuffer.d.ts +32 -0
  149. package/dist/types/codec/TypedString.d.ts +17 -0
  150. package/dist/types/index.d.ts +94 -0
  151. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
  152. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  153. package/dist/types/mongo/index.d.ts +2 -0
  154. package/dist/types/mongo.d.ts +1 -0
  155. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  156. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  157. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
  158. package/dist/types/rop/AddressList.d.ts +48 -0
  159. package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
  160. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  161. package/dist/types/rop/ContactTarget.d.ts +29 -0
  162. package/dist/types/rop/ContentsTable.d.ts +16 -0
  163. package/dist/types/rop/ExecuteBudget.d.ts +48 -0
  164. package/dist/types/rop/FastTransferStream.d.ts +57 -0
  165. package/dist/types/rop/FolderTarget.d.ts +64 -0
  166. package/dist/types/rop/HandleDataCache.d.ts +145 -0
  167. package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
  168. package/dist/types/rop/MessageBodyStream.d.ts +35 -0
  169. package/dist/types/rop/MessageTarget.d.ts +45 -0
  170. package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
  171. package/dist/types/rop/PropertyResolvers.d.ts +86 -0
  172. package/dist/types/rop/RepoPaging.d.ts +30 -0
  173. package/dist/types/rop/RestapiRules.d.ts +32 -0
  174. package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
  175. package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
  176. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
  177. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
  178. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
  179. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
  180. package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
  181. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
  182. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
  183. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  184. package/dist/types/rop/RopHandler.d.ts +106 -0
  185. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  186. package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
  187. package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
  188. package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
  189. package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
  190. package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
  191. package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
  192. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
  193. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  194. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  195. package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
  196. package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
  197. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  198. package/dist/types/rop/TaskTarget.d.ts +24 -0
  199. package/dist/types/rop/TransportSend.d.ts +10 -0
  200. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
  201. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  202. package/dist/types/sql/index.d.ts +2 -0
  203. package/dist/types/sql.d.ts +1 -0
  204. package/package.json +104 -0
@@ -0,0 +1,60 @@
1
+ import { FolderType } from "@rapidmx/restapi";
2
+ import { CALENDAR_EVENT_SORT } from "./CalendarEventTarget.js";
3
+ import { CONTACT_SORT } from "./ContactTarget.js";
4
+ import { MESSAGE_SORT } from "./MessageTarget.js";
5
+ import { findWindow } from "./RepoPaging.js";
6
+ import { TASK_SORT } from "./TaskTarget.js";
7
+ const SORT_BY_KIND = {
8
+ message: MESSAGE_SORT,
9
+ calendarEvent: CALENDAR_EVENT_SORT,
10
+ contact: CONTACT_SORT,
11
+ task: TASK_SORT,
12
+ };
13
+ /** Which repo a folder's items live in, decided by the folder's own `type`: a `Contact`/`Task`/`CalendarEvent` is
14
+ * its own entity, never a `Message` row. */
15
+ export async function resolveContentsKind(folderUid, context) {
16
+ context.budget?.chargeQueries();
17
+ const folder = await context.folderRepo.findOne(folderUid, { ignoreACL: true });
18
+ switch (folder?.type) {
19
+ case FolderType.CALENDAR:
20
+ return "calendarEvent";
21
+ case FolderType.CONTACTS:
22
+ return "contact";
23
+ case FolderType.TASKS:
24
+ return "task";
25
+ default:
26
+ return "message";
27
+ }
28
+ }
29
+ function repoFor(kind, context) {
30
+ switch (kind) {
31
+ case "calendarEvent":
32
+ return context.calendarEventRepo;
33
+ case "contact":
34
+ return context.contactRepo;
35
+ case "task":
36
+ return context.taskRepo;
37
+ default:
38
+ return context.messageRepo;
39
+ }
40
+ }
41
+ /**
42
+ * The `"<kind>:<uid>"` row targets at positions `[start, start + count)` of a contents table (a `"folder:<uid>"`
43
+ * table with `contentsKind` set), read straight from the database in a fixed, stable order. A context without the
44
+ * matching optional repo yields no rows.
45
+ *
46
+ * Reading a window per `RopQueryRows` instead of snapshotting every row at `RopGetContentsTable` time keeps session
47
+ * state small and makes no folder too large to page through. The trade-off is that a message added or removed
48
+ * between two `RopQueryRows` calls can shift the rows that follow.
49
+ */
50
+ export async function resolveContentsWindow(context, table, start, count) {
51
+ const kind = table.contentsKind;
52
+ const repo = repoFor(kind, context);
53
+ if (!repo) {
54
+ return [];
55
+ }
56
+ const folderUid = table.entityUid.slice("folder:".length);
57
+ const rows = await findWindow(repo, { folderUid, mailboxUid: context.mailboxUid }, SORT_BY_KIND[kind], start, count, context.budget);
58
+ return rows.map((row) => `${kind}:${row.uid}`);
59
+ }
60
+ //# sourceMappingURL=ContentsTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentsTable.js","sourceRoot":"","sources":["../../../src/rop/ContentsTable.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAe,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,YAAY,GAAmC;IACjD,OAAO,EAAE,YAAY;IACrB,aAAa,EAAE,mBAAmB;IAClC,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,SAAS;CAClB,CAAC;AAEF;4CAC4C;AAC5C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAiB,EAAE,OAAmB;IAC5E,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC;IAChC,MAAM,MAAM,GAAuB,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpG,QAAQ,MAAM,EAAE,IAAI,EAAE,CAAC;QACnB,KAAK,UAAU,CAAC,QAAQ;YACpB,OAAO,eAAe,CAAC;QAC3B,KAAK,UAAU,CAAC,QAAQ;YACpB,OAAO,SAAS,CAAC;QACrB,KAAK,UAAU,CAAC,KAAK;YACjB,OAAO,MAAM,CAAC;QAClB;YACI,OAAO,SAAS,CAAC;IACzB,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,IAAkB,EAAE,OAAmB;IACpD,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,eAAe;YAChB,OAAO,OAAO,CAAC,iBAAiB,CAAC;QACrC,KAAK,SAAS;YACV,OAAO,OAAO,CAAC,WAAW,CAAC;QAC/B,KAAK,MAAM;YACP,OAAO,OAAO,CAAC,QAAQ,CAAC;QAC5B;YACI,OAAO,OAAO,CAAC,WAAW,CAAC;IACnC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAmB,EAAE,KAAuB,EAAE,KAAa,EAAE,KAAa;IAClH,MAAM,IAAI,GAAiB,KAAK,CAAC,YAAa,CAAC;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAsB,MAAM,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACxJ,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,82 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /** The most rows (items or folders) whose properties one `Execute` resolves, across all of its ROPs. */
6
+ export const MAX_ROWS_RESOLVED_PER_EXECUTE = 20000;
7
+ /** The most bytes one `Execute` builds or parses (FastTransfer streams and message bodies), across all of its ROPs. */
8
+ export const MAX_BYTES_BUILT_PER_EXECUTE = 64 * 1024 * 1024;
9
+ /** The most database queries (paged reads, table lookups, deletes) one `Execute` runs through the budgeted paths. */
10
+ export const MAX_QUERIES_PER_EXECUTE = 20000;
11
+ /** The most rows one `Execute` reads back from paged queries (folder lists, folder walks, item lists). */
12
+ export const MAX_ROWS_FETCHED_PER_EXECUTE = 100000;
13
+ /** The most `RopSubmitMessage`s one `Execute` runs. Each one can relay mail, so a request can't repeat it 1024 times. */
14
+ export const MAX_SUBMITS_PER_EXECUTE = 16;
15
+ /** Thrown when an `Execute` has used up its `ExecuteBudget`. The ROP that hit it fails; see `RopDispatcher`. */
16
+ export class WorkBudgetExceededError extends Error {
17
+ constructor(what) {
18
+ super(`Execute: the per-request ${what} budget is exhausted.`);
19
+ this.name = "WorkBudgetExceededError";
20
+ }
21
+ }
22
+ /**
23
+ * The work one `Execute` may do. Limits on ROP count and per-ROP sizes alone still let one request chain many
24
+ * expensive ROPs (a dozen 10000-row FastTransfer dumps, the same large body parsed by several streams, or a thousand
25
+ * hierarchy tables or folder walks), so rows resolved, bytes built, queries run, rows fetched and submits are counted
26
+ * across the whole request. Every charge happens before the work it pays for. Also dedupes message body parses within
27
+ * the request (`bodies`).
28
+ */
29
+ export class ExecuteBudget {
30
+ constructor(maxRows = MAX_ROWS_RESOLVED_PER_EXECUTE, maxBytes = MAX_BYTES_BUILT_PER_EXECUTE, limits = {}) {
31
+ /** Message uid -> its decoded body, so opening the same body twice in one request parses it once. */
32
+ this.bodies = new Map();
33
+ this.rowsRemaining = maxRows;
34
+ this.bytesRemaining = maxBytes;
35
+ this.queriesRemaining = limits.maxQueries ?? MAX_QUERIES_PER_EXECUTE;
36
+ this.fetchedRowsRemaining = limits.maxFetchedRows ?? MAX_ROWS_FETCHED_PER_EXECUTE;
37
+ this.submitsRemaining = limits.maxSubmits ?? MAX_SUBMITS_PER_EXECUTE;
38
+ }
39
+ /** Uses `count` rows of the budget, throwing `WorkBudgetExceededError` once it is exhausted. */
40
+ chargeRows(count = 1) {
41
+ this.rowsRemaining -= count;
42
+ if (this.rowsRemaining < 0) {
43
+ throw new WorkBudgetExceededError("row");
44
+ }
45
+ }
46
+ /** Uses `count` bytes of the budget, throwing `WorkBudgetExceededError` once it is exhausted. */
47
+ chargeBytes(count) {
48
+ this.bytesRemaining -= count;
49
+ if (this.bytesRemaining < 0) {
50
+ throw new WorkBudgetExceededError("byte");
51
+ }
52
+ }
53
+ /** Throws `WorkBudgetExceededError` when no bytes are left, so work that can't be sized up front (fetching a blob)
54
+ * isn't started at all. */
55
+ assertBytesLeft() {
56
+ if (this.bytesRemaining <= 0) {
57
+ throw new WorkBudgetExceededError("byte");
58
+ }
59
+ }
60
+ /** Uses `count` queries of the budget. Called before running them. */
61
+ chargeQueries(count = 1) {
62
+ this.queriesRemaining -= count;
63
+ if (this.queriesRemaining < 0) {
64
+ throw new WorkBudgetExceededError("query");
65
+ }
66
+ }
67
+ /** Uses `count` fetched rows of the budget. */
68
+ chargeFetchedRows(count) {
69
+ this.fetchedRowsRemaining -= count;
70
+ if (this.fetchedRowsRemaining < 0) {
71
+ throw new WorkBudgetExceededError("fetched row");
72
+ }
73
+ }
74
+ /** Uses one submit of the budget. */
75
+ chargeSubmit() {
76
+ this.submitsRemaining -= 1;
77
+ if (this.submitsRemaining < 0) {
78
+ throw new WorkBudgetExceededError("submit");
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=ExecuteBudget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExecuteBudget.js","sourceRoot":"","sources":["../../../src/rop/ExecuteBudget.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAE/E,wGAAwG;AACxG,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAEnD,uHAAuH;AACvH,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5D,qHAAqH;AACrH,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAE7C,0GAA0G;AAC1G,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC;AAEnD,yHAAyH;AACzH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,gHAAgH;AAChH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAC9C,YAAmB,IAAY;QAC3B,KAAK,CAAC,4BAA4B,IAAI,uBAAuB,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IAC1C,CAAC;CACJ;AAED;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IAStB,YACI,UAAkB,6BAA6B,EAC/C,WAAmB,2BAA2B,EAC9C,SAAgF,EAAE;QANtF,qGAAqG;QACrF,WAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;QAOxD,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;QAC/B,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,UAAU,IAAI,uBAAuB,CAAC;QACrE,IAAI,CAAC,oBAAoB,GAAG,MAAM,CAAC,cAAc,IAAI,4BAA4B,CAAC;QAClF,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,UAAU,IAAI,uBAAuB,CAAC;IACzE,CAAC;IAED,gGAAgG;IACzF,UAAU,CAAC,KAAK,GAAG,CAAC;QACvB,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC;QAC5B,IAAI,IAAI,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAED,iGAAiG;IAC1F,WAAW,CAAC,KAAa;QAC5B,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC;QAC7B,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IAED;+BAC2B;IACpB,eAAe;QAClB,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IAED,sEAAsE;IAC/D,aAAa,CAAC,KAAK,GAAG,CAAC;QAC1B,IAAI,CAAC,gBAAgB,IAAI,KAAK,CAAC;QAC/B,IAAI,IAAI,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED,+CAA+C;IACxC,iBAAiB,CAAC,KAAa;QAClC,IAAI,CAAC,oBAAoB,IAAI,KAAK,CAAC;QACnC,IAAI,IAAI,CAAC,oBAAoB,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,uBAAuB,CAAC,aAAa,CAAC,CAAC;QACrD,CAAC;IACL,CAAC;IAED,qCAAqC;IAC9B,YAAY;QACf,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;IACL,CAAC;CACJ"}
@@ -0,0 +1,195 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { FolderType } from "@rapidmx/restapi";
6
+ import { BufferWriter } from "../codec/BufferCursor.js";
7
+ import { PropertyType, writeTaggedPropertyValue } from "../codec/PropertyValue.js";
8
+ import { assignHandle, releaseHandle } from "../MapiSessionManager.js";
9
+ import { resolveFolderCalendarEvents } from "./CalendarEventTarget.js";
10
+ import { handleDataKey, handleDataOwners } from "./HandleDataCache.js";
11
+ import { resolveFolderMessages } from "./MessageTarget.js";
12
+ import { resolvePropertyValues } from "./PropertyResolvers.js";
13
+ import { handleDataStoreOf } from "./RopHandler.js";
14
+ /**
15
+ * Builds the `FastTransfer` binary stream (`[MS-OXCFXICS]` §2.2.4) `RopFastTransferSourceCopyTo`/
16
+ * `CopyProperties` hand off to `RopFastTransferSourceGetBuffer` for paging out, per this pragmatic subset's own
17
+ * "ICS" scope (see the architecture plan's "Incremental sync" row): a **full, non-differential** dump of the
18
+ * target's current live state, built eagerly and stored whole on the output handle - not the real, much larger
19
+ * `contentsSync`/`hierarchySync` grammar (`IncrSyncChg`/`IncrSyncDel`/`IncrSyncStateBegin` markers), which
20
+ * requires a persisted per-device ICS state object (an `IDSET` baseline) this pragmatic subset doesn't
21
+ * implement. A real client that asks for an incremental sync here simply gets a full re-list every time and
22
+ * reconciles by diffing against its own local cache - spec-legitimate client behavior, just more state
23
+ * re-fetched per sync than a byte-perfect ICS server would send, exactly the tradeoff the plan accepts.
24
+ *
25
+ * Only the plain `PidTag*` properties this pragmatic subset already resolves via `PropertyResolvers.ts` are
26
+ * ever included - a Calendar item's own named properties (start/end/location/recurrence/...) are **not**
27
+ * included in the stream (only `PidTagSubject`), a deliberate scope cut: a client wanting those already has to
28
+ * `RopOpenMessage`+`RopGetPropertiesSpecific` the item directly (the same "just re-fetches more state" tradeoff
29
+ * applied one level deeper). No recipients/attachments/subfolders are emitted either - `messageChildren` is
30
+ * always empty and `Level` (whether to recurse into subfolders) is not honored, matching this pragmatic
31
+ * subset's existing "no `RopModifyRecipients`" and folder-hierarchy-is-flat-per-call precedents elsewhere.
32
+ *
33
+ * @author Jean-Philippe Steinmetz
34
+ */
35
+ /** FastTransfer markers (`[MS-OXCFXICS]` §2.2.4.1.4, confirmed this session) - only the four this pragmatic
36
+ * subset's flat, non-differential dump ever needs. */
37
+ const MARKER_START_TOP_FLD = 0x40090003;
38
+ const MARKER_END_FOLDER = 0x400b0003;
39
+ const MARKER_START_MESSAGE = 0x400c0003;
40
+ const MARKER_END_MESSAGE = 0x400d0003;
41
+ /** The well-known columns this pragmatic subset includes by default when the caller (`RopFastTransferSourceCopyTo`)
42
+ * didn't supply an explicit include list - the same small "what a real client needs" sets `PropertyResolvers.ts`
43
+ * already defines its own defaults around. */
44
+ const DEFAULT_FOLDER_COLUMNS = [
45
+ { propertyId: 0x3001, propertyType: PropertyType.PtypString }, // PidTagDisplayName
46
+ { propertyId: 0x3602, propertyType: PropertyType.PtypInteger32 }, // PidTagContentCount
47
+ { propertyId: 0x3603, propertyType: PropertyType.PtypInteger32 }, // PidTagContentUnreadCount
48
+ ];
49
+ const DEFAULT_MESSAGE_COLUMNS = [
50
+ { propertyId: 0x0037, propertyType: PropertyType.PtypString }, // PidTagSubject
51
+ { propertyId: 0x0e07, propertyType: PropertyType.PtypInteger32 }, // PidTagMessageFlags
52
+ { propertyId: 0x0e1b, propertyType: PropertyType.PtypBoolean }, // PidTagHasAttachments
53
+ { propertyId: 0x0e06, propertyType: PropertyType.PtypTime }, // PidTagMessageDeliveryTime
54
+ ];
55
+ /** A calendar item's named properties are deliberately excluded from the stream (see class doc comment) -
56
+ * `PidTagSubject` is the only column emitted for one. */
57
+ const DEFAULT_CALENDAR_COLUMNS = [{ propertyId: 0x0037, propertyType: PropertyType.PtypString }];
58
+ async function writePropList(writer, target, columns, context) {
59
+ const values = await resolvePropertyValues(target, columns, context);
60
+ columns.forEach((column, index) => writeTaggedPropertyValue(writer, { propertyId: column.propertyId, propertyType: column.propertyType, value: values[index] }));
61
+ }
62
+ function filterExcluded(columns, excludePropertyIds) {
63
+ return columns.filter((column) => !excludePropertyIds.has(column.propertyId));
64
+ }
65
+ /** The largest FastTransfer stream built for one handle; a larger source fails with `MAPI_E_TOO_BIG`. */
66
+ export const MAX_FAST_TRANSFER_BYTES = 32 * 1024 * 1024;
67
+ /** How long a built stream is kept for paging out. A client pages a stream out right after opening it; one that
68
+ * leaves it longer than this has to open the transfer again. */
69
+ export const FAST_TRANSFER_TTL_SECONDS = 10 * 60;
70
+ /** Thrown while building a stream that has grown past its byte limit. */
71
+ export class FastTransferTooBigError extends Error {
72
+ constructor() {
73
+ super("FastTransfer: the stream is larger than MAX_FAST_TRANSFER_BYTES.");
74
+ this.name = "FastTransferTooBigError";
75
+ }
76
+ }
77
+ /**
78
+ * Builds the complete FastTransfer stream for `handle` (a `"folder"` or `"message"` Server object). `columns`,
79
+ * when given (a `RopFastTransferSourceCopyProperties` explicit include list), replaces every default column set
80
+ * uniformly at both folder- and message-level; `excludePropertyIds` (a `RopFastTransferSourceCopyTo` exclude
81
+ * list) is only ever applied to this pragmatic subset's own default columns, per the same reasoning.
82
+ *
83
+ * The size is checked after every item, so a huge folder stops being built as soon as it passes `maxBytes`
84
+ * (`FastTransferTooBigError`) instead of being fully built first. Every item resolved and the finished stream's bytes
85
+ * count against the request's `ExecuteBudget` when there is one.
86
+ */
87
+ export async function buildFastTransferStream(handle, context, options = {}) {
88
+ const writer = new BufferWriter();
89
+ const exclude = options.excludePropertyIds ?? new Set();
90
+ const maxBytes = options.maxBytes ?? MAX_FAST_TRANSFER_BYTES;
91
+ const checkSize = () => {
92
+ if (writer.length > maxBytes) {
93
+ throw new FastTransferTooBigError();
94
+ }
95
+ };
96
+ if (handle.type === "folder") {
97
+ const folderColumns = options.columns ?? filterExcluded(DEFAULT_FOLDER_COLUMNS, exclude);
98
+ writer.writeUInt32LE(MARKER_START_TOP_FLD);
99
+ await writePropList(writer, handle.entityUid, folderColumns, context);
100
+ if (handle.entityUid.startsWith("folder:")) {
101
+ const folderUid = handle.entityUid.slice("folder:".length);
102
+ const folder = await context.folderRepo.findOne(folderUid, { ignoreACL: true });
103
+ const isCalendar = folder?.type === FolderType.CALENDAR;
104
+ const rows = isCalendar
105
+ ? await resolveFolderCalendarEvents(folderUid, context.calendarEventRepo, context.budget)
106
+ : await resolveFolderMessages(folderUid, context.messageRepo, context.budget);
107
+ const messageColumns = options.columns ?? filterExcluded(isCalendar ? DEFAULT_CALENDAR_COLUMNS : DEFAULT_MESSAGE_COLUMNS, exclude);
108
+ for (const row of rows) {
109
+ writer.writeUInt32LE(MARKER_START_MESSAGE);
110
+ await writePropList(writer, row, messageColumns, context);
111
+ writer.writeUInt32LE(MARKER_END_MESSAGE);
112
+ checkSize();
113
+ }
114
+ }
115
+ writer.writeUInt32LE(MARKER_END_FOLDER);
116
+ }
117
+ else {
118
+ const isCalendar = handle.entityUid.startsWith("calendarEvent:");
119
+ const columns = options.columns ?? filterExcluded(isCalendar ? DEFAULT_CALENDAR_COLUMNS : DEFAULT_MESSAGE_COLUMNS, exclude);
120
+ await writePropList(writer, handle.entityUid, columns, context);
121
+ }
122
+ checkSize();
123
+ context.budget?.chargeBytes(writer.length);
124
+ return writer.toBuffer();
125
+ }
126
+ /** Builds a `"fastTransfer"` handle's stream from what the handle records about its source, or `undefined` when it
127
+ * is larger than `MAX_FAST_TRANSFER_BYTES`. */
128
+ async function rebuildFastTransferStream(transfer, context) {
129
+ try {
130
+ return await buildFastTransferStream({ type: transfer.transferSourceType, entityUid: transfer.entityUid }, context, {
131
+ columns: transfer.transferColumns,
132
+ excludePropertyIds: new Set(transfer.transferExcludeIds ?? []),
133
+ });
134
+ }
135
+ catch (err) {
136
+ if (err instanceof FastTransferTooBigError) {
137
+ return undefined;
138
+ }
139
+ throw err;
140
+ }
141
+ }
142
+ /**
143
+ * The built stream for the `"fastTransfer"` handle at `handleIndex`, from the shared `HandleDataStore` (Redis when
144
+ * configured, so any replica can page it out).
145
+ *
146
+ * When the stream is gone (expired, evicted, or built by a replica without shared storage), it is rebuilt only if
147
+ * nothing has been paged out yet. Part-way through, a rebuild could differ from what the client already has (items
148
+ * added or changed since), and continuing at the old offset would hand it bytes from a different stream, so that
149
+ * case reports `"lost"` and the client restarts the transfer. A rebuild is held to `MAX_FAST_TRANSFER_BYTES` like
150
+ * the original build, and storing it to the session's and user's handle data quota (`handleDataOwners`); either
151
+ * failing reports `"tooBig"`.
152
+ */
153
+ export async function loadFastTransferBuffer(context, handleIndex, transfer) {
154
+ const store = handleDataStoreOf(context);
155
+ const key = handleDataKey(context.session.uid, handleIndex, transfer.generation);
156
+ const stored = await store.get(key);
157
+ if (stored) {
158
+ return stored;
159
+ }
160
+ if ((transfer.transferPosition ?? 0) > 0) {
161
+ return "lost";
162
+ }
163
+ const buffer = await rebuildFastTransferStream(transfer, context);
164
+ if (!buffer || !(await store.set(key, buffer, FAST_TRANSFER_TTL_SECONDS, handleDataOwners(context.session.uid, context.session.userUid)))) {
165
+ return "tooBig";
166
+ }
167
+ return buffer;
168
+ }
169
+ /**
170
+ * Shared by `RopFastTransferSourceCopyTo`/`CopyProperties`: builds the stream for `source`, and on success stores a
171
+ * `"fastTransfer"` handle at `outputHandleIndex` with the stream in the `HandleDataStore`. Returns `false`, storing
172
+ * nothing, when the stream exceeds `MAX_FAST_TRANSFER_BYTES` or the session's or user's handle data quota
173
+ * (`handleDataOwners`) has no room for it.
174
+ */
175
+ export async function openFastTransferHandle(context, outputHandleIndex, source, options) {
176
+ const transfer = {
177
+ type: "fastTransfer",
178
+ entityUid: source.entityUid,
179
+ transferSourceType: source.type === "folder" ? "folder" : "message",
180
+ transferColumns: options.columns,
181
+ transferExcludeIds: options.excludePropertyIds,
182
+ transferPosition: 0,
183
+ };
184
+ const buffer = await rebuildFastTransferStream(transfer, context);
185
+ if (!buffer) {
186
+ return false;
187
+ }
188
+ assignHandle(context.session, outputHandleIndex, transfer);
189
+ const stored = await handleDataStoreOf(context).set(handleDataKey(context.session.uid, outputHandleIndex, transfer.generation), buffer, FAST_TRANSFER_TTL_SECONDS, handleDataOwners(context.session.uid, context.session.userUid));
190
+ if (!stored) {
191
+ releaseHandle(context.session, outputHandleIndex);
192
+ }
193
+ return stored;
194
+ }
195
+ //# sourceMappingURL=FastTransferStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FastTransferStream.js","sourceRoot":"","sources":["../../../src/rop/FastTransferStream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { findAllCapped, MAX_COLLECTION_ROWS } from "./RepoPaging.js";
2
+ /** Every folder in `mailboxUid` (up to `MAX_COLLECTION_ROWS`), by name. Paged explicitly: a bare `find()` stops at
3
+ * the repo's 100-row default, which silently dropped folders from hierarchy tables in larger mailboxes. Each page is
4
+ * charged to `budget` before it is read. */
5
+ async function findMailboxFolders(mailboxUid, folderRepo, budget) {
6
+ return (await findAllCapped(folderRepo, { mailboxUid }, { name: "ASC", uid: "ASC" }, MAX_COLLECTION_ROWS, budget)).items;
7
+ }
8
+ /**
9
+ * Shared helpers for resolving the `"virtual:<name>"`/`"folder:<uid>"` target strings `RopLogonHandler`
10
+ * assigns to FIDs (see its own doc comment) into real folder data - used by `RopOpenFolderHandler` and
11
+ * `RopGetHierarchyTableHandler`.
12
+ */
13
+ /** Display names for the virtual (no real backing `Folder`) special folders - see `RopLogonHandler`'s own
14
+ * doc comment for why these don't have real rows in this data model. */
15
+ const VIRTUAL_DISPLAY_NAMES = {
16
+ root: "Root",
17
+ deferredAction: "Deferred Action",
18
+ spoolerQueue: "Spooler Queue",
19
+ ipmSubtree: "Top of Information Store",
20
+ commonViews: "Common Views",
21
+ schedule: "Schedule",
22
+ search: "Finder",
23
+ views: "Views",
24
+ shortcuts: "Shortcuts",
25
+ };
26
+ /** Resolves a target string into the display data a folder-table row needs. A `"folder:<uid>"` target whose
27
+ * `Folder` has since been deleted (soft-deleted or otherwise vanished) degrades to empty-looking values rather
28
+ * than throwing - the row simply won't be interesting to a client, not a reason to fail the whole ROP. */
29
+ export async function resolveFolderInfo(mailboxUid, target, folderRepo, cache, budget) {
30
+ if (target.startsWith("folder:")) {
31
+ const uid = target.slice("folder:".length);
32
+ const folder = await folderRepo.findOne(uid, { ignoreACL: true });
33
+ const hasChildren = (await resolveFolderChildren(mailboxUid, target, folderRepo, cache, budget)).length > 0;
34
+ return {
35
+ displayName: folder?.name ?? "",
36
+ unreadCount: folder?.unreadCount ?? 0,
37
+ totalCount: folder?.totalCount ?? 0,
38
+ hasChildren,
39
+ };
40
+ }
41
+ const name = target.slice("virtual:".length);
42
+ const hasChildren = (await resolveFolderChildren(mailboxUid, target, folderRepo, cache, budget)).length > 0;
43
+ return { displayName: VIRTUAL_DISPLAY_NAMES[name] ?? name, unreadCount: 0, totalCount: 0, hasChildren };
44
+ }
45
+ /**
46
+ * Resolves the direct children of `target`, as an array of the same target-string format, for
47
+ * `RopGetHierarchyTable`. Only `"virtual:root"`/`"virtual:ipmSubtree"` (this mailbox's top-level real
48
+ * folders - both collapse to the same "top of the visible tree" concept in this pragmatic subset, see
49
+ * `RopLogonHandler`'s own doc comment) and a real folder (its own real children) have any children at all;
50
+ * every other virtual folder (Deferred Action, Spooler Queue, ...) is permanently empty, since this data
51
+ * model has no concept of nesting anything under them.
52
+ *
53
+ * Filters an already-fetched full folder list in application code rather than querying by
54
+ * `parentFolderUid: undefined` directly - query-DSL semantics for "field is unset" aren't reliably consistent
55
+ * across backends (the exact kind of gap this project's own testing philosophy has caught before, e.g. `$or`
56
+ * being Mongo-only), so filtering a fetched array sidesteps the question entirely rather than risking it.
57
+ * The comparison itself uses `== null` (matching both `null` and `undefined`), not `=== undefined` - a
58
+ * top-level folder's unset `parentFolderUid` round-trips as genuine `undefined` from Mongo but as `null` from
59
+ * a SQL `nullable` column, a real, previously-confirmed cross-backend discrepancy in this codebase (caught by
60
+ * this exact test against a real SQLite-backed server, not assumed).
61
+ *
62
+ * **`cache`**: without it, this fetches every folder in the mailbox on every call - fine for the single call
63
+ * `RopGetHierarchyTableHandler` makes to build a table, but `resolveFolderInfo` also calls this once per row
64
+ * just to compute `hasChildren`, and a table page can hold many rows. A caller resolving a whole page (
65
+ * `RopQueryRowsHandler`/`RopGetPropertiesSpecificHandler`) passes one shared cache object through every call in
66
+ * that batch so the full list is fetched at most once per batch instead of once per row - see
67
+ * `FolderResolutionCache`'s own doc comment for why this isn't session state.
68
+ */
69
+ export async function resolveFolderChildren(mailboxUid, target, folderRepo, cache, budget) {
70
+ const isTopLevel = target === "virtual:root" || target === "virtual:ipmSubtree";
71
+ let parentFolderUid;
72
+ if (isTopLevel) {
73
+ parentFolderUid = undefined;
74
+ }
75
+ else if (target.startsWith("folder:")) {
76
+ parentFolderUid = target.slice("folder:".length);
77
+ }
78
+ else {
79
+ return [];
80
+ }
81
+ let allFolders;
82
+ if (cache) {
83
+ cache.allFolders ?? (cache.allFolders = await findMailboxFolders(mailboxUid, folderRepo, budget));
84
+ allFolders = cache.allFolders;
85
+ }
86
+ else {
87
+ allFolders = await findMailboxFolders(mailboxUid, folderRepo, budget);
88
+ }
89
+ return allFolders
90
+ .filter((f) => (isTopLevel ? f.parentFolderUid == null : f.parentFolderUid === parentFolderUid))
91
+ .map((f) => `folder:${f.uid}`);
92
+ }
93
+ /**
94
+ * Returns `target`'s existing FID if `RopLogon` or an earlier `RopGetHierarchyTable` row already assigned
95
+ * one, otherwise assigns and remembers the next free small integer FID. This is what lets a client
96
+ * `RopOpenFolder` a child folder it only ever learned about via a `RopQueryRows` row's `PidTagFolderId`
97
+ * column - without this, only the 13 `RopLogon`-time special folders could ever be opened.
98
+ *
99
+ * Backed by `session.folderTargetIds` (target -> FID) and `session.nextFolderId`, an O(1) reverse index/counter
100
+ * pair rather than a linear scan of `session.folderIds` plus a `Math.max(...spread)` over its keys - both real
101
+ * costs, not just theoretical ones: `folderTargetIds` only ever grows for a session's lifetime as a client
102
+ * browses more of a mailbox's folder tree, so the old scan-per-lookup approach was quadratic across a session
103
+ * that opens many folders, and `Math.max` spreading an unbounded array as call arguments risked a stack
104
+ * overflow past V8's argument-count limit on a mailbox with enough folders. `RopLogonHandler` keeps
105
+ * `folderTargetIds`/`nextFolderId` in sync with `folderIds` for the 13 special folders it assigns directly,
106
+ * rather than going through this function, since it assigns all 13 as one batch up front.
107
+ */
108
+ export function assignOrGetFid(session, target) {
109
+ const existing = session.folderTargetIds[target];
110
+ if (existing !== undefined) {
111
+ return existing;
112
+ }
113
+ const fid = session.nextFolderId++;
114
+ session.folderIds[String(fid)] = target;
115
+ session.folderTargetIds[target] = fid;
116
+ return fid;
117
+ }
118
+ //# sourceMappingURL=FolderTarget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FolderTarget.js","sourceRoot":"","sources":["../../../src/rop/FolderTarget.ts"],"names":[],"mappings":"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"}