@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,17 @@
1
+ import type { BufferReader, BufferWriter } from "./BufferCursor.js";
2
+ /**
3
+ * `TypedString` (`[MS-OXCDATA]` §2.11.7): a leading `StringType` byte followed by an optional string in the
4
+ * format that byte names - used by `RopOpenMessage`'s success response (`SubjectPrefix`/`NormalizedSubject`).
5
+ * `writeTypedString` never produces the "reduced Unicode" (`0x03`) form - that's purely a wire-size
6
+ * optimization real Exchange applies when every character happens to fit in one byte, not something a peer is
7
+ * ever required to send, so this codec always writes plain UTF-16LE (`0x04`) for non-empty strings. `undefined`
8
+ * writes as "no string" (`0x00`), matching a field the spec allows to be entirely absent (e.g. no subject
9
+ * prefix at all, as opposed to an empty one).
10
+ */
11
+ export declare function writeTypedString(writer: BufferWriter, value: string | undefined): void;
12
+ /** Reads a `TypedString`, supporting every `StringType` value a peer could legally send (`0x00`-`0x04`) even
13
+ * though `writeTypedString` only ever produces `0x00`/`0x01`/`0x04` itself - this side of the codec is only
14
+ * ever exercised in this library's own round-trip tests, since a real MAPI/HTTP client never sends a
15
+ * `TypedString` value to the server. Returns `undefined` for `StringType = 0x00` ("no string"), matching
16
+ * `writeTypedString`'s own `undefined` input. */
17
+ export declare function readTypedString(reader: BufferReader): string | undefined;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * MAPI-over-HTTP protocol compatibility (Phase 3 of this library's roadmap): a hand-built ROP (Remote
3
+ * Operation) codec and buffer-framing layer (`[MS-OXCROPS]`), JWT-secured EMSMDB transport (`BaseMapiEmsmdbRoute`,
4
+ * reusing the framework's existing `@Auth(["jwt"])` strategy - no MAPI-specific auth code of its own), and a
5
+ * minimal NSPI address-book endpoint (`BaseMapiNspiRoute`) for GAL "search as you type" lookups.
6
+ *
7
+ * This module exports only the backend-agnostic surface: the wire-format codecs, the abstract route base
8
+ * classes, `MapiSessionManager`, the ROP dispatcher, and every ROP/NSPI handler class (each takes a generic
9
+ * `RopContext`/repo argument, not a concrete entity class, so none of them are backend-specific). The concrete
10
+ * Mongo/SQL classes a deployment actually instantiates (`MapiEmsmdbRouteMongo`/`SQL`,
11
+ * `MapiNspiRouteMongo`/`SQL`) are exported from this package's `./mongo`/`./sql` subpaths instead, alongside
12
+ * every other entity/route/job this library defines - see `src/eas/index.ts`'s identical doc comment for the
13
+ * same convention.
14
+ *
15
+ * A deployment mounts both MAPI endpoints with a trivial one-line subclass each, the same pattern
16
+ * `src/eas/index.ts`'s doc comment describes for `BaseEasRoute`:
17
+ * ```ts
18
+ * import { MapiEmsmdbRouteMongo, MapiNspiRouteMongo } from "@rapidrest/mail/mongo";
19
+ * import { RouteDecorators } from "@rapidrest/service-core";
20
+ * const { Route } = RouteDecorators;
21
+ *
22
+ * @Route("/mapi/emsmdb")
23
+ * export class MyMapiEmsmdbRoute extends MapiEmsmdbRouteMongo {}
24
+ *
25
+ * @Route("/mapi/nspi")
26
+ * export class MyMapiNspiRoute extends MapiNspiRouteMongo {}
27
+ * ```
28
+ * Real Outlook desktop locates both URLs via `@rapidrest/mail/autodiscover`'s Outlook/EXCH response
29
+ * (`BaseAutodiscoverRoute`'s `mapiUrl` property) - see that module's own doc comment.
30
+ *
31
+ * **Documented gaps in this pragmatic subset** (see the architecture plan's "MAPI over HTTP (Phase 3)" section
32
+ * for the full reasoning behind each):
33
+ * - Incremental sync (`RopFastTransferSourceCopyTo`/`CopyProperties`/`GetBuffer`) always builds a full,
34
+ * non-differential dump, not real IDSET-based ICS (`[MS-OXCFXICS]`) - a real client still works correctly
35
+ * against this, just less efficiently than byte-perfect ICS.
36
+ * - Calendar/meetings: no counter-proposals, meeting-forwarding, delegate scheduling, or resource-booking
37
+ * auto-accept; no DST-aware timezones (fixed-offset approximation only); no recurrence exceptions/modified
38
+ * instances; no Hijri/`MonthEnd` recurrence patterns.
39
+ * - No `RopModifyRecipients` - attendees/recipients are always added via the same `PidTagDisplayTo`/`Cc`
40
+ * pragmatic path used for ordinary mail.
41
+ * - No public-folder support, no delegate/shared-mailbox access, no rules/permissions/search-folder ROPs, no
42
+ * client-certificate enrollment.
43
+ * - Calendar named properties (location/recurrence/timezone/etc.) are excluded from FastTransfer streams -
44
+ * only the columns in `FastTransferStream.ts`'s `DEFAULT_*_COLUMNS` tables are included.
45
+ * - NSPI is limited to `Bind`/`Unbind`/`GetMatches` only - no row/name lookups beyond a single content-
46
+ * restriction search, no directory replication.
47
+ */
48
+ export * from "./codec/BufferCursor.js";
49
+ export * from "./codec/MapiGuid.js";
50
+ export * from "./codec/TypedString.js";
51
+ export * from "./codec/PropertyValue.js";
52
+ export * from "./codec/RopBuffer.js";
53
+ export * from "./codec/AppointmentRecurrence.js";
54
+ export * from "./codec/MapiTimeZone.js";
55
+ export * from "./codec/GlobalObjectId.js";
56
+ export * from "./RopDispatcher.js";
57
+ export * from "./MapiSessionManager.js";
58
+ export * from "./BaseMapiEmsmdbRoute.js";
59
+ export * from "./BaseMapiNspiRoute.js";
60
+ export * from "./rop/RopHandler.js";
61
+ export * from "./rop/FolderTarget.js";
62
+ export * from "./rop/MessageTarget.js";
63
+ export * from "./rop/CalendarEventTarget.js";
64
+ export * from "./rop/MessageBodyStream.js";
65
+ export * from "./rop/PropertyResolvers.js";
66
+ export * from "./rop/NamedPropertyRegistry.js";
67
+ export * from "./rop/CalendarNamedProperties.js";
68
+ export * from "./rop/FastTransferStream.js";
69
+ export * from "./rop/MeetingMessageClassHandler.js";
70
+ export * from "./rop/RopLogonHandler.js";
71
+ export * from "./rop/RopReleaseHandler.js";
72
+ export * from "./rop/RopOpenFolderHandler.js";
73
+ export * from "./rop/RopGetHierarchyTableHandler.js";
74
+ export * from "./rop/RopSetColumnsHandler.js";
75
+ export * from "./rop/RopQueryRowsHandler.js";
76
+ export * from "./rop/RopGetContentsTableHandler.js";
77
+ export * from "./rop/RopOpenMessageHandler.js";
78
+ export * from "./rop/RopGetPropertiesSpecificHandler.js";
79
+ export * from "./rop/RopOpenStreamHandler.js";
80
+ export * from "./rop/RopReadStreamHandler.js";
81
+ export * from "./rop/RopCreateMessageHandler.js";
82
+ export * from "./rop/RopSetPropertiesHandler.js";
83
+ export * from "./rop/RopWriteStreamHandler.js";
84
+ export * from "./rop/RopSaveChangesMessageHandler.js";
85
+ export * from "./rop/RopSubmitMessageHandler.js";
86
+ export * from "./rop/RopGetPropertyIdsFromNamesHandler.js";
87
+ export * from "./rop/RopDeleteMessagesHandler.js";
88
+ export * from "./rop/RopDeleteFolderHandler.js";
89
+ export * from "./rop/RopFastTransferSourceCopyToHandler.js";
90
+ export * from "./rop/RopFastTransferSourceCopyPropertiesHandler.js";
91
+ export * from "./rop/RopFastTransferSourceGetBufferHandler.js";
92
+ export * from "./nspi/NspiCodec.js";
93
+ export * from "./nspi/NspiBindHandler.js";
94
+ export * from "./nspi/NspiGetMatchesHandler.js";
@@ -0,0 +1,20 @@
1
+ import { MailboxMongo } from "@rapidmx/restapi/mongo";
2
+ import { BaseMapiEmsmdbRoute } from "../BaseMapiEmsmdbRoute.js";
3
+ /**
4
+ * Mongo-backed concrete `BaseMapiEmsmdbRoute`, mounted at `/mapi/emsmdb`. Exported from this plugin's
5
+ * `./mongo` entry point, so the server host mounts it without a wrapper class of its
6
+ * own.
7
+ *
8
+ * @author Jean-Philippe Steinmetz
9
+ */
10
+ export declare class MapiEmsmdbRouteMongo extends BaseMapiEmsmdbRoute<MailboxMongo> {
11
+ protected mailboxClass: any;
12
+ protected folderClass: any;
13
+ protected messageClass: any;
14
+ protected calendarEventClass: any;
15
+ protected contactClass: any;
16
+ protected taskClass: any;
17
+ protected labelClass: any;
18
+ protected auditLogClass: any;
19
+ protected ropHandlerClasses: any[];
20
+ }
@@ -0,0 +1,13 @@
1
+ import { MailboxMongo } from "@rapidmx/restapi/mongo";
2
+ import { BaseMapiNspiRoute } from "../BaseMapiNspiRoute.js";
3
+ /**
4
+ * Mongo-backed concrete `BaseMapiNspiRoute`, mounted at `/mapi/nspi`. Exported from this plugin's
5
+ * `./mongo` entry point, so the server host mounts it without a wrapper class of its
6
+ * own.
7
+ *
8
+ * @author Jean-Philippe Steinmetz
9
+ */
10
+ export declare class MapiNspiRouteMongo extends BaseMapiNspiRoute<MailboxMongo> {
11
+ protected mailboxClass: any;
12
+ protected contactClass: any;
13
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./MapiEmsmdbRouteMongo.js";
2
+ export * from "./MapiNspiRouteMongo.js";
@@ -0,0 +1 @@
1
+ export * from "./mongo/index.js";
@@ -0,0 +1,22 @@
1
+ import type { JWTUser } from "@rapidrest/core";
2
+ import type { HttpResponse, RepoUtils } from "@rapidrest/service-core";
3
+ /**
4
+ * `Bind`/`Unbind` request types (`[MS-OXCMAPIHTTP]` §2.2.5.1/§2.2.5.2, backed by `[MS-OXNSPI]`'s own `NspiBind`/
5
+ * `NspiUnbind` methods): establishes/tears down an NSPI Session Context, the address-book endpoint's own
6
+ * analog of EMSMDB's `Connect`/`Disconnect`.
7
+ *
8
+ * **No real session state is created or tracked** - a deliberate, documented simplification, not an oversight:
9
+ * every subsequent NSPI operation in this pragmatic subset (`GetMatches`) is independently `@Auth(["jwt"])`-
10
+ * protected and re-resolves the caller's own mailbox from that same JWT, exactly the way `RopLogon`/every ROP
11
+ * handler already does for EMSMDB - the real spec's `MapiContext`-equivalent session cookie exists mainly for
12
+ * multi-server-farm request affinity, not additional authentication, so this pragmatic subset mints an opaque,
13
+ * unvalidated cookie value purely for wire-format conformance and never checks it again. `HasState`/`State`
14
+ * (an optional `STAT` scoping the bind to one address-book container) is accepted but not decoded at all -
15
+ * this pragmatic subset has exactly one "container" (a mailbox's own `Contact` list) regardless.
16
+ *
17
+ * @author Jean-Philippe Steinmetz
18
+ */
19
+ export declare function handleNspiBind(res: HttpResponse, user: JWTUser, mailboxRepo: RepoUtils<any>): Promise<void>;
20
+ /** `Unbind` never fails and has nothing to tear down - see `handleNspiBind`'s own doc comment for why no real
21
+ * session exists to release. */
22
+ export declare function handleNspiUnbind(res: HttpResponse): void;
@@ -0,0 +1,59 @@
1
+ import { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import { type PropertyTag, type PropertyValueData } from "../codec/PropertyValue.js";
3
+ /**
4
+ * Shared wire-format helpers for the NSPI-over-HTTP address-book endpoint (`[MS-OXCMAPIHTTP]` §2.2.5,
5
+ * `[MS-OXNSPI]`) - a genuinely different transport shape from EMSMDB's ROP buffers: each `X-RequestType` value
6
+ * (`Bind`/`Unbind`/`GetMatches`/...) has its own flat request/response body, not a multiplexed ROP stream, so
7
+ * there is no generic framing codec to share the way `RopBuffer.ts` does for EMSMDB - just these small
8
+ * structures every NSPI operation body embeds.
9
+ *
10
+ * @author Jean-Philippe Steinmetz
11
+ */
12
+ /** `STAT` (`[MS-OXNSPI]` §2.2.8, confirmed this session): 9 `DWORD`-sized fields (36 bytes total, `Delta`
13
+ * signed, every other field unsigned) describing an address-book container's paging/locale state. This
14
+ * pragmatic subset never persists a `STAT` across calls (see `NspiGetMatchesHandler.ts`'s own doc comment for
15
+ * why) - `readStat`/`writeStat` exist purely to keep the surrounding request/response body's own field
16
+ * offsets correct, not to drive any real positioning logic. */
17
+ export interface Stat {
18
+ sortType: number;
19
+ containerId: number;
20
+ currentRec: number;
21
+ delta: number;
22
+ numPos: number;
23
+ totalRecs: number;
24
+ codePage: number;
25
+ templateLocale: number;
26
+ sortLocale: number;
27
+ }
28
+ export declare function readStat(reader: BufferReader): Stat;
29
+ export declare function writeStat(writer: BufferWriter, stat: Stat): void;
30
+ /** A blank `STAT` (`SortType`/`ContainerID`/... all zero) - what this pragmatic subset always echoes back
31
+ * (in `Bind`'s optional acknowledgement and `GetMatches`' response) regardless of the caller's own request
32
+ * `STAT`, since no real per-container/locale state is tracked. */
33
+ export declare const BLANK_STAT: Stat;
34
+ /** `LargePropertyTagArray` (`[MS-OXCMAPIHTTP]` §2.2.1.8): a 4-byte count followed by that many `PropertyTag`
35
+ * structures (`[MS-OXCDATA]` §2.9 - the same `PropertyType`-then-`PropertyId` encoding `PropertyValue.ts`'s own
36
+ * `readPropertyTag`/`writePropertyTag` already implement). */
37
+ export declare function readLargePropertyTagArray(reader: BufferReader): PropertyTag[];
38
+ export declare function writeLargePropertyTagArray(writer: BufferWriter, tags: PropertyTag[]): void;
39
+ /** Writes one `AddressBookPropertyRow` (`[MS-OXCMAPIHTTP]` §2.2.1.7) - always `Flags=0x00` (every column
40
+ * value present without error), the exact `StandardPropertyRow`-equivalent choice `RopQueryRowsHandler`/
41
+ * `RopGetPropertiesSpecificHandler` already make for EMSMDB's own row encoding. */
42
+ export declare function writeAddressBookPropertyRow(writer: BufferWriter, columns: PropertyTag[], values: PropertyValueData[]): void;
43
+ /** Decodes a `Restriction` (`[MS-OXCDATA]` §2.12)'s leading `RestrictType` byte and, only for a
44
+ * `ContentRestriction`/`RES_CONTENT` (`0x03`) - the shape a real client's own GAL "search as you type" sends -
45
+ * extracts the plain string search term from its `TaggedValue` field.
46
+ *
47
+ * **Throws for any other restriction type** (`AND`/`OR`/`PropertyRestriction`/...) rather than degrading
48
+ * gracefully - unlike this pragmatic subset's usual "return a default instead of failing" stance elsewhere,
49
+ * a restriction's own byte length is type-dependent (12 different packet formats, several recursively nested),
50
+ * so once an unrecognized `RestrictType` is seen there is no safe way to know how many bytes to skip to reach
51
+ * the request body's own subsequent fields (`RowCount`/`Columns`/...) - silently returning `undefined` here
52
+ * would leave the reader mid-structure and corrupt every field decoded afterward. Throwing is the honest,
53
+ * safe choice for an input this narrow codec was never meant to parse, not a design gap.
54
+ *
55
+ * `FuzzyLevelLow`/`FuzzyLevelHigh` (exact/substring/prefix matching, case sensitivity) and the restriction's
56
+ * own target `PropertyTag` are decoded to advance the reader correctly but not honored - this pragmatic
57
+ * subset always does a case-insensitive substring match against its own fixed Contact field set regardless of
58
+ * which property or fuzzy level the client's restriction actually named. */
59
+ export declare function extractContentRestrictionSearchTerm(reader: BufferReader): string;
@@ -0,0 +1,26 @@
1
+ import type { HttpRequest, HttpResponse, RepoUtils } from "@rapidrest/service-core";
2
+ /** The most rows one `GetMatches` lookup asks the repo for per query - `RepoUtils.find()`'s own page cap. */
3
+ export declare const MAX_MATCH_ROWS = 1000;
4
+ /**
5
+ * `GetMatches` request type (`[MS-OXCMAPIHTTP]` §2.2.5.5, backed by `[MS-OXNSPI]`'s own `NspiGetMatches`
6
+ * method): searches the GAL (this library's `Contact` records, the same source of truth EAS's own `Search`
7
+ * command already uses) for entries matching the client's restriction, returning up to `RowCount` matches as
8
+ * `AddressBookPropertyRow`s. This is the real operation behind a client's own address-book "search as you
9
+ * type" UI - the closest real NSPI equivalent to this plan's own "minimal prefix/substring lookups" scope.
10
+ *
11
+ * `HasState`/`State` (an input `STAT` scoping the search and reporting position) and `HasMinimalIds`/
12
+ * `MinimalIds` (an "Explicit Table" restricting the search to a caller-supplied candidate set) are decoded to
13
+ * advance the reader correctly but not honored - this pragmatic subset always searches the caller's entire
14
+ * Contact list fresh on every call (no real per-container/table state, see `NspiBindHandler.ts`'s own doc
15
+ * comment for the same reasoning applied to `Bind`). `HasFilter`'s `Filter` is decoded via
16
+ * `NspiCodec.extractContentRestrictionSearchTerm` - see that function's own doc comment for why an
17
+ * unsupported restriction type throws rather than degrading. `HasPropertyName` (an alternate open-by-named-
18
+ * property addressing mode) is decoded to advance the reader correctly but never itself used to select a
19
+ * search field.
20
+ *
21
+ * The response's own `State` is always `BLANK_STAT` (with `TotalRecs` set to the real match count) - no real
22
+ * per-container `STAT` is tracked, matching this pragmatic subset's stated scope.
23
+ *
24
+ * @author Jean-Philippe Steinmetz
25
+ */
26
+ export declare function handleNspiGetMatches(req: HttpRequest, res: HttpResponse, mailboxUid: string, contactRepo: RepoUtils<any>): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import type { RopContext } from "./RopHandler.js";
2
+ /** Splits a `PidTagDisplayTo`/`Cc`/`Bcc`-style string on the semicolons Outlook separates recipients with,
3
+ * trimming and dropping empty entries. Commas are not separators: a resolved recipient's display name commonly
4
+ * contains one (`Doe, Jane <jane@example.com>`). */
5
+ export declare function splitAddressList(value: string | undefined): string[];
6
+ /** `true` for a plain SMTP address (see `PLAIN_EMAIL_ADDRESS`) no longer than RFC 5321's 254 characters. These
7
+ * strings reach MIME headers, the SMTP envelope and iCalendar `mailto:` lines, where a CR/LF would inject content. */
8
+ export declare function isPlainEmailAddress(value: string): boolean;
9
+ /** One entry of a display string: an address with an optional display name, a display name alone (`address`
10
+ * undefined, still to be resolved), or something that can't be used (`invalid`). */
11
+ export interface RecipientEntry {
12
+ name: string;
13
+ address?: string;
14
+ invalid?: boolean;
15
+ }
16
+ /**
17
+ * Parses one `;`-separated entry with nodemailer's `addressparser`: `jane@example.com`, `Jane <jane@example.com>`,
18
+ * `"Doe, Jane" <jane@example.com>` or a bare display name `Jane Doe`. An entry holding several addresses yields one
19
+ * recipient each. Any address that isn't a plain SMTP address marks the entry invalid.
20
+ */
21
+ export declare function parseRecipientEntry(entry: string): RecipientEntry[];
22
+ /** Every entry of a `PidTagDisplayTo`/`Cc`/`Bcc`-style string - see `parseRecipientEntry`. */
23
+ export declare function parseRecipientList(value: string | undefined): RecipientEntry[];
24
+ /** The plain addresses in a `PidTagDisplayTo`/`Cc`/`Bcc`-style string, including those written as
25
+ * `Name <address>`, skipping display names without an address and anything invalid. */
26
+ export declare function parseAddressList(value: string | undefined): string[];
27
+ /** A recipient ready to send to. */
28
+ export interface ResolvedRecipient {
29
+ name: string;
30
+ address: string;
31
+ }
32
+ /** The outcome of `resolveRecipientList`: the usable recipients, and the entries that were display names with no
33
+ * single matching contact (`unresolved`) or not usable at all (`invalid`). */
34
+ export interface RecipientResolution {
35
+ recipients: ResolvedRecipient[];
36
+ unresolved: string[];
37
+ invalid: string[];
38
+ }
39
+ /** The most recipients (To, Cc and Bcc together) one submitted message or meeting may have - the same limit
40
+ * `@rapidmx/activesync`'s `ComposeMailCommand` enforces (`MAX_COMPOSE_RECIPIENTS`). */
41
+ export declare const MAX_RECIPIENTS_PER_MESSAGE = 500;
42
+ /**
43
+ * Resolves a display string into addresses. An entry carrying an address is used as is; a bare display name (Outlook
44
+ * writes names, not addresses, into `PidTagDisplayTo` once it has resolved a recipient against an address book) is
45
+ * looked up among the caller's own contacts by exact display name and used when exactly one contact matches, with
46
+ * that contact's first valid email address.
47
+ */
48
+ export declare function resolveRecipientList(value: string | undefined, context: Pick<RopContext, "mailboxUid" | "contactRepo" | "budget">): Promise<RecipientResolution>;
@@ -0,0 +1,33 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import { Attendee, BusyStatus, RecurrenceRule } from "@rapidmx/restapi";
3
+ import type { ExecuteBudget } from "./ExecuteBudget.js";
4
+ import { type RepoSort } from "./RepoPaging.js";
5
+ /**
6
+ * The `RopGetContentsTable`/`RopOpenMessage` analog of `MessageTarget.ts`, for a `Folder` of type `CALENDAR`:
7
+ * resolves a `"calendarEvent:<uid>"` row/handle target into the display data a calendar-item row or an opened
8
+ * appointment's properties need. A `CalendarEvent` is addressed by MID exactly the same way a `Message` is -
9
+ * `MessageTarget.assignOrGetMid`/`session.messageIds` are reused as-is (see that file's own doc comment; the
10
+ * mechanism is already generic over the target-string prefix, not message-specific despite its file location),
11
+ * so this file adds no MID-registry code of its own.
12
+ */
13
+ export interface CalendarEventTargetInfo {
14
+ title: string;
15
+ location?: string;
16
+ startDate: Date;
17
+ endDate: Date;
18
+ timezone: string;
19
+ busyStatus: BusyStatus;
20
+ recurrenceRule?: RecurrenceRule;
21
+ reminderMinutesBeforeStart?: number;
22
+ organizerAddress: string;
23
+ attendees: Attendee[];
24
+ }
25
+ /** Degrades to empty-looking values for a `"calendarEvent:<uid>"` target whose real `CalendarEvent` has since
26
+ * vanished (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
27
+ * `MessageTarget.resolveMessageInfo`/`FolderTarget.resolveFolderInfo` already apply. */
28
+ export declare function resolveCalendarEventInfo(target: string, calendarEventRepo: RepoUtils<any>): Promise<CalendarEventTargetInfo>;
29
+ /** The order a calendar folder's events are listed in: latest start first, ties broken by `uid`. */
30
+ export declare const CALENDAR_EVENT_SORT: RepoSort;
31
+ /** Resolves the calendar events directly in `folderUid`, latest first and capped at `MAX_COLLECTION_ROWS`, as
32
+ * `"calendarEvent:<uid>"` target strings. */
33
+ export declare function resolveFolderCalendarEvents(folderUid: string, calendarEventRepo: RepoUtils<any>, budget?: ExecuteBudget): Promise<string[]>;
@@ -0,0 +1,37 @@
1
+ import { AttendeeResponseStatus, BusyStatus } from "@rapidmx/restapi";
2
+ /**
3
+ * The Calendar named-property identity table (`(PropertySet GUID, LID)` pairs) this pragmatic subset supports,
4
+ * per `[MS-OXPROPS]` - each pinned against its own MS-OXPROPS page (not invented), shared by `PropertyResolvers.ts`
5
+ * (the read side: `calendarEventValueFor`) and `RopSaveChangesMessageHandler.ts` (the write side: decoding a
6
+ * draft's accumulated `RopSetProperties` values back into a real `CalendarEvent`) so both directions of this
7
+ * mapping stay in exactly one place. See the architecture plan's "Calendar support" section for the full table.
8
+ */
9
+ export declare const PSETID_APPOINTMENT = "00062002-0000-0000-c000-000000000046";
10
+ export declare const PSETID_COMMON = "00062008-0000-0000-c000-000000000046";
11
+ export declare const PSETID_MEETING = "6ed8da90-450b-101b-98da-00aa003f1305";
12
+ export declare const LID_LOCATION = 33288;
13
+ export declare const LID_APPOINTMENT_START_WHOLE = 33293;
14
+ export declare const LID_APPOINTMENT_END_WHOLE = 33294;
15
+ export declare const LID_BUSY_STATUS = 33285;
16
+ export declare const LID_RECURRING = 33315;
17
+ export declare const LID_REMINDER_SET = 34051;
18
+ export declare const LID_REMINDER_DELTA = 34049;
19
+ export declare const LID_RESPONSE_STATUS = 33304;
20
+ export declare const LID_APPOINTMENT_RECUR = 33302;
21
+ export declare const LID_TIME_ZONE_STRUCT = 33331;
22
+ /** `PidLidGlobalObjectId` (`PSETID_Meeting`) - see `GlobalObjectId.ts` for the BLOB it carries and
23
+ * `MeetingMessageClassHandler.ts` for how a meeting response's own copy of it is correlated back to the
24
+ * original `CalendarEvent`. */
25
+ export declare const LID_GLOBAL_OBJECT_ID = 3;
26
+ /** `PidLidBusyStatus`'s wire values (`[MS-OXOCAL]` §2.2.1.2, confirmed this session) - a different numbering
27
+ * than `CalendarSyncAdapter`'s own `BUSY_STATUS_CODES` table for the unrelated MS-ASCAL `BusyStatus` field.
28
+ * `olWorkingElsewhere` (0x00000004) has no equivalent in this library's own `BusyStatus` enum - never produced,
29
+ * and decoded (via `BUSY_STATUS_FROM_CODE`) with a fallback to `BUSY`, a documented, harmless approximation. */
30
+ export declare const BUSY_STATUS_CODES: Record<BusyStatus, number>;
31
+ export declare const BUSY_STATUS_FROM_CODE: Record<number, BusyStatus>;
32
+ /** `PidLidResponseStatus`'s wire values (`[MS-OXOCAL]` §2.2.1.11, confirmed this session) for the caller's own
33
+ * attendee record. `respOrganized` (0x00000001) is returned when the caller *is* the organizer - not a value
34
+ * `AttendeeResponseStatus` itself has a case for, since that enum only models an attendee's own response. */
35
+ export declare const RESPONSE_STATUS_CODES: Record<AttendeeResponseStatus, number>;
36
+ export declare const RESPONSE_STATUS_ORGANIZED = 1;
37
+ export declare const RESPONSE_STATUS_NONE = 0;
@@ -0,0 +1,29 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import type { RepoSort } from "./RepoPaging.js";
3
+ /**
4
+ * The `RopGetContentsTable`/`RopOpenMessage` analog of `MessageTarget.ts`, for a `Folder` of type `CONTACTS`:
5
+ * resolves a `"contact:<uid>"` row/handle target into the display data a contact-item row or an opened
6
+ * address-card's properties need. A `Contact` is addressed by MID exactly the same way a `Message`/
7
+ * `CalendarEvent` is - `MessageTarget.assignOrGetMid`/`session.messageIds` are reused as-is (see that file's own
8
+ * doc comment), so this file adds no MID-registry code of its own.
9
+ *
10
+ * Read-only in this pragmatic subset - a client editing/creating a Contact via MAPI (`RopSetProperties`/
11
+ * `RopSaveChangesMessage` against a `"contact:"` handle) is a documented gap, the same "browse only" limitation
12
+ * this codebase already accepts for Tasks (`TaskTarget.ts`); NSPI's own `Bind`/`Unbind`/`GetMatches` already
13
+ * covers the "search the GAL" case this data also backs.
14
+ */
15
+ export interface ContactTargetInfo {
16
+ displayName: string;
17
+ givenName?: string;
18
+ surname?: string;
19
+ email?: string;
20
+ businessPhone?: string;
21
+ companyName?: string;
22
+ jobTitle?: string;
23
+ }
24
+ /** Degrades to empty-looking values for a `"contact:<uid>"` target whose real `Contact` has since vanished
25
+ * (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
26
+ * `MessageTarget.resolveMessageInfo`/`CalendarEventTarget.resolveCalendarEventInfo` already apply. */
27
+ export declare function resolveContactInfo(target: string, contactRepo: RepoUtils<any>): Promise<ContactTargetInfo>;
28
+ /** The order a `CONTACTS` folder's contents table lists contacts in (see `ContentsTable.ts`). */
29
+ export declare const CONTACT_SORT: RepoSort;
@@ -0,0 +1,16 @@
1
+ import type { MapiObjectHandle } from "../MapiSessionManager.js";
2
+ import type { RopContext } from "./RopHandler.js";
3
+ export type ContentsKind = NonNullable<MapiObjectHandle["contentsKind"]>;
4
+ /** Which repo a folder's items live in, decided by the folder's own `type`: a `Contact`/`Task`/`CalendarEvent` is
5
+ * its own entity, never a `Message` row. */
6
+ export declare function resolveContentsKind(folderUid: string, context: RopContext): Promise<ContentsKind>;
7
+ /**
8
+ * The `"<kind>:<uid>"` row targets at positions `[start, start + count)` of a contents table (a `"folder:<uid>"`
9
+ * table with `contentsKind` set), read straight from the database in a fixed, stable order. A context without the
10
+ * matching optional repo yields no rows.
11
+ *
12
+ * Reading a window per `RopQueryRows` instead of snapshotting every row at `RopGetContentsTable` time keeps session
13
+ * state small and makes no folder too large to page through. The trade-off is that a message added or removed
14
+ * between two `RopQueryRows` calls can shift the rows that follow.
15
+ */
16
+ export declare function resolveContentsWindow(context: RopContext, table: MapiObjectHandle, start: number, count: number): Promise<string[]>;
@@ -0,0 +1,48 @@
1
+ /** The most rows (items or folders) whose properties one `Execute` resolves, across all of its ROPs. */
2
+ export declare const MAX_ROWS_RESOLVED_PER_EXECUTE = 20000;
3
+ /** The most bytes one `Execute` builds or parses (FastTransfer streams and message bodies), across all of its ROPs. */
4
+ export declare const MAX_BYTES_BUILT_PER_EXECUTE: number;
5
+ /** The most database queries (paged reads, table lookups, deletes) one `Execute` runs through the budgeted paths. */
6
+ export declare const MAX_QUERIES_PER_EXECUTE = 20000;
7
+ /** The most rows one `Execute` reads back from paged queries (folder lists, folder walks, item lists). */
8
+ export declare const MAX_ROWS_FETCHED_PER_EXECUTE = 100000;
9
+ /** The most `RopSubmitMessage`s one `Execute` runs. Each one can relay mail, so a request can't repeat it 1024 times. */
10
+ export declare const MAX_SUBMITS_PER_EXECUTE = 16;
11
+ /** Thrown when an `Execute` has used up its `ExecuteBudget`. The ROP that hit it fails; see `RopDispatcher`. */
12
+ export declare class WorkBudgetExceededError extends Error {
13
+ constructor(what: string);
14
+ }
15
+ /**
16
+ * The work one `Execute` may do. Limits on ROP count and per-ROP sizes alone still let one request chain many
17
+ * expensive ROPs (a dozen 10000-row FastTransfer dumps, the same large body parsed by several streams, or a thousand
18
+ * hierarchy tables or folder walks), so rows resolved, bytes built, queries run, rows fetched and submits are counted
19
+ * across the whole request. Every charge happens before the work it pays for. Also dedupes message body parses within
20
+ * the request (`bodies`).
21
+ */
22
+ export declare class ExecuteBudget {
23
+ rowsRemaining: number;
24
+ bytesRemaining: number;
25
+ queriesRemaining: number;
26
+ fetchedRowsRemaining: number;
27
+ submitsRemaining: number;
28
+ /** Message uid -> its decoded body, so opening the same body twice in one request parses it once. */
29
+ readonly bodies: Map<string, Promise<Buffer<ArrayBufferLike>>>;
30
+ constructor(maxRows?: number, maxBytes?: number, limits?: {
31
+ maxQueries?: number;
32
+ maxFetchedRows?: number;
33
+ maxSubmits?: number;
34
+ });
35
+ /** Uses `count` rows of the budget, throwing `WorkBudgetExceededError` once it is exhausted. */
36
+ chargeRows(count?: number): void;
37
+ /** Uses `count` bytes of the budget, throwing `WorkBudgetExceededError` once it is exhausted. */
38
+ chargeBytes(count: number): void;
39
+ /** Throws `WorkBudgetExceededError` when no bytes are left, so work that can't be sized up front (fetching a blob)
40
+ * isn't started at all. */
41
+ assertBytesLeft(): void;
42
+ /** Uses `count` queries of the budget. Called before running them. */
43
+ chargeQueries(count?: number): void;
44
+ /** Uses `count` fetched rows of the budget. */
45
+ chargeFetchedRows(count: number): void;
46
+ /** Uses one submit of the budget. */
47
+ chargeSubmit(): void;
48
+ }
@@ -0,0 +1,57 @@
1
+ import { PropertyType } from "../codec/PropertyValue.js";
2
+ import { type MapiObjectHandle } from "../MapiSessionManager.js";
3
+ import { type RopContext } from "./RopHandler.js";
4
+ interface PropertyColumn {
5
+ propertyId: number;
6
+ propertyType: PropertyType;
7
+ }
8
+ /** The largest FastTransfer stream built for one handle; a larger source fails with `MAPI_E_TOO_BIG`. */
9
+ export declare const MAX_FAST_TRANSFER_BYTES: number;
10
+ /** How long a built stream is kept for paging out. A client pages a stream out right after opening it; one that
11
+ * leaves it longer than this has to open the transfer again. */
12
+ export declare const FAST_TRANSFER_TTL_SECONDS: number;
13
+ /** Thrown while building a stream that has grown past its byte limit. */
14
+ export declare class FastTransferTooBigError extends Error {
15
+ constructor();
16
+ }
17
+ /**
18
+ * Builds the complete FastTransfer stream for `handle` (a `"folder"` or `"message"` Server object). `columns`,
19
+ * when given (a `RopFastTransferSourceCopyProperties` explicit include list), replaces every default column set
20
+ * uniformly at both folder- and message-level; `excludePropertyIds` (a `RopFastTransferSourceCopyTo` exclude
21
+ * list) is only ever applied to this pragmatic subset's own default columns, per the same reasoning.
22
+ *
23
+ * The size is checked after every item, so a huge folder stops being built as soon as it passes `maxBytes`
24
+ * (`FastTransferTooBigError`) instead of being fully built first. Every item resolved and the finished stream's bytes
25
+ * count against the request's `ExecuteBudget` when there is one.
26
+ */
27
+ export declare function buildFastTransferStream(handle: MapiObjectHandle, context: RopContext, options?: {
28
+ columns?: PropertyColumn[];
29
+ excludePropertyIds?: ReadonlySet<number>;
30
+ maxBytes?: number;
31
+ }): Promise<Buffer>;
32
+ /** Why `loadFastTransferBuffer` has no stream: `"tooBig"` when a rebuild passed `MAX_FAST_TRANSFER_BYTES`, `"lost"`
33
+ * when the stored stream is gone part-way through paging. */
34
+ export type FastTransferLoadFailure = "tooBig" | "lost";
35
+ /**
36
+ * The built stream for the `"fastTransfer"` handle at `handleIndex`, from the shared `HandleDataStore` (Redis when
37
+ * configured, so any replica can page it out).
38
+ *
39
+ * When the stream is gone (expired, evicted, or built by a replica without shared storage), it is rebuilt only if
40
+ * nothing has been paged out yet. Part-way through, a rebuild could differ from what the client already has (items
41
+ * added or changed since), and continuing at the old offset would hand it bytes from a different stream, so that
42
+ * case reports `"lost"` and the client restarts the transfer. A rebuild is held to `MAX_FAST_TRANSFER_BYTES` like
43
+ * the original build, and storing it to the session's and user's handle data quota (`handleDataOwners`); either
44
+ * failing reports `"tooBig"`.
45
+ */
46
+ export declare function loadFastTransferBuffer(context: RopContext, handleIndex: number, transfer: MapiObjectHandle): Promise<Buffer | FastTransferLoadFailure>;
47
+ /**
48
+ * Shared by `RopFastTransferSourceCopyTo`/`CopyProperties`: builds the stream for `source`, and on success stores a
49
+ * `"fastTransfer"` handle at `outputHandleIndex` with the stream in the `HandleDataStore`. Returns `false`, storing
50
+ * nothing, when the stream exceeds `MAX_FAST_TRANSFER_BYTES` or the session's or user's handle data quota
51
+ * (`handleDataOwners`) has no room for it.
52
+ */
53
+ export declare function openFastTransferHandle(context: RopContext, outputHandleIndex: number, source: MapiObjectHandle, options: {
54
+ columns?: PropertyColumn[];
55
+ excludePropertyIds?: number[];
56
+ }): Promise<boolean>;
57
+ export {};
@@ -0,0 +1,64 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import { Folder } from "@rapidmx/restapi";
3
+ import type { MapiSessionContext } from "../MapiSessionManager.js";
4
+ import type { ExecuteBudget } from "./ExecuteBudget.js";
5
+ export interface FolderTargetInfo {
6
+ displayName: string;
7
+ unreadCount: number;
8
+ totalCount: number;
9
+ hasChildren: boolean;
10
+ }
11
+ /** An optional, purely request-scoped (never persisted to the session) memo for `resolveFolderChildren`'s own
12
+ * full-mailbox `folderRepo.find({ mailboxUid })` fetch - see that function's own doc comment for why this
13
+ * matters. Deliberately just a plain object a caller creates fresh per ROP-handler invocation and threads
14
+ * through every `resolveFolderInfo`/`resolvePropertyValues` call in that same batch, not session state - unlike
15
+ * the FID/MID/named-property registries, there's no cross-request reuse to gain here (a mailbox's folder list
16
+ * can change between requests), only cross-*row* reuse within one table page. */
17
+ export interface FolderResolutionCache {
18
+ allFolders?: Folder[];
19
+ }
20
+ /** Resolves a target string into the display data a folder-table row needs. A `"folder:<uid>"` target whose
21
+ * `Folder` has since been deleted (soft-deleted or otherwise vanished) degrades to empty-looking values rather
22
+ * than throwing - the row simply won't be interesting to a client, not a reason to fail the whole ROP. */
23
+ export declare function resolveFolderInfo(mailboxUid: string, target: string, folderRepo: RepoUtils<any>, cache?: FolderResolutionCache, budget?: ExecuteBudget): Promise<FolderTargetInfo>;
24
+ /**
25
+ * Resolves the direct children of `target`, as an array of the same target-string format, for
26
+ * `RopGetHierarchyTable`. Only `"virtual:root"`/`"virtual:ipmSubtree"` (this mailbox's top-level real
27
+ * folders - both collapse to the same "top of the visible tree" concept in this pragmatic subset, see
28
+ * `RopLogonHandler`'s own doc comment) and a real folder (its own real children) have any children at all;
29
+ * every other virtual folder (Deferred Action, Spooler Queue, ...) is permanently empty, since this data
30
+ * model has no concept of nesting anything under them.
31
+ *
32
+ * Filters an already-fetched full folder list in application code rather than querying by
33
+ * `parentFolderUid: undefined` directly - query-DSL semantics for "field is unset" aren't reliably consistent
34
+ * across backends (the exact kind of gap this project's own testing philosophy has caught before, e.g. `$or`
35
+ * being Mongo-only), so filtering a fetched array sidesteps the question entirely rather than risking it.
36
+ * The comparison itself uses `== null` (matching both `null` and `undefined`), not `=== undefined` - a
37
+ * top-level folder's unset `parentFolderUid` round-trips as genuine `undefined` from Mongo but as `null` from
38
+ * a SQL `nullable` column, a real, previously-confirmed cross-backend discrepancy in this codebase (caught by
39
+ * this exact test against a real SQLite-backed server, not assumed).
40
+ *
41
+ * **`cache`**: without it, this fetches every folder in the mailbox on every call - fine for the single call
42
+ * `RopGetHierarchyTableHandler` makes to build a table, but `resolveFolderInfo` also calls this once per row
43
+ * just to compute `hasChildren`, and a table page can hold many rows. A caller resolving a whole page (
44
+ * `RopQueryRowsHandler`/`RopGetPropertiesSpecificHandler`) passes one shared cache object through every call in
45
+ * that batch so the full list is fetched at most once per batch instead of once per row - see
46
+ * `FolderResolutionCache`'s own doc comment for why this isn't session state.
47
+ */
48
+ export declare function resolveFolderChildren(mailboxUid: string, target: string, folderRepo: RepoUtils<any>, cache?: FolderResolutionCache, budget?: ExecuteBudget): Promise<string[]>;
49
+ /**
50
+ * Returns `target`'s existing FID if `RopLogon` or an earlier `RopGetHierarchyTable` row already assigned
51
+ * one, otherwise assigns and remembers the next free small integer FID. This is what lets a client
52
+ * `RopOpenFolder` a child folder it only ever learned about via a `RopQueryRows` row's `PidTagFolderId`
53
+ * column - without this, only the 13 `RopLogon`-time special folders could ever be opened.
54
+ *
55
+ * Backed by `session.folderTargetIds` (target -> FID) and `session.nextFolderId`, an O(1) reverse index/counter
56
+ * pair rather than a linear scan of `session.folderIds` plus a `Math.max(...spread)` over its keys - both real
57
+ * costs, not just theoretical ones: `folderTargetIds` only ever grows for a session's lifetime as a client
58
+ * browses more of a mailbox's folder tree, so the old scan-per-lookup approach was quadratic across a session
59
+ * that opens many folders, and `Math.max` spreading an unbounded array as call arguments risked a stack
60
+ * overflow past V8's argument-count limit on a mailbox with enough folders. `RopLogonHandler` keeps
61
+ * `folderTargetIds`/`nextFolderId` in sync with `folderIds` for the 13 special folders it assigns directly,
62
+ * rather than going through this function, since it assigns all 13 as one batch up front.
63
+ */
64
+ export declare function assignOrGetFid(session: MapiSessionContext, target: string): number;