@rapidmx/mapi-plugin 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +297 -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 +119 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RopDispatcher.js +34 -0
  10. package/dist/lib/RopDispatcher.js.map +1 -0
  11. package/dist/lib/codec/AppointmentRecurrence.js +314 -0
  12. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  13. package/dist/lib/codec/BufferCursor.js +213 -0
  14. package/dist/lib/codec/BufferCursor.js.map +1 -0
  15. package/dist/lib/codec/GlobalObjectId.js +92 -0
  16. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  17. package/dist/lib/codec/MapiGuid.js +41 -0
  18. package/dist/lib/codec/MapiGuid.js.map +1 -0
  19. package/dist/lib/codec/MapiTimeZone.js +93 -0
  20. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  21. package/dist/lib/codec/PropertyValue.js +214 -0
  22. package/dist/lib/codec/PropertyValue.js.map +1 -0
  23. package/dist/lib/codec/RopBuffer.js +26 -0
  24. package/dist/lib/codec/RopBuffer.js.map +1 -0
  25. package/dist/lib/codec/TypedString.js +54 -0
  26. package/dist/lib/codec/TypedString.js.map +1 -0
  27. package/dist/lib/index.js +99 -0
  28. package/dist/lib/index.js.map +1 -0
  29. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +84 -0
  30. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  31. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  32. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/index.js +7 -0
  34. package/dist/lib/mongo/index.js.map +1 -0
  35. package/dist/lib/mongo.js +6 -0
  36. package/dist/lib/mongo.js.map +1 -0
  37. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  38. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  39. package/dist/lib/nspi/NspiCodec.js +106 -0
  40. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  41. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  42. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  43. package/dist/lib/rop/CalendarEventTarget.js +27 -0
  44. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  45. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  46. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  47. package/dist/lib/rop/ContactTarget.js +23 -0
  48. package/dist/lib/rop/ContactTarget.js.map +1 -0
  49. package/dist/lib/rop/FastTransferStream.js +98 -0
  50. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  51. package/dist/lib/rop/FolderTarget.js +111 -0
  52. package/dist/lib/rop/FolderTarget.js.map +1 -0
  53. package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
  54. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  55. package/dist/lib/rop/MessageBodyStream.js +42 -0
  56. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  57. package/dist/lib/rop/MessageTarget.js +44 -0
  58. package/dist/lib/rop/MessageTarget.js.map +1 -0
  59. package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
  60. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  61. package/dist/lib/rop/PropertyResolvers.js +313 -0
  62. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  63. package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
  64. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  65. package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
  66. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  67. package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -0
  68. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  69. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  70. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  71. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +61 -0
  72. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  73. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
  74. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  75. package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
  76. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  77. package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
  78. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  79. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -0
  80. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  81. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  82. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopHandler.js +2 -0
  84. package/dist/lib/rop/RopHandler.js.map +1 -0
  85. package/dist/lib/rop/RopLogonHandler.js +132 -0
  86. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  87. package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
  88. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  89. package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
  90. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  91. package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
  92. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  93. package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
  94. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  95. package/dist/lib/rop/RopReadStreamHandler.js +55 -0
  96. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  97. package/dist/lib/rop/RopReleaseHandler.js +20 -0
  98. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  99. package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
  100. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  101. package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
  102. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  103. package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
  104. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  105. package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
  106. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  107. package/dist/lib/rop/RopWriteStreamHandler.js +45 -0
  108. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  109. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  110. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  111. package/dist/lib/rop/TaskTarget.js +19 -0
  112. package/dist/lib/rop/TaskTarget.js.map +1 -0
  113. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -0
  114. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  115. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  116. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  117. package/dist/lib/sql/index.js +7 -0
  118. package/dist/lib/sql/index.js.map +1 -0
  119. package/dist/lib/sql.js +6 -0
  120. package/dist/lib/sql.js.map +1 -0
  121. package/dist/types/BaseMapiEmsmdbRoute.d.ts +82 -0
  122. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  123. package/dist/types/MapiSessionManager.d.ts +130 -0
  124. package/dist/types/RopDispatcher.d.ts +17 -0
  125. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  126. package/dist/types/codec/BufferCursor.d.ts +78 -0
  127. package/dist/types/codec/GlobalObjectId.d.ts +11 -0
  128. package/dist/types/codec/MapiGuid.d.ts +6 -0
  129. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  130. package/dist/types/codec/PropertyValue.d.ts +77 -0
  131. package/dist/types/codec/RopBuffer.d.ts +25 -0
  132. package/dist/types/codec/TypedString.d.ts +17 -0
  133. package/dist/types/index.d.ts +94 -0
  134. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +19 -0
  135. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  136. package/dist/types/mongo/index.d.ts +2 -0
  137. package/dist/types/mongo.d.ts +1 -0
  138. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  139. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  140. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +24 -0
  141. package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
  142. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  143. package/dist/types/rop/ContactTarget.d.ts +29 -0
  144. package/dist/types/rop/FastTransferStream.d.ts +18 -0
  145. package/dist/types/rop/FolderTarget.d.ts +63 -0
  146. package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
  147. package/dist/types/rop/MessageBodyStream.d.ts +26 -0
  148. package/dist/types/rop/MessageTarget.d.ts +38 -0
  149. package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
  150. package/dist/types/rop/PropertyResolvers.d.ts +60 -0
  151. package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
  152. package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
  153. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
  154. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
  155. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
  156. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
  157. package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
  158. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
  159. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
  160. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  161. package/dist/types/rop/RopHandler.d.ts +67 -0
  162. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  163. package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
  164. package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
  165. package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
  166. package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
  167. package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
  168. package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
  169. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -0
  170. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  171. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  172. package/dist/types/rop/RopSubmitMessageHandler.d.ts +70 -0
  173. package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
  174. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  175. package/dist/types/rop/TaskTarget.d.ts +24 -0
  176. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -0
  177. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  178. package/dist/types/sql/index.d.ts +2 -0
  179. package/dist/types/sql.d.ts +1 -0
  180. package/package.json +104 -0
@@ -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,24 @@
1
+ import type { HttpRequest, HttpResponse, RepoUtils } from "@rapidrest/service-core";
2
+ /**
3
+ * `GetMatches` request type (`[MS-OXCMAPIHTTP]` §2.2.5.5, backed by `[MS-OXNSPI]`'s own `NspiGetMatches`
4
+ * method): searches the GAL (this library's `Contact` records, the same source of truth EAS's own `Search`
5
+ * command already uses) for entries matching the client's restriction, returning up to `RowCount` matches as
6
+ * `AddressBookPropertyRow`s. This is the real operation behind a client's own address-book "search as you
7
+ * type" UI - the closest real NSPI equivalent to this plan's own "minimal prefix/substring lookups" scope.
8
+ *
9
+ * `HasState`/`State` (an input `STAT` scoping the search and reporting position) and `HasMinimalIds`/
10
+ * `MinimalIds` (an "Explicit Table" restricting the search to a caller-supplied candidate set) are decoded to
11
+ * advance the reader correctly but not honored - this pragmatic subset always searches the caller's entire
12
+ * Contact list fresh on every call (no real per-container/table state, see `NspiBindHandler.ts`'s own doc
13
+ * comment for the same reasoning applied to `Bind`). `HasFilter`'s `Filter` is decoded via
14
+ * `NspiCodec.extractContentRestrictionSearchTerm` - see that function's own doc comment for why an
15
+ * unsupported restriction type throws rather than degrading. `HasPropertyName` (an alternate open-by-named-
16
+ * property addressing mode) is decoded to advance the reader correctly but never itself used to select a
17
+ * search field.
18
+ *
19
+ * The response's own `State` is always `BLANK_STAT` (with `TotalRecs` set to the real match count) - no real
20
+ * per-container `STAT` is tracked, matching this pragmatic subset's stated scope.
21
+ *
22
+ * @author Jean-Philippe Steinmetz
23
+ */
24
+ export declare function handleNspiGetMatches(req: HttpRequest, res: HttpResponse, mailboxUid: string, contactRepo: RepoUtils<any>): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import { Attendee, BusyStatus, RecurrenceRule } from "@rapidmx/restapi";
3
+ /**
4
+ * The `RopGetContentsTable`/`RopOpenMessage` analog of `MessageTarget.ts`, for a `Folder` of type `CALENDAR`:
5
+ * resolves a `"calendarEvent:<uid>"` row/handle target into the display data a calendar-item row or an opened
6
+ * appointment's properties need. A `CalendarEvent` is addressed by MID exactly the same way a `Message` is -
7
+ * `MessageTarget.assignOrGetMid`/`session.messageIds` are reused as-is (see that file's own doc comment; the
8
+ * mechanism is already generic over the target-string prefix, not message-specific despite its file location),
9
+ * so this file adds no MID-registry code of its own.
10
+ */
11
+ export interface CalendarEventTargetInfo {
12
+ title: string;
13
+ location?: string;
14
+ startDate: Date;
15
+ endDate: Date;
16
+ timezone: string;
17
+ busyStatus: BusyStatus;
18
+ recurrenceRule?: RecurrenceRule;
19
+ reminderMinutesBeforeStart?: number;
20
+ organizerAddress: string;
21
+ attendees: Attendee[];
22
+ }
23
+ /** Degrades to empty-looking values for a `"calendarEvent:<uid>"` target whose real `CalendarEvent` has since
24
+ * vanished (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
25
+ * `MessageTarget.resolveMessageInfo`/`FolderTarget.resolveFolderInfo` already apply. */
26
+ export declare function resolveCalendarEventInfo(target: string, calendarEventRepo: RepoUtils<any>): Promise<CalendarEventTargetInfo>;
27
+ /** Resolves the calendar events directly in `folderUid`, as `"calendarEvent:<uid>"` target strings, for
28
+ * `RopGetContentsTable` against a `CALENDAR`-type folder. */
29
+ export declare function resolveFolderCalendarEvents(folderUid: string, calendarEventRepo: RepoUtils<any>): 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
+ /**
3
+ * The `RopGetContentsTable`/`RopOpenMessage` analog of `MessageTarget.ts`, for a `Folder` of type `CONTACTS`:
4
+ * resolves a `"contact:<uid>"` row/handle target into the display data a contact-item row or an opened
5
+ * address-card's properties need. A `Contact` is addressed by MID exactly the same way a `Message`/
6
+ * `CalendarEvent` is - `MessageTarget.assignOrGetMid`/`session.messageIds` are reused as-is (see that file's own
7
+ * doc comment), so this file adds no MID-registry code of its own.
8
+ *
9
+ * Read-only in this pragmatic subset - a client editing/creating a Contact via MAPI (`RopSetProperties`/
10
+ * `RopSaveChangesMessage` against a `"contact:"` handle) is a documented gap, the same "browse only" limitation
11
+ * this codebase already accepts for Tasks (`TaskTarget.ts`); NSPI's own `Bind`/`Unbind`/`GetMatches` already
12
+ * covers the "search the GAL" case this data also backs.
13
+ */
14
+ export interface ContactTargetInfo {
15
+ displayName: string;
16
+ givenName?: string;
17
+ surname?: string;
18
+ email?: string;
19
+ businessPhone?: string;
20
+ companyName?: string;
21
+ jobTitle?: string;
22
+ }
23
+ /** Degrades to empty-looking values for a `"contact:<uid>"` target whose real `Contact` has since vanished
24
+ * (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
25
+ * `MessageTarget.resolveMessageInfo`/`CalendarEventTarget.resolveCalendarEventInfo` already apply. */
26
+ export declare function resolveContactInfo(target: string, contactRepo: RepoUtils<any>): Promise<ContactTargetInfo>;
27
+ /** Resolves the contacts directly in `folderUid`, as `"contact:<uid>"` target strings, for
28
+ * `RopGetContentsTable` against a `CONTACTS`-type folder. */
29
+ export declare function resolveFolderContacts(folderUid: string, contactRepo: RepoUtils<any>): Promise<string[]>;
@@ -0,0 +1,18 @@
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
+ /**
9
+ * Builds the complete FastTransfer stream for `handle` (a `"folder"` or `"message"` Server object). `columns`,
10
+ * when given (a `RopFastTransferSourceCopyProperties` explicit include list), replaces every default column set
11
+ * uniformly at both folder- and message-level; `excludePropertyIds` (a `RopFastTransferSourceCopyTo` exclude
12
+ * list) is only ever applied to this pragmatic subset's own default columns, per the same reasoning.
13
+ */
14
+ export declare function buildFastTransferStream(handle: MapiObjectHandle, context: RopContext, options?: {
15
+ columns?: PropertyColumn[];
16
+ excludePropertyIds?: ReadonlySet<number>;
17
+ }): Promise<Buffer>;
18
+ export {};
@@ -0,0 +1,63 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import { Folder } from "@rapidmx/restapi";
3
+ import type { MapiSessionContext } from "../MapiSessionManager.js";
4
+ export interface FolderTargetInfo {
5
+ displayName: string;
6
+ unreadCount: number;
7
+ totalCount: number;
8
+ hasChildren: boolean;
9
+ }
10
+ /** An optional, purely request-scoped (never persisted to the session) memo for `resolveFolderChildren`'s own
11
+ * full-mailbox `folderRepo.find({ mailboxUid })` fetch - see that function's own doc comment for why this
12
+ * matters. Deliberately just a plain object a caller creates fresh per ROP-handler invocation and threads
13
+ * through every `resolveFolderInfo`/`resolvePropertyValues` call in that same batch, not session state - unlike
14
+ * the FID/MID/named-property registries, there's no cross-request reuse to gain here (a mailbox's folder list
15
+ * can change between requests), only cross-*row* reuse within one table page. */
16
+ export interface FolderResolutionCache {
17
+ allFolders?: Folder[];
18
+ }
19
+ /** Resolves a target string into the display data a folder-table row needs. A `"folder:<uid>"` target whose
20
+ * `Folder` has since been deleted (soft-deleted or otherwise vanished) degrades to empty-looking values rather
21
+ * than throwing - the row simply won't be interesting to a client, not a reason to fail the whole ROP. */
22
+ export declare function resolveFolderInfo(mailboxUid: string, target: string, folderRepo: RepoUtils<any>, cache?: FolderResolutionCache): Promise<FolderTargetInfo>;
23
+ /**
24
+ * Resolves the direct children of `target`, as an array of the same target-string format, for
25
+ * `RopGetHierarchyTable`. Only `"virtual:root"`/`"virtual:ipmSubtree"` (this mailbox's top-level real
26
+ * folders - both collapse to the same "top of the visible tree" concept in this pragmatic subset, see
27
+ * `RopLogonHandler`'s own doc comment) and a real folder (its own real children) have any children at all;
28
+ * every other virtual folder (Deferred Action, Spooler Queue, ...) is permanently empty, since this data
29
+ * model has no concept of nesting anything under them.
30
+ *
31
+ * Filters an already-fetched full folder list in application code rather than querying by
32
+ * `parentFolderUid: undefined` directly - query-DSL semantics for "field is unset" aren't reliably consistent
33
+ * across backends (the exact kind of gap this project's own testing philosophy has caught before, e.g. `$or`
34
+ * being Mongo-only), so filtering a fetched array sidesteps the question entirely rather than risking it.
35
+ * The comparison itself uses `== null` (matching both `null` and `undefined`), not `=== undefined` - a
36
+ * top-level folder's unset `parentFolderUid` round-trips as genuine `undefined` from Mongo but as `null` from
37
+ * a SQL `nullable` column, a real, previously-confirmed cross-backend discrepancy in this codebase (caught by
38
+ * this exact test against a real SQLite-backed server, not assumed).
39
+ *
40
+ * **`cache`**: without it, this fetches every folder in the mailbox on every call - fine for the single call
41
+ * `RopGetHierarchyTableHandler` makes to build a table, but `resolveFolderInfo` also calls this once per row
42
+ * just to compute `hasChildren`, and a table page can hold many rows. A caller resolving a whole page (
43
+ * `RopQueryRowsHandler`/`RopGetPropertiesSpecificHandler`) passes one shared cache object through every call in
44
+ * that batch so the full list is fetched at most once per batch instead of once per row - see
45
+ * `FolderResolutionCache`'s own doc comment for why this isn't session state.
46
+ */
47
+ export declare function resolveFolderChildren(mailboxUid: string, target: string, folderRepo: RepoUtils<any>, cache?: FolderResolutionCache): Promise<string[]>;
48
+ /**
49
+ * Returns `target`'s existing FID if `RopLogon` or an earlier `RopGetHierarchyTable` row already assigned
50
+ * one, otherwise assigns and remembers the next free small integer FID. This is what lets a client
51
+ * `RopOpenFolder` a child folder it only ever learned about via a `RopQueryRows` row's `PidTagFolderId`
52
+ * column - without this, only the 13 `RopLogon`-time special folders could ever be opened.
53
+ *
54
+ * Backed by `session.folderTargetIds` (target -> FID) and `session.nextFolderId`, an O(1) reverse index/counter
55
+ * pair rather than a linear scan of `session.folderIds` plus a `Math.max(...spread)` over its keys - both real
56
+ * costs, not just theoretical ones: `folderTargetIds` only ever grows for a session's lifetime as a client
57
+ * browses more of a mailbox's folder tree, so the old scan-per-lookup approach was quadratic across a session
58
+ * that opens many folders, and `Math.max` spreading an unbounded array as call arguments risked a stack
59
+ * overflow past V8's argument-count limit on a mailbox with enough folders. `RopLogonHandler` keeps
60
+ * `folderTargetIds`/`nextFolderId` in sync with `folderIds` for the 13 special folders it assigns directly,
61
+ * rather than going through this function, since it assigns all 13 as one batch up front.
62
+ */
63
+ export declare function assignOrGetFid(session: MapiSessionContext, target: string): number;
@@ -0,0 +1,36 @@
1
+ import type { RopContext } from "./RopHandler.js";
2
+ /**
3
+ * Handles a submitted `"IPM.Schedule.Meeting.Resp.{Pos,Neg,Tent}"` message - an attendee's own response to a
4
+ * meeting invite this server previously sent (`RopSubmitMessageHandler.submitAppointment`). Delegated to from
5
+ * `RopSubmitMessageHandler` for that message-class prefix, instead of the ordinary mail or Appointment paths.
6
+ *
7
+ * Correlates the response back to the original `CalendarEvent` via `PidLidGlobalObjectId` (echoed by a real
8
+ * client from the invite it's responding to - see `GlobalObjectId.ts`'s own doc comment for why this requires
9
+ * no separate correlation table) and updates the caller's own `Attendee.responseStatus` in place, reusing the
10
+ * exact same matching-by-mailbox-address logic `MeetingResponseCommand` (EAS's own `MeetingResponse` handler)
11
+ * already implements - this is a from-scratch reimplementation of that small algorithm rather than a
12
+ * cross-import, since EAS and MAPI are independent protocol verticals in this codebase with no dependency
13
+ * between them.
14
+ *
15
+ * Every failure mode here (no `PidLidGlobalObjectId` set, no matching `CalendarEvent`, no resolvable mailbox,
16
+ * caller isn't actually an attendee, unrecognized message-class suffix) is a silent no-op rather than an error
17
+ * response - the same "don't fail the whole ROP over a stale/unresolvable reference" principle this pragmatic
18
+ * subset applies throughout (e.g. `MessageTarget.resolveMessageInfo`'s own doc comment). A response that can't
19
+ * be correlated or applied is simply dropped, not a reason to reject the client's `RopSubmitMessage` call.
20
+ *
21
+ * **The one query in this package that isn't `mailboxUid`-scoped, and why that's unavoidable here**: unlike
22
+ * every other `ignoreACL: true` repo call in this codebase (all transitively scoped to the caller's own
23
+ * mailbox via a session FID/MID registry that itself only ever gets populated from mailbox-scoped queries),
24
+ * `calendarEventRepo.find({ icalUid })` below deliberately searches *every* mailbox on this server - the
25
+ * `CalendarEvent` being responded to belongs to the meeting's *organizer*, who is very often a different
26
+ * mailbox than the attendee submitting this response, and the caller's own session has no way to know which
27
+ * mailbox that is. Safety instead comes from two independent properties: `icalUid` is a server-generated
28
+ * `crypto.randomUUID()` (see `RopSaveChangesMessageHandler.saveAppointment`), for all practical purposes
29
+ * globally unique, so this can't be used to enumerate or collide with another organizer's events; and the
30
+ * actual authorization gate is the attendee-membership check a few lines down (`callerAddresses.has(...)`),
31
+ * derived entirely from *this* caller's own resolved mailbox record, not from anything the client supplied -
32
+ * a crafted `PidLidGlobalObjectId` can at best name a real `icalUid` it doesn't already know the value of
33
+ * (astronomically unlikely against a random UUID) and still can't mutate an event unless the caller's own
34
+ * mailbox is genuinely a listed attendee on it.
35
+ */
36
+ export declare function submitMeetingResponse(messageClass: string, draftProperties: Record<string, string>, context: RopContext): Promise<void>;
@@ -0,0 +1,26 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import { type BlobStore } from "@rapidmx/restapi";
3
+ /** `PidTagBody` (`[MS-OXPROPS]`): property ID `0x1000`, `PtypString`. The only streamable property this
4
+ * pragmatic subset supports via `RopOpenStream`/`RopReadStream` - `PidTagHtml`/`PidTagRtfCompressed` are a
5
+ * documented gap (see `RopOpenStreamHandler`'s own doc comment). */
6
+ export declare const PID_TAG_BODY = 4096;
7
+ /**
8
+ * Resolves a `"message:<uid>"` target's plain-text body as the exact byte sequence `RopReadStream` must serve
9
+ * for a streamed `PtypString` property - confirmed via `[MS-OXCPRPT]`'s `RopOpenStream` page ("a string of
10
+ * Unicode characters in UTF-16LE format encoding with terminating null character"), the identical encoding
11
+ * `writePropertyValue()` already uses for an inline `PtypString` value, so this reuses
12
+ * `BufferWriter.writeNullTerminatedUtf16LE` rather than reimplementing it.
13
+ *
14
+ * Always derived from the message's raw MIME source (`bodyBlobKey`, via `mailparser` - the same fallback path
15
+ * `ItemOperationsCommand.fetchMessage()` already uses for EAS's own plain-text body delivery) rather than
16
+ * `sanitizedHtmlBlobKey` - `PidTagBody` is specifically the plain-text body per `[MS-OXPROPS]`, unlike EAS's
17
+ * `Body` element, which can carry either format tagged by its own `Type` field.
18
+ *
19
+ * Recomputed on every call rather than cached anywhere (including on the `"stream"` handle itself) -
20
+ * `MapiSessionContext` is serialized through `RedisCache`'s JSON round trip for multi-instance deployments, so
21
+ * a raw `Buffer` stored there wouldn't survive it (the same class of gap this codebase already documented for
22
+ * `Date` fields, see `MapiSessionContext`'s own doc comment). A documented, pragmatic trade-off: a large body
23
+ * read across several `RopReadStream` calls re-parses the MIME source each time rather than once - correct,
24
+ * not byte-perfect-efficient.
25
+ */
26
+ export declare function resolveMessageBodyBytes(target: string, messageRepo: RepoUtils<any>, blobStore: BlobStore): Promise<Buffer>;
@@ -0,0 +1,38 @@
1
+ import type { RepoUtils } from "@rapidrest/service-core";
2
+ import type { MapiSessionContext } from "../MapiSessionManager.js";
3
+ /**
4
+ * The `RopGetContentsTable` analog of `FolderTarget.ts`: resolves a `"message:<uid>"` row target (see
5
+ * `RopGetContentsTableHandler`'s own doc comment for why contents-table rows use this prefix instead of
6
+ * `FolderTarget.ts`'s `"folder:"`/`"virtual:"`) into the display data a message-table row needs.
7
+ */
8
+ export interface MessageTargetInfo {
9
+ subject: string;
10
+ read: boolean;
11
+ hasAttachments: boolean;
12
+ receivedDate: Date;
13
+ /** `Label.uid`s applied to this message (Gmail-style, independent of folder placement) - resolved to
14
+ * display names for `PidNameKeywords` (Outlook Categories) by `PropertyResolvers.ts`, which is where the
15
+ * `labelRepo` needed to translate a uid into a name lives. */
16
+ labelUids: string[];
17
+ }
18
+ /** Degrades to empty-looking values for a `"message:<uid>"` target whose real `Message` has since vanished
19
+ * (soft-deleted or otherwise), the same "don't fail the whole ROP over one stale row" principle
20
+ * `FolderTarget.resolveFolderInfo` already applies. */
21
+ export declare function resolveMessageInfo(target: string, messageRepo: RepoUtils<any>): Promise<MessageTargetInfo>;
22
+ /** Resolves the messages directly in `folderUid`, as `"message:<uid>"` target strings, for
23
+ * `RopGetContentsTable`. */
24
+ export declare function resolveFolderMessages(folderUid: string, messageRepo: RepoUtils<any>): Promise<string[]>;
25
+ /**
26
+ * Returns `target`'s existing MID if an earlier `RopQueryRows` row already assigned one, otherwise assigns and
27
+ * remembers the next free small integer MID. The exact `FolderTarget.assignOrGetFid` pattern, adapted for
28
+ * messages: this is what lets a client `RopOpenMessage` a message it only ever learned about via a
29
+ * `RopQueryRows` row's `PidTagMid` column.
30
+ *
31
+ * Backed by `session.messageTargetIds` (target -> MID) and `session.nextMessageId`, an O(1) reverse index/
32
+ * counter pair rather than a linear scan of `session.messageIds` plus a `Math.max(...spread)` over its keys -
33
+ * see `FolderTarget.assignOrGetFid`'s own doc comment for why both of those were real costs (not just
34
+ * theoretical ones) at real mailbox/session scale: `messageIds` only ever grows for a session's lifetime as a
35
+ * client pages through a mailbox, so a linear-scan lookup repeated once per row is quadratic over a session
36
+ * that pages through many messages.
37
+ */
38
+ export declare function assignOrGetMid(session: MapiSessionContext, target: string): number;
@@ -0,0 +1,46 @@
1
+ import type { MapiSessionContext } from "../MapiSessionManager.js";
2
+ /** A `PropertyName` (`[MS-OXCDATA]` §2.6.1): identifies a named property by a property-set `guid` plus either a
3
+ * numeric `lid` (`kind: "lid"`) or a string `name` (`kind: "name"`) - never both. `guid` is the textual form
4
+ * `MapiGuid.ts`'s `encodeGuid`/`decodeGuid` already produce/consume (the wire `PropertyName` structure's own
5
+ * `GUID` field is a `FlatUID` - confirmed, per its own spec page, to be byte-identical to the same little-endian
6
+ * `Data1`/`Data2`/`Data3`+as-is-`Data4` layout `MapiGuid.ts` already implements, so no second GUID codec is
7
+ * needed here). */
8
+ export interface PropertyName {
9
+ guid: string;
10
+ kind: "lid" | "name";
11
+ lid?: number;
12
+ name?: string;
13
+ }
14
+ /**
15
+ * Returns `propertyName`'s existing numeric property ID if an earlier `RopGetPropertyIdsFromNames` call in
16
+ * this session already assigned one, otherwise assigns and remembers the next free ID starting at
17
+ * `FIRST_NAMED_PROPERTY_ID`. This pragmatic subset always behaves as if the request's own `Flags` field
18
+ * requested "assign a new ID if unmapped" (`0x02`) - real Exchange's alternative (`0x00`, "only return
19
+ * already-mapped IDs") exists to let a client probe without committing a mailbox-wide registration; since this
20
+ * registry is already only ever session-scoped (not a real persisted per-mailbox mapping table), there is no
21
+ * meaningful difference between "probe" and "assign" here.
22
+ *
23
+ * Backed by `session.namedProperties`/`namedPropertyIds` (a forward and reverse map, kept in sync) and
24
+ * `session.nextNamedPropertyId`, an O(1) lookup/assignment pair rather than a linear scan plus a
25
+ * `Math.max(...spread)` over `Object.values(session.namedProperties)` - both real costs at scale: the spread
26
+ * form risks a `RangeError: Maximum call stack size exceeded` once a session has registered enough distinct
27
+ * names to exceed V8's function-argument-count limit, and the registry only ever grows for a session's
28
+ * lifetime, so a linear scan repeated once per property per row is quadratic over a session that resolves many
29
+ * named properties.
30
+ *
31
+ * Once `nextNamedPropertyId` would exceed `LAST_NAMED_PROPERTY_ID` (32,768 distinct names already registered
32
+ * this session - the entire `0x8000`-`0xFFFF` numeric ID space this pragmatic subset has to hand out), a *new*
33
+ * name can no longer be assigned a real ID; this returns `0x0000` for it instead of throwing, the exact value
34
+ * `[MS-OXCPRPT]`'s own `RopGetPropertyIdsFromNames` processing rules already use for "this `PropertyName`
35
+ * could not be resolved" (the same value this pragmatic subset already produces for a `Kind = 0xFF` entry) -
36
+ * not a new failure mode, just the same one applied to a different unmappable case. A name already registered
37
+ * before the registry filled up keeps returning its real, previously-assigned ID.
38
+ */
39
+ export declare function assignOrGetNamedPropertyId(session: MapiSessionContext, propertyName: PropertyName): number;
40
+ /** The reverse lookup `RopSetProperties`/`RopGetPropertiesSpecific`/`RopQueryRows` use to recognize an incoming
41
+ * property ID `>= 0x8000` as one of this session's own mapped named properties - an O(1) lookup against
42
+ * `session.namedPropertyIds` (kept in sync by `assignOrGetNamedPropertyId`) rather than a linear scan of
43
+ * `session.namedProperties` with a `JSON.parse` per candidate - see that function's own doc comment for why
44
+ * this matters at scale, particularly since this is called once per named-property column per row from
45
+ * `RopQueryRows`. */
46
+ export declare function resolveNamedProperty(session: MapiSessionContext, propertyId: number): PropertyName | undefined;
@@ -0,0 +1,60 @@
1
+ import { PropertyType, PropertyValueData } from "../codec/PropertyValue.js";
2
+ import type { MapiSessionContext } from "../MapiSessionManager.js";
3
+ import { CalendarEventTargetInfo } from "./CalendarEventTarget.js";
4
+ import { ContactTargetInfo } from "./ContactTarget.js";
5
+ import { FolderResolutionCache, FolderTargetInfo } from "./FolderTarget.js";
6
+ import { MessageTargetInfo } from "./MessageTarget.js";
7
+ import type { RopContext } from "./RopHandler.js";
8
+ import { TaskTargetInfo } from "./TaskTarget.js";
9
+ /** A type-appropriate zero/empty value for a requested property this handler has no real data for - keeps
10
+ * `StandardPropertyRow` encoding valid (a real value of the right type, per `writePropertyValue`'s
11
+ * expectations) without needing to model every property a client could ever ask for. Shared by
12
+ * `RopQueryRowsHandler` (table rows) and `RopGetPropertiesSpecificHandler` (single-object property fetch). */
13
+ export declare function defaultValueForType(propertyType: PropertyType): PropertyValueData;
14
+ /** Resolves one requested property's value for a `"folder:<uid>"`/`"virtual:<name>"` target. */
15
+ export declare function folderValueFor(session: MapiSessionContext, propertyId: number, propertyType: PropertyType, target: string, info: FolderTargetInfo): PropertyValueData;
16
+ /** Resolves one requested property's value for a `"message:<uid>"` target. `labelNames` is this message's own
17
+ * `labelUids` already resolved to display names (see `resolvePropertyValues`'s own doc comment) - a plain
18
+ * parameter rather than a repo lookup here, since this function (like every other `xxxValueFor`) is
19
+ * synchronous and can't do its own async DB access. */
20
+ export declare function messageValueFor(session: MapiSessionContext, propertyId: number, propertyType: PropertyType, target: string, info: MessageTargetInfo, labelNames?: string[]): PropertyValueData;
21
+ /** Resolves one requested property's value for a `"calendarEvent:<uid>"` target. Almost every Appointment
22
+ * property is a *named* property (`PidLid*`, no fixed numeric ID) rather than a plain `PidTag*` - a property ID
23
+ * `>= 0x8000` is resolved back to its `(PropertySet GUID, LID)` identity via `resolveNamedProperty` before this
24
+ * can dispatch on it (see `NamedPropertyRegistry.ts`'s own doc comment and the architecture plan's "Calendar
25
+ * support" section for the full named-property table this switch implements). */
26
+ export declare function calendarEventValueFor(session: MapiSessionContext, propertyId: number, propertyType: PropertyType, target: string, info: CalendarEventTargetInfo, callerAddress: string): PropertyValueData;
27
+ /** Resolves one requested property's value for a `"contact:<uid>"` target - a `Folder` of type `CONTACTS`'s
28
+ * content-table rows/opened items. Every field here is a plain fixed `PidTag` (`[MS-OXOCNTC]`), unlike Calendar/
29
+ * Task's named-property tables, since Contact's own well-known fields all have real numeric IDs. */
30
+ export declare function contactValueFor(session: MapiSessionContext, propertyId: number, propertyType: PropertyType, target: string, info: ContactTargetInfo): PropertyValueData;
31
+ /** Resolves one requested property's value for a `"task:<uid>"` target - a `Folder` of type `TASKS`'s
32
+ * content-table rows/opened items. Almost every Task-specific property is a *named* property (`PidLid*`) under
33
+ * `PSETID_Task`, resolved back to its `(PropertySet GUID, LID)` identity via `resolveNamedProperty` exactly the
34
+ * same way `calendarEventValueFor` handles `PSETID_Appointment`/`PSETID_Common` - see `TaskNamedProperties.ts`'s
35
+ * own doc comment for the LID table this switch implements. */
36
+ export declare function taskValueFor(session: MapiSessionContext, propertyId: number, propertyType: PropertyType, target: string, info: TaskTargetInfo): PropertyValueData;
37
+ /** An optional, purely request-scoped (never persisted) memo shared across every `resolvePropertyValues` call
38
+ * within one ROP handler invocation - e.g. one `RopQueryRows` call resolving many table rows, or the (rarer)
39
+ * case of `RopGetPropertiesSpecific` resolving many columns for one object. Without it, both `allFolders` (via
40
+ * `resolveFolderInfo`'s own `hasChildren` computation) and `callerAddress` (the calendar organizer/attendee
41
+ * lookup below) would be re-fetched from the repo on every single row even though they can only ever have one
42
+ * value for the whole batch (this mailbox's folder list, this mailbox's own primary address) - a real,
43
+ * measurable cost for a hierarchy/calendar table with many rows, not just a theoretical one. A caller that
44
+ * resolves only a single target (`RopGetPropertiesSpecificHandler`) can simply omit this - resolving without a
45
+ * cache is still correct, just not deduplicated across calls it doesn't make anyway. */
46
+ export interface ResolutionCache extends FolderResolutionCache {
47
+ callerAddress?: string;
48
+ /** `Label.uid` -> `Label.name`, for the whole mailbox - built at most once per call the same way
49
+ * `allFolders`/`callerAddress` are, the first time a message row actually has a non-empty `labelUids` to
50
+ * resolve (most messages have none, so this stays unbuilt for a page with no labeled messages at all). */
51
+ labelNamesByUid?: Map<string, string>;
52
+ }
53
+ /** Resolves every column in `columns` for a single `target` (a `"folder:"`/`"virtual:"`/`"message:"`/
54
+ * `"calendarEvent:"`/`"contact:"`/`"task:"` target string), in order - the shared implementation behind both
55
+ * `RopQueryRowsHandler` (one call per table row) and `RopGetPropertiesSpecificHandler` (one call for the single
56
+ * object a handle refers to). */
57
+ export declare function resolvePropertyValues(target: string, columns: {
58
+ propertyId: number;
59
+ propertyType: PropertyType;
60
+ }[], context: Pick<RopContext, "mailboxUid" | "session" | "folderRepo" | "messageRepo" | "calendarEventRepo" | "mailboxRepo" | "contactRepo" | "taskRepo" | "labelRepo">, cache?: ResolutionCache): Promise<PropertyValueData[]>;
@@ -0,0 +1,23 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopCreateMessage` (`[MS-OXCMSG]`/`[MS-OXCROPS]`): begins composing a new message in a folder, producing a
5
+ * new `"message"` Server object handle later ROPs (`RopSetProperties`, `RopOpenStream`, `RopSaveChangesMessage`)
6
+ * reference by `InputHandleIndex`. Unlike `RopOpenMessage`'s handle (an already-real, already-saved message),
7
+ * this handle's `entityUid` is deliberately left `""` - there is no real backing `Message` row, and won't be
8
+ * one until `RopSubmitMessage` actually sends it (this pragmatic subset has no separate Drafts-folder
9
+ * persistence step; see `RopSaveChangesMessageHandler`'s own doc comment). `draftFolderUid` remembers the
10
+ * target folder for that eventual send, and `draftProperties` starts empty, filled in by later
11
+ * `RopSetProperties`/`RopWriteStream` calls.
12
+ *
13
+ * `CodePageId` is decoded to advance past it correctly but not otherwise acted on (this pragmatic subset always
14
+ * encodes strings as UTF-16LE/UTF-8 directly, the same treatment `RopOpenMessageHandler` gives it).
15
+ * `AssociatedFlag` (FAI messages - hidden configuration/rules objects, not real mail) is decoded but not
16
+ * honored - this pragmatic subset's compose/send path only ever produces ordinary mail messages.
17
+ *
18
+ * @author Jean-Philippe Steinmetz
19
+ */
20
+ export declare class RopCreateMessageHandler implements RopHandler {
21
+ readonly ropId = 6;
22
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): void;
23
+ }
@@ -0,0 +1,38 @@
1
+ import type { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
2
+ import type { RopContext, RopHandler } from "./RopHandler.js";
3
+ /**
4
+ * `RopDeleteFolder` (`[MS-OXCFOLD]`/`[MS-OXCROPS]`, RopId `0x1D`): deletes a folder by `FolderId`. Reuses
5
+ * `RecoverableRepoUtils.delete()` exactly as-built in Phase 2 for `Folder`/`Message`/`CalendarEvent` - see
6
+ * `RopDeleteMessagesHandler.ts`'s own doc comment for why that matters (EAS's watermark-based incremental
7
+ * delete detection depends on it).
8
+ *
9
+ * **Real spec semantics honored, not simplified away**: per `[MS-OXCFOLD]`, `RopDeleteFolder` only operates on
10
+ * an empty folder by default - `DEL_MESSAGES` must be set to also delete the folder's own messages/calendar
11
+ * events/contacts/tasks, and `DEL_FOLDERS` to also delete (recursively) its subfolders; a non-empty folder
12
+ * deleted without the matching flag fails rather than silently cascading. `DELETE_HARD_DELETE` maps directly
13
+ * onto `RepoDeleteOptions.purge` - real, exact semantic overlap with this library's own soft-delete model, not
14
+ * a coincidence this codec papers over.
15
+ *
16
+ * **Contacts/Tasks folders**: emptiness is checked against `contactRepo`/`taskRepo` alongside `messageRepo`/
17
+ * `calendarEventRepo`, and both are included in the delete cascade - a `CONTACTS`/`TASKS` folder's real content
18
+ * lives in those repos (see `ContactTarget.ts`/`TaskTarget.ts`), not `messageRepo`, so checking only the latter
19
+ * would let a non-empty Contacts/Tasks folder be deleted without `DEL_MESSAGES` and orphan its rows. Guarded
20
+ * with `context.contactRepo`/`taskRepo` presence checks the same way `RopGetContentsTableHandler` does, since
21
+ * `RopContext`'s doc comment documents both as optional.
22
+ *
23
+ * The request's own `InputHandleIndex` (nominally the *parent* folder of the one being deleted) is validated to
24
+ * be a real, open folder handle but not cross-checked against the target folder's actual `parentFolderUid` -
25
+ * the same pragmatic simplification `RopOpenMessageHandler`'s own doc comment documents for its analogous
26
+ * `FolderId` field.
27
+ *
28
+ * @author Jean-Philippe Steinmetz
29
+ */
30
+ export declare class RopDeleteFolderHandler implements RopHandler {
31
+ readonly ropId = 29;
32
+ handle(reader: BufferReader, writer: BufferWriter, context: RopContext): Promise<void>;
33
+ /** Cascades a `DEL_FOLDERS` delete into `folderUid`'s own messages/calendar events/subfolders before
34
+ * deleting it - this data model's folder hierarchy is a plain parent-pointer tree (never a graph), so
35
+ * unbounded recursion here needs no cycle protection, the same assumption `FolderTarget.resolveFolderChildren`
36
+ * already makes. */
37
+ private deleteFolderRecursive;
38
+ }