@rapidmx/activesync-plugin 1.0.0-beta.2 → 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 (201) hide show
  1. package/README.md +4 -4
  2. package/dist/lib/BaseDeviceSyncStateRoute.js +32 -0
  3. package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -1
  4. package/dist/lib/BaseEasRoute.js +91 -15
  5. package/dist/lib/BaseEasRoute.js.map +1 -1
  6. package/dist/lib/EasAuditLog.js +56 -0
  7. package/dist/lib/EasAuditLog.js.map +1 -0
  8. package/dist/lib/EasCollectionLease.js +199 -0
  9. package/dist/lib/EasCollectionLease.js.map +1 -0
  10. package/dist/lib/EasCollectionStore.js +102 -0
  11. package/dist/lib/EasCollectionStore.js.map +1 -0
  12. package/dist/lib/EasCollectionSync.js +294 -0
  13. package/dist/lib/EasCollectionSync.js.map +1 -0
  14. package/dist/lib/EasSyncKeyUtils.js +125 -62
  15. package/dist/lib/EasSyncKeyUtils.js.map +1 -1
  16. package/dist/lib/MessageMoveRules.js +100 -0
  17. package/dist/lib/MessageMoveRules.js.map +1 -0
  18. package/dist/lib/MimeHeaderUtils.js +395 -0
  19. package/dist/lib/MimeHeaderUtils.js.map +1 -0
  20. package/dist/lib/RegexPatternUtils.js +30 -0
  21. package/dist/lib/RegexPatternUtils.js.map +1 -0
  22. package/dist/lib/RestapiCompat.js +41 -0
  23. package/dist/lib/RestapiCompat.js.map +1 -0
  24. package/dist/lib/adapters/CalendarSyncAdapter.js +142 -33
  25. package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -1
  26. package/dist/lib/adapters/EmailSyncAdapter.js +114 -27
  27. package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -1
  28. package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js +2 -1
  29. package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js.map +1 -1
  30. package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js +2 -1
  31. package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js.map +1 -1
  32. package/dist/lib/codec/WbxmlDecoder.js +86 -34
  33. package/dist/lib/codec/WbxmlDecoder.js.map +1 -1
  34. package/dist/lib/codec/WbxmlEncoder.js +103 -22
  35. package/dist/lib/codec/WbxmlEncoder.js.map +1 -1
  36. package/dist/lib/commands/ComposeMailCommand.js +105 -31
  37. package/dist/lib/commands/ComposeMailCommand.js.map +1 -1
  38. package/dist/lib/commands/FolderSyncCommand.js +67 -27
  39. package/dist/lib/commands/FolderSyncCommand.js.map +1 -1
  40. package/dist/lib/commands/GetItemEstimateCommand.js +80 -59
  41. package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -1
  42. package/dist/lib/commands/ItemOperationsCommand.js +219 -56
  43. package/dist/lib/commands/ItemOperationsCommand.js.map +1 -1
  44. package/dist/lib/commands/MeetingResponseCommand.js +212 -53
  45. package/dist/lib/commands/MeetingResponseCommand.js.map +1 -1
  46. package/dist/lib/commands/MoveItemsCommand.js +54 -21
  47. package/dist/lib/commands/MoveItemsCommand.js.map +1 -1
  48. package/dist/lib/commands/PingCommand.js +288 -56
  49. package/dist/lib/commands/PingCommand.js.map +1 -1
  50. package/dist/lib/commands/ProvisionCommand.js +34 -17
  51. package/dist/lib/commands/ProvisionCommand.js.map +1 -1
  52. package/dist/lib/commands/ResolveRecipientsCommand.js +43 -5
  53. package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -1
  54. package/dist/lib/commands/SearchCommand.js +92 -17
  55. package/dist/lib/commands/SearchCommand.js.map +1 -1
  56. package/dist/lib/commands/SettingsCommand.js +2 -1
  57. package/dist/lib/commands/SettingsCommand.js.map +1 -1
  58. package/dist/lib/commands/SmartForwardCommand.js +2 -1
  59. package/dist/lib/commands/SmartForwardCommand.js.map +1 -1
  60. package/dist/lib/commands/SmartReplyCommand.js +2 -1
  61. package/dist/lib/commands/SmartReplyCommand.js.map +1 -1
  62. package/dist/lib/commands/SyncCommand.js +481 -244
  63. package/dist/lib/commands/SyncCommand.js.map +1 -1
  64. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +6 -1
  65. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -1
  66. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +3 -1
  67. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -1
  68. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +2 -1
  69. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -1
  70. package/dist/lib/commands/mongo/PingCommandMongo.js +23 -0
  71. package/dist/lib/commands/mongo/PingCommandMongo.js.map +1 -0
  72. package/dist/lib/commands/mongo/SearchCommandMongo.js +3 -1
  73. package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -1
  74. package/dist/lib/commands/mongo/SendMailCommandMongo.js +2 -1
  75. package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -1
  76. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +2 -1
  77. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -1
  78. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +2 -1
  79. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -1
  80. package/dist/lib/commands/mongo/SyncCommandMongo.js +7 -1
  81. package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -1
  82. package/dist/lib/commands/mongo/index.js +1 -0
  83. package/dist/lib/commands/mongo/index.js.map +1 -1
  84. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +6 -1
  85. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -1
  86. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +3 -1
  87. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -1
  88. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +2 -1
  89. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -1
  90. package/dist/lib/commands/sql/PingCommandSQL.js +23 -0
  91. package/dist/lib/commands/sql/PingCommandSQL.js.map +1 -0
  92. package/dist/lib/commands/sql/SearchCommandSQL.js +3 -1
  93. package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -1
  94. package/dist/lib/commands/sql/SendMailCommandSQL.js +2 -1
  95. package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -1
  96. package/dist/lib/commands/sql/SmartForwardCommandSQL.js +2 -1
  97. package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -1
  98. package/dist/lib/commands/sql/SmartReplyCommandSQL.js +2 -1
  99. package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -1
  100. package/dist/lib/commands/sql/SyncCommandSQL.js +7 -1
  101. package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -1
  102. package/dist/lib/commands/sql/index.js +1 -0
  103. package/dist/lib/commands/sql/index.js.map +1 -1
  104. package/dist/lib/index.js +5 -0
  105. package/dist/lib/index.js.map +1 -1
  106. package/dist/lib/jobs/EasDeviceStateCleanupJob.js +44 -2
  107. package/dist/lib/jobs/EasDeviceStateCleanupJob.js.map +1 -1
  108. package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js +4 -0
  109. package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js.map +1 -1
  110. package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js +9 -0
  111. package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js.map +1 -1
  112. package/dist/lib/models/EasCollectionChunk.js +2 -0
  113. package/dist/lib/models/EasCollectionChunk.js.map +1 -0
  114. package/dist/lib/models/EasCollectionState.js +2 -0
  115. package/dist/lib/models/EasCollectionState.js.map +1 -0
  116. package/dist/lib/models/mongo/DeviceSyncStateMongo.js +7 -0
  117. package/dist/lib/models/mongo/DeviceSyncStateMongo.js.map +1 -1
  118. package/dist/lib/models/mongo/EasCollectionChunkMongo.js +83 -0
  119. package/dist/lib/models/mongo/EasCollectionChunkMongo.js.map +1 -0
  120. package/dist/lib/models/mongo/EasCollectionStateMongo.js +162 -0
  121. package/dist/lib/models/mongo/EasCollectionStateMongo.js.map +1 -0
  122. package/dist/lib/models/sql/DeviceSyncStateSQL.js +7 -0
  123. package/dist/lib/models/sql/DeviceSyncStateSQL.js.map +1 -1
  124. package/dist/lib/models/sql/EasCollectionChunkSQL.js +83 -0
  125. package/dist/lib/models/sql/EasCollectionChunkSQL.js.map +1 -0
  126. package/dist/lib/models/sql/EasCollectionStateSQL.js +162 -0
  127. package/dist/lib/models/sql/EasCollectionStateSQL.js.map +1 -0
  128. package/dist/lib/mongo/EasRouteMongo.js +2 -2
  129. package/dist/lib/mongo/EasRouteMongo.js.map +1 -1
  130. package/dist/lib/mongo.js +2 -0
  131. package/dist/lib/mongo.js.map +1 -1
  132. package/dist/lib/sql/EasRouteSQL.js +2 -2
  133. package/dist/lib/sql/EasRouteSQL.js.map +1 -1
  134. package/dist/lib/sql.js +2 -0
  135. package/dist/lib/sql.js.map +1 -1
  136. package/dist/types/BaseDeviceSyncStateRoute.d.ts +10 -0
  137. package/dist/types/BaseEasRoute.d.ts +15 -4
  138. package/dist/types/EasAuditLog.d.ts +47 -0
  139. package/dist/types/EasCollectionLease.d.ts +60 -0
  140. package/dist/types/EasCollectionStore.d.ts +51 -0
  141. package/dist/types/EasCollectionSync.d.ts +96 -0
  142. package/dist/types/EasCommandHandler.d.ts +7 -4
  143. package/dist/types/EasSyncKeyUtils.d.ts +73 -42
  144. package/dist/types/MessageMoveRules.d.ts +45 -0
  145. package/dist/types/MimeHeaderUtils.d.ts +73 -0
  146. package/dist/types/RegexPatternUtils.d.ts +14 -0
  147. package/dist/types/RestapiCompat.d.ts +24 -0
  148. package/dist/types/adapters/CalendarSyncAdapter.d.ts +47 -18
  149. package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +18 -2
  150. package/dist/types/adapters/EmailSyncAdapter.d.ts +24 -7
  151. package/dist/types/adapters/mongo/EmailSyncAdapterMongo.d.ts +1 -0
  152. package/dist/types/adapters/sql/EmailSyncAdapterSQL.d.ts +1 -0
  153. package/dist/types/codec/WbxmlDecoder.d.ts +39 -0
  154. package/dist/types/codec/WbxmlEncoder.d.ts +36 -1
  155. package/dist/types/commands/ComposeMailCommand.d.ts +33 -15
  156. package/dist/types/commands/FolderSyncCommand.d.ts +4 -1
  157. package/dist/types/commands/GetItemEstimateCommand.d.ts +20 -15
  158. package/dist/types/commands/ItemOperationsCommand.d.ts +29 -4
  159. package/dist/types/commands/MeetingResponseCommand.d.ts +52 -13
  160. package/dist/types/commands/MoveItemsCommand.d.ts +13 -3
  161. package/dist/types/commands/PingCommand.d.ts +72 -14
  162. package/dist/types/commands/ProvisionCommand.d.ts +16 -11
  163. package/dist/types/commands/ResolveRecipientsCommand.d.ts +4 -0
  164. package/dist/types/commands/SearchCommand.d.ts +13 -1
  165. package/dist/types/commands/SyncCommand.d.ts +102 -62
  166. package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +3 -0
  167. package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +2 -0
  168. package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +1 -0
  169. package/dist/types/commands/mongo/PingCommandMongo.d.ts +8 -0
  170. package/dist/types/commands/mongo/SearchCommandMongo.d.ts +2 -0
  171. package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +1 -0
  172. package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +1 -0
  173. package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +1 -0
  174. package/dist/types/commands/mongo/SyncCommandMongo.d.ts +4 -0
  175. package/dist/types/commands/mongo/index.d.ts +1 -0
  176. package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +3 -0
  177. package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +2 -0
  178. package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +1 -0
  179. package/dist/types/commands/sql/PingCommandSQL.d.ts +8 -0
  180. package/dist/types/commands/sql/SearchCommandSQL.d.ts +2 -0
  181. package/dist/types/commands/sql/SendMailCommandSQL.d.ts +1 -0
  182. package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +1 -0
  183. package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +1 -0
  184. package/dist/types/commands/sql/SyncCommandSQL.d.ts +4 -0
  185. package/dist/types/commands/sql/index.d.ts +1 -0
  186. package/dist/types/index.d.ts +5 -0
  187. package/dist/types/jobs/EasDeviceStateCleanupJob.d.ts +15 -0
  188. package/dist/types/jobs/mongo/EasDeviceStateCleanupJobMongo.d.ts +2 -0
  189. package/dist/types/jobs/sql/EasDeviceStateCleanupJobSQL.d.ts +4 -0
  190. package/dist/types/models/DeviceSyncState.d.ts +13 -2
  191. package/dist/types/models/EasCollectionChunk.d.ts +20 -0
  192. package/dist/types/models/EasCollectionState.d.ts +79 -0
  193. package/dist/types/models/mongo/DeviceSyncStateMongo.d.ts +1 -0
  194. package/dist/types/models/mongo/EasCollectionChunkMongo.d.ts +16 -0
  195. package/dist/types/models/mongo/EasCollectionStateMongo.d.ts +27 -0
  196. package/dist/types/models/sql/DeviceSyncStateSQL.d.ts +1 -0
  197. package/dist/types/models/sql/EasCollectionChunkSQL.d.ts +16 -0
  198. package/dist/types/models/sql/EasCollectionStateSQL.d.ts +27 -0
  199. package/dist/types/mongo.d.ts +2 -0
  200. package/dist/types/sql.d.ts +2 -0
  201. package/package.json +267 -238
@@ -1,33 +1,91 @@
1
1
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
2
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
3
+ /** Binds one MS-ASCMD `Class` value to the entity class whose rows a `Ping` checks for pending changes. */
4
+ export interface PingCollectionBinding {
5
+ entityClass: any;
6
+ }
3
7
  /**
4
8
  * Handles EAS `Ping`: a long-poll HTTP request that blocks for up to `HeartbeatInterval` seconds waiting for a
5
- * change in any of the client-specified folders, then reports which (if any) actually changed. No websocket
6
- * needed - subscribes a plain, request-scoped Redis client (the same construction `BasePushRoute.connect()`
7
- * uses, minus its own per-socket bookkeeping this one-shot request doesn't need) directly to the requested
8
- * `folderUid` channels. Every `Message`/`CalendarEvent`/etc. mutation already publishes to exactly these
9
- * channels via `BaseScopedChildRoute.notify()`/`RepoUtils`'s own push - `Ping` needs no new publish call site
10
- * of its own, only a subscriber.
9
+ * change in any of the client-specified folders, then reports which (if any) actually changed. Every
10
+ * `Message`/`CalendarEvent`/etc. mutation already publishes to the `folderUid` channels via
11
+ * `BaseScopedChildRoute.notify()`/`RepoUtils`'s own push - `Ping` only needs a subscriber.
12
+ *
13
+ * Resource bounds:
14
+ * - One shared Redis subscriber client per process (per Redis URL), lazily connected once. Each `Ping` attaches
15
+ * its own listener to its channels and removes exactly that listener when it settles. A connect/subscribe
16
+ * failure fails open (the `Ping` just waits out its heartbeat and reports no changes) and a failed connect is
17
+ * forgotten so a later `Ping` retries it.
18
+ * - At most one active `Ping` per (mailbox, device) per process: a newer one supersedes (answers Status 1) the
19
+ * older one.
20
+ * - The wait ends as soon as the HTTP response finishes (including a client abort).
21
+ * - More than `mail:eas:ping_max_folders` folders is rejected with Status 6 before any ACL work, and the ACL
22
+ * checks themselves run in bounded concurrent chunks.
23
+ * - Without a `datastores:events` config the `Ping` still waits the full (clamped) heartbeat before answering
24
+ * Status 1, so a device can't hot-loop against it.
25
+ *
26
+ * **Changes made before the `Ping` started**: a publish only reaches a subscriber that exists at that moment, so a
27
+ * change landing between the device's last `Sync` and this `Ping`'s subscribe would otherwise go unnoticed until
28
+ * the next change. Once subscribed (or right away, without Redis), each folder's stream is checked for a row after
29
+ * the cursor its `EasCollectionState` recorded, and any folder with one is answered with Status 2 immediately. This
30
+ * needs `collectionStateClass`/`collectionBindings` (set by `PingCommandMongo`/`PingCommandSQL`); a folder the
31
+ * device never synced has no cursor and isn't checked.
11
32
  *
12
33
  * Requested folder uids are filtered down to only those the caller currently has `READ` on before subscribing -
13
- * matching every other command's ownership check on a client-supplied id, since without it a device could
14
- * long-poll indefinitely on any folder uid it happens to know (including one it used to have legitimate access
15
- * to and no longer does) and learn purely from this channel when that other mailbox's data changes.
34
+ * without it a device could long-poll on any folder uid it happens to know (including one whose share was since
35
+ * revoked) and learn from this channel when that other mailbox's data changes.
16
36
  *
17
37
  * @author Jean-Philippe Steinmetz
18
38
  */
19
39
  export declare class PingCommand implements EasCommandHandler {
40
+ /** Shared subscriber connections, keyed by Redis URL - each value resolves once that client is connected. */
41
+ private static readonly subscribers;
42
+ /** The cancel function of the currently active `Ping` for each (mailbox, device) pair. */
43
+ private static readonly activePings;
20
44
  readonly command = "Ping";
21
45
  private redisConfig;
22
46
  private minHeartbeatSeconds;
23
47
  private maxHeartbeatSeconds;
48
+ private maxFolders;
24
49
  private aclUtils?;
50
+ /** Supplied by the Mongo/SQL subclasses; without them the pending-change check is skipped. */
51
+ protected collectionStateClass?: any;
52
+ protected collectionBindings: Record<string, PingCollectionBinding>;
53
+ private _objectFactory?;
54
+ private collectionStateRepo?;
55
+ private repos;
56
+ init(): Promise<void>;
57
+ /** Forgets all process-wide shared state (subscriber clients and active pings). Intended for tests. */
58
+ static resetSharedState(): void;
25
59
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
26
60
  private statusResponse;
27
- /** Subscribes to `folderUids` and resolves with whichever of them published a change first, or `[]` if
28
- * `timeoutSeconds` elapses with no publish. A missing `datastores:events` config (Redis pub/sub not set up
29
- * for this deployment) fails open to "no changes" rather than blocking forever or throwing - `Ping` is a
30
- * best-effort low-latency notification path, not the only way a client ever discovers new mail (it will
31
- * eventually poll a real `Sync`/`FolderSync` regardless). */
61
+ /** Returns the subset of `folderUids` the caller has `READ` on, checking at most `ACL_CHECK_CHUNK_SIZE` at once. */
62
+ private filterPermitted;
63
+ /**
64
+ * The subset of `folderUids` whose change stream has a row the device hasn't synced yet (after its collection's
65
+ * recorded cursor, other than the device's own writes). A failed lookup counts as "no pending change".
66
+ *
67
+ * Batched, so a `Ping` watching hundreds of folders costs a handful of queries rather than three per folder: every
68
+ * folder's `EasCollectionState` is read in one `in(...)` query per `STATE_LOOKUP_CHUNK`, and each collection class's
69
+ * folders are checked together (`pendingInClass()`); only a folder that batch can't decide is scanned on its own.
70
+ */
71
+ private pendingChanges;
72
+ /** Every requested folder's collection state for this device, keyed by folder uid - one query per chunk of plain
73
+ * folder uids (exact-matched in memory), and one query per folder uid that can't be listed in `in(...)`. */
74
+ private loadStates;
75
+ /**
76
+ * The folders among `states` (all of one collection class, stored in `repo`) with a pending change. One pair of
77
+ * queries (live and soft-deleted rows) reads, across all of them, the rows at or after the earliest cursor, in
78
+ * stream order, up to `PENDING_BATCH_ROW_LIMIT`. When neither page is full, that is every row that could be pending
79
+ * for any of the folders, so each folder is decided exactly in memory - the same rule as a per-folder `scanAfter()`.
80
+ * When a page is full, a folder with a pending row among the rows read is still pending; any other folder is scanned
81
+ * on its own, as before. A folder uid that can't be listed in `in(...)` is always scanned on its own.
82
+ */
83
+ private pendingInClass;
84
+ /** Returns the process-wide subscriber client for `url`, connecting it on first use. A failed connect is
85
+ * evicted from the cache (and the client destroyed) so a later call retries. */
86
+ private static getSubscriber;
87
+ /** Waits up to `timeoutSeconds` for a publish on any of `folderUids`, resolving with the channel that changed,
88
+ * or `[]` on timeout, supersede by a newer `Ping` from the same device, request close, or Redis being
89
+ * unavailable (in which case the full heartbeat is still waited). */
32
90
  private waitForChange;
33
91
  }
@@ -15,19 +15,25 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
15
15
  * no way for the server to inspect device state directly either).
16
16
  *
17
17
  * - **Request 1** (no `PolicyKey` in the body): mint a new policy key, store it on `DeviceSyncState` (not yet
18
- * provisioned), and send back the policy document under that key. If a `RemoteWipe` was requested for this
19
- * device (`DeviceSyncState.remoteWipeRequested`, set by an admin - see the remote-wipe route), skip normal
20
- * policy issuance entirely and send the `RemoteWipe` directive instead.
18
+ * provisioned), and send back the policy document under that key.
21
19
  * - **Request 2** (client echoes the `PolicyKey` back, acknowledging the policy): if the key matches what was
22
20
  * minted in request 1 *and* the client's own `Status` is `1`, mark the device provisioned and re-confirm the
23
21
  * same key; anything else (a stale/replayed key, a device that never actually saw request 1's response, or a
24
22
  * device reporting it could not comply) is rejected without provisioning.
23
+ * - **While a wipe is pending**, every other Provision request (a policy request *or* an acknowledgement of an
24
+ * older key) gets the `RemoteWipe` directive and never provisions. Requesting the wipe also clears the stored
25
+ * policy key (`BaseDeviceSyncStateRoute.remoteWipe`), and `BaseEasRoute` refuses any command whose presented
26
+ * `X-MS-PolicyKey` doesn't match the stored key, so a device can't keep syncing on its old key.
25
27
  * - **RemoteWipe acknowledgement**: after wiping itself, a device sends a bare `<Provision><RemoteWipe>
26
28
  * <Status>1</Status></RemoteWipe></Provision>` (no `Policies`). Detected first, ahead of the normal
27
- * issue/acknowledge branching. Clears `remoteWipeRequested` and stamps `remoteWipeAcknowledgedAt` for audit,
28
- * but deliberately leaves `provisioned` untouched (`false`, from when the wipe was requested) - the device
29
- * must complete a genuine fresh Provision handshake to re-add the account, it does not fall straight back into
30
- * "provisioned". `remoteWipeAccountOnly` is recorded for admin audit only; the wire directive sent to the
29
+ * issue/acknowledge branching, and ignored (Status 2) unless a wipe is actually pending. Clears `remoteWipeRequested`,
30
+ * stamps `remoteWipeAcknowledgedAt` for audit and sets `blocked`, leaving `provisioned` `false`.
31
+ * - **A blocked device** (one that acknowledged a wipe) is refused every Provision request with Status 129
32
+ * (DeviceIsBlockedForThisUser) - and `BaseEasRoute` refuses its other commands - until an administrator clears the
33
+ * flag (`BaseDeviceSyncStateRoute.unblock`). Otherwise a device (or whoever holds it) could acknowledge the wipe
34
+ * without wiping anything and simply provision again. The block (like the wipe itself) is keyed on the client-supplied
35
+ * `DeviceId`: a client that ignores the directive can pair again under another `DeviceId` with the same credentials -
36
+ * see `DeviceSyncState.blocked`; revoking the account's credentials is what stops that. `remoteWipeAccountOnly` is recorded for admin audit only; the wire directive sent to the
31
37
  * device is identical either way (a real "wipe just this account's data" vs. "wipe the whole device"
32
38
  * distinction would require an MDM-capable client extension this library doesn't implement).
33
39
  *
@@ -41,14 +47,13 @@ export declare class ProvisionCommand implements EasCommandHandler {
41
47
  private requireDeviceEncryption;
42
48
  private allowSimplePassword;
43
49
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
44
- /** Request 1: mint and store a new policy key, send the policy document - or, if a remote wipe is
45
- * pending for this device, the `RemoteWipe` directive instead. */
50
+ /** Request 1: mint and store a new policy key and send the policy document. */
46
51
  private issuePolicy;
47
52
  /** Request 2: the client acknowledges the policy key it was handed in request 1, self-reporting whether
48
53
  * it actually applied the policy via its own `Status`. */
49
54
  private acknowledgePolicy;
50
- /** The device has wiped itself and is acknowledging - clear the pending flag but leave `provisioned`
51
- * alone (still `false`, from when the wipe was requested) so a genuine re-provision is required. */
55
+ /** The device has wiped itself and is acknowledging - clear the pending flag and block the device until an
56
+ * administrator unblocks it; `provisioned` stays `false` (from when the wipe was requested). */
52
57
  private acknowledgeRemoteWipe;
53
58
  private persist;
54
59
  }
@@ -1,5 +1,7 @@
1
1
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
2
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
3
+ /** [MS-ASCMD]: a `ResolveRecipients` request MUST NOT contain more than 100 `To` elements. */
4
+ export declare const MAX_RESOLVE_RECIPIENTS_TO = 100;
3
5
  /**
4
6
  * Handles EAS `ResolveRecipients`: resolves each `<To>` value (a display name, partial name, or address) the
5
7
  * client is unsure how to reach against the mailbox's own `Contact` (GAL) store - the same substring-match
@@ -28,8 +30,10 @@ export declare abstract class ResolveRecipientsCommand implements EasCommandHand
28
30
  private maxMatches;
29
31
  private _objectFactory?;
30
32
  private contactRepo?;
33
+ private logger;
31
34
  init(): Promise<void>;
32
35
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
36
+ private statusOnly;
33
37
  private resolveOne;
34
38
  private recipientElement;
35
39
  private responseElement;
@@ -30,7 +30,12 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
30
30
  * request's own capped fetch actually returned" - a client requesting a `Range` past that cap sees fewer
31
31
  * results than may really exist, a documented approximation rather than exact server-side paging.
32
32
  *
33
- * `contactClass`/`messageClass`/`emailAdapterClass` are supplied by the Mongo/SQL concrete subclasses.
33
+ * **Audit**: the index is the caller's own mailbox's, but a hit is rendered from the stored message, wherever it is
34
+ * filed now. Results from a mailbox the caller doesn't own (restapi's `isNonOwnerAccess()`) are recorded as one
35
+ * `MESSAGE_CONTENT_ACCESSED` entry per such mailbox per request, listing the returned message uids (`EasAuditLog`).
36
+ *
37
+ * `contactClass`/`messageClass`/`emailAdapterClass`/`mailboxClass`/`auditLogClass` are supplied by the Mongo/SQL
38
+ * concrete subclasses.
34
39
  *
35
40
  * @author Jean-Philippe Steinmetz
36
41
  */
@@ -39,7 +44,12 @@ export declare abstract class SearchCommand implements EasCommandHandler {
39
44
  protected abstract contactClass: any;
40
45
  protected abstract messageClass: any;
41
46
  protected abstract emailAdapterClass: any;
47
+ protected abstract mailboxClass: any;
48
+ protected abstract auditLogClass: any;
42
49
  private _objectFactory?;
50
+ private config?;
51
+ private logger;
52
+ private mailboxRepo?;
43
53
  private contactRepo?;
44
54
  private messageRepo?;
45
55
  private emailAdapter?;
@@ -51,6 +61,8 @@ export declare abstract class SearchCommand implements EasCommandHandler {
51
61
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
52
62
  private handleGal;
53
63
  private handleMailbox;
64
+ /** One `MESSAGE_CONTENT_ACCESSED` entry per non-owner mailbox among `page`'s messages (see this class's doc comment). */
65
+ private auditResults;
54
66
  private contactToResult;
55
67
  /** Reuses `EmailSyncAdapter.toApplicationData()`'s own field mapping (Subject/From/To/Cc/Bcc/DateReceived/
56
68
  * Importance/Read/Flag/Body/ConversationId) for a Mailbox-store search hit's `Properties` - the same
@@ -1,100 +1,140 @@
1
1
  import { type RecoverableBaseEntity } from "@rapidrest/service-core";
2
2
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
3
3
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
4
+ /** Most `<Collection>`s one `Sync` request may carry - more is answered with a top-level Status 4. */
5
+ export declare const MAX_SYNC_COLLECTIONS = 300;
6
+ /** Most client `Add`/`Change`/`Delete` commands one collection of a `Sync` request may carry - more is answered
7
+ * with that collection's Status 4, without applying any of them. */
8
+ export declare const MAX_SYNC_COMMANDS_PER_COLLECTION = 512;
4
9
  /** Binds one MS-ASCMD `Class` value (`"Email"`, `"Contacts"`, ...) to the concrete entity class `SyncCommand`
5
10
  * should build a `RepoUtils` for, and the adapter class that maps that entity to/from `ApplicationData`.
6
- * Supplied by the Mongo/SQL concrete subclasses, one map entry per supported collection type.
7
- *
8
- * `adapterClass`, not a pre-built `adapter` instance: `SyncCommand.init()` instantiates each one itself via
9
- * `ObjectFactory`, so an adapter can `@Inject` its own dependencies (`EmailSyncAdapter` needs `BlobStore` for a
10
- * Draft's body) exactly like any other DI-managed class in this library - a bare `new EmailSyncAdapter()` has
11
- * no way to satisfy that. */
11
+ * Supplied by the Mongo/SQL concrete subclasses, one map entry per supported collection type. */
12
12
  export interface SyncCollectionBinding<T extends RecoverableBaseEntity> {
13
13
  entityClass: any;
14
14
  adapterClass: any;
15
15
  }
16
16
  /**
17
- * Handles EAS `Sync`: enumerates `Add`/`Change`/`Delete`s for a single folder's contents since the device's last
18
- * `Sync` of that folder, using the same watermark-based cursor mechanism `FolderSyncCommand` uses (via
19
- * `EasSyncKeyUtils`), scoped by `folderUid` instead of `mailboxUid`, and keyed per-folder in
20
- * `DeviceSyncState.folderSyncKeys` (the `CollectionId` a client sends *is* the `folderUid` - this library never
21
- * invents a separate collection identifier).
17
+ * Handles EAS `Sync` for `Email`/`Contacts`/`Calendar`/`Tasks` folders.
18
+ *
19
+ * **Per-collection state** lives in its own `EasCollectionState` row per (mailbox, device, folder) - see that
20
+ * model - rather than in `DeviceSyncState`, so concurrent `Sync`s of different folders never contend for one row.
21
+ * Besides the issued `SyncKey`, the row records exactly which items the device holds (inline in `serverIds` while
22
+ * small, in `EasCollectionChunk` rows once large - see `EasCollectionStore`). That is what makes the reported
23
+ * commands correct rather than guessed: an item the device doesn't hold is always an `Add` (including on the first
24
+ * round after `SyncKey 0`), an item it holds is a `Change`, and an item it holds that has been deleted *or moved to
25
+ * another folder* is a `Delete` - see `EasCollectionSync.enumerateCollection`.
26
+ *
27
+ * **One round per collection at a time**: each collection's round runs under a lease on (mailbox, device, folder)
28
+ * (`EasCollectionLease`: in-process, plus Redis `datastores:cache` across server copies when configured), and its
29
+ * state is read only once the lease is held - a second `Sync` of the same collection waits for the first to finish
30
+ * and then sees its result (typically answering the now-previous `SyncKey` as a retry), instead of both computing a
31
+ * round from the same state. A lease not acquired within `LEASE_WAIT_MS` is answered with Status 16 (retry).
32
+ *
33
+ * **Round order**: the client's own `Commands` are applied first, then server changes are enumerated. The device's
34
+ * own writes are not echoed back: each successful `Add`/`Change` records the resulting `dateModified` in the row's
35
+ * `echoes`, and a changed row still carrying exactly that timestamp is skipped. The cursor itself only ever advances
36
+ * past rows actually enumerated, never past a pending server change.
37
+ *
38
+ * **Lost state**: a round's state that can't be saved is answered with the collection's Status 3 (and a failed
39
+ * `SyncKey 0` restart with Status 5), never with a `SyncKey` the server doesn't have - the device re-syncs from
40
+ * scratch instead of continuing from a key whose held set was never recorded.
41
+ *
42
+ * **Retries**: a client that never received a response re-sends the `SyncKey` it still holds. The row keeps the
43
+ * previous round's key and delta (`previous`), so that key is accepted and the round is recomputed from the state
44
+ * before it; an `Add` re-sent with the same `ClientId` is answered with the item created the first time, and a
45
+ * `Delete` of an item that round already removed succeeds silently.
46
+ *
47
+ * **Options honoured**: `WindowSize` (capped by `mail:eas:sync_window_size` and 512), `FilterType` (age window for
48
+ * `Email`/`Calendar`, incomplete-only for `Tasks`; applied to items the device doesn't hold yet). A collection
49
+ * started without `Options` has no filter recorded, and the first `FilterType` sent while the device still holds
50
+ * nothing is adopted (clients commonly send `Options` only from the second request on); a `FilterType` differing
51
+ * from the recorded one otherwise gets Status 3 so the client re-syncs from 0. `DeletesAsMoves` (default `true`: an
52
+ * `Email` delete moves the message to Deleted Items; a delete inside Deleted Items, or with `DeletesAsMoves` `0`,
53
+ * deletes it) and `GetChanges` `0` (no server changes this round).
22
54
  *
23
- * **Multi-collection requests**: every `<Collection>` in a request's `<Collections>` is processed and gets its
24
- * own `<Collection>` entry in the response, each with its own independent `SyncKey`/`Status` - a client
25
- * syncing several folders in one round trip (the common case once the initial per-folder backlog is done)
26
- * gets one response covering all of them. All per-collection `SyncKey`/remembered-`Class` writes for the whole
27
- * request are batched into a single `persistDeviceSyncState` call after every collection has been processed
28
- * (never one call per collection) - see `EasSyncKeyUtils.persistDeviceSyncState`'s own doc comment for why a
29
- * second write to the same `DeviceSyncState` within one request must never be built off a stale copy.
55
+ * **Meetings**: a device deleting or editing an attendee's copy of someone else's meeting never makes restapi's
56
+ * `MeetingSchedulingJob` send cancellations/invitations as the organizer - see `CalendarSyncAdapter.beforeDelete`
57
+ * and its `fromApplicationData`.
30
58
  *
31
- * **`Class` is only required on a collection's first (`SyncKey "0"`) request**, per `[MS-ASCMD]` - once seen,
32
- * it's remembered in `DeviceSyncState.folderCollectionClasses` (keyed by `folderUid`) so a later request may
33
- * omit it; omitting it for a folder never previously synced still gets `Status 4` (nothing to fall back to).
59
+ * **Access**: every `CollectionId` needs `READ` on the folder (otherwise Status 4, indistinguishable from an unknown
60
+ * collection); `Add`/`Change`/`Delete` additionally need `CREATE`/`UPDATE`/`DELETE`, and a `ServerId` that resolves
61
+ * to an item outside the synced folder is reported as not found (Status 8). An `Email` `Add` is only accepted in
62
+ * a Drafts folder ([MS-ASCMD]: no non-draft email may be added by a client), and a new item's `mailboxUid` is the
63
+ * folder's own mailbox, so an item added to a shared folder belongs to that folder's mailbox. An `Email` body can only
64
+ * be changed on a genuine draft (`EmailSyncAdapter`), and a delete-as-move out of Outbox cancels the scheduled send
65
+ * (`MessageMoveRules.planMessageMove`; Status 6 once the message was relayed), and no delete at all happens while a send of
66
+ * the message is in flight (`hasLiveSendLease`, Status 6). Every update is version-checked on both
67
+ * backends (`asEntity`).
34
68
  *
35
- * **Every `CollectionId` is ACL-checked against the caller before it's touched**: `processCollection()` requires
36
- * `ACLAction.READ` on the folder before enumerating or accepting any Commands for it at all (a folder the caller
37
- * can't read is reported the same as an unrecognized collection - Status `4` - rather than leaking whether it
38
- * exists); `applyAdd`/`applyChange`/`applyDelete` additionally require `CREATE`/`UPDATE`/`DELETE` respectively,
39
- * and `applyChange`/`applyDelete` re-verify the resolved item's own `folderUid` actually matches the collection
40
- * being synced (treating a mismatch identically to "not found" - Status `8` - never revealing that the
41
- * `ServerId` resolves to something real elsewhere). Without this, a client could supply any other mailbox's
42
- * folder/item uid as its own `CollectionId`/`ServerId` and read or mutate that mailbox's data directly - the
43
- * same ownership-verification-after-an-`ignoreACL`-lookup pattern `ItemOperationsCommand`/`MoveItemsCommand`
44
- * already use, applied consistently here too.
69
+ * **Audit** (`EasAuditLog`), only in a mailbox the caller doesn't own (restapi's `isNonOwnerAccess()` - an administrator
70
+ * or a delegate syncing a shared folder), and only for `Email`: a round that sends `Add`/`Change` items records one
71
+ * `MESSAGE_CONTENT_ACCESSED` entry for the collection listing the sent message uids (at most a window's worth), rather
72
+ * than one per row; each successful client `Delete` records one `MESSAGE_DELETE` entry, as restapi's REST delete does.
45
73
  *
46
- * **Pragmatic subset, deliberately not the full MS-ASCMD `Sync` surface**:
47
- * - **Client-originated `Add`/`Change`/`Delete` commands are accepted for every collection type**, including
48
- * `Email` (a device creating/editing a Draft, or deleting a message locally - see `applyAdd`/`applyChange`/
49
- * `applyDelete`). `[MS-ASCMD]` itself disallows `Add`/`Change` for any *non-draft* `Email` item - this library
50
- * doesn't verify a Sync `Email` Add/Change actually targets the caller's own Drafts folder *specifically*
51
- * (only that it's a folder the caller actually owns/can write to - see above), matching how Contacts/Calendar/
52
- * Tasks folder targeting is equally unchecked beyond ownership elsewhere. A collection whose adapter has no
53
- * `fromApplicationData` at all would get Status `6` for `Add`/`Change` instead, but every adapter today
54
- * implements it. Per `[MS-ASCMD]`'s own "Add (Sync)"/"Status (Sync)" pages: `Add` always gets a `Responses`
55
- * entry (it must report the assigned `ServerId`); `Change`/`Delete` only get one on **failure** - a silent
56
- * success means "assume it worked."
57
- * - Only a body preview is returned per item (see `EmailSyncAdapter`'s own doc comment) - full body content is
58
- * fetched separately via `ItemOperationsCommand`. A Draft's `Email` Add/Change is plain-text-only, with no
59
- * attachment support (mirrors `ComposeMailCommand`'s own already-documented attachment gap).
74
+ * Per `[MS-ASCMD]`, `Add` always gets a `Responses` entry; `Change`/`Delete` only on failure.
60
75
  *
61
76
  * @author Jean-Philippe Steinmetz
62
77
  */
63
78
  export declare abstract class SyncCommand implements EasCommandHandler {
64
79
  readonly command = "Sync";
65
80
  protected abstract collectionBindings: Record<string, SyncCollectionBinding<any>>;
66
- /** Supplied by the Mongo/SQL concrete subclasses so a client-originated `Add`'s `newEntityDefaults()` can
67
- * be given the caller's own `Mailbox` (`EmailSyncAdapter` needs it for a new Draft's `from`) - same
68
- * one-line-per-backend pattern `MeetingResponseCommand`/`SettingsCommand` already use. */
81
+ /** Supplied by the Mongo/SQL concrete subclasses. */
69
82
  protected abstract mailboxClass: any;
83
+ protected abstract folderClass: any;
84
+ protected abstract collectionStateClass: any;
85
+ protected abstract collectionChunkClass: any;
86
+ protected abstract auditLogClass: any;
70
87
  private windowSize;
88
+ private cacheConfig;
89
+ private config?;
90
+ protected moveScanLimit: number;
91
+ protected reconcileLimit: number;
92
+ protected leaseWaitMs: number;
71
93
  private _objectFactory?;
72
94
  private aclUtils?;
95
+ private logger;
73
96
  private repos;
74
97
  private adapters;
75
98
  private mailboxRepo?;
99
+ private folderRepo?;
100
+ private collectionStateRepo?;
101
+ private collectionChunkRepo?;
76
102
  init(): Promise<void>;
77
- /** Resolves the caller's own `Mailbox` at most once per request, and only if actually needed - most `Sync`
78
- * requests contain no client-originated `Add` at all, so most requests never pay this extra round trip. */
103
+ private get chunkStore();
104
+ /** Resolves a mailbox at most once per request, and only if actually needed. */
79
105
  private mailboxLoader;
80
106
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
81
- /** Processes one `<Collection>` from the request into its own `<Collection>` response element, plus (when
82
- * this round advanced anything) the `folderUid`/new `SyncKey`/remembered `Class` for `handle()` to fold
83
- * into its single end-of-request `persistDeviceSyncState` call - this method itself never persists
84
- * anything, so it's safe to call once per collection in a request without the write-batching hazard
85
- * `EasSyncKeyUtils.persistDeviceSyncState`'s doc comment describes. */
107
+ private effectiveWindowSize;
86
108
  private processCollection;
87
- private itemToCommandElement;
88
- /** One client-originated command's outcome: `response` is a `Responses/{Add,Change,Delete}` entry to
89
- * include (per MS-ASCMD, always present for `Add`, only present on failure for `Change`/`Delete`);
90
- * `writtenAt` is the resulting `dateModified` of whatever was actually written, used to advance the
91
- * persisted watermark past this round's own writes (see `handle()`'s own comment on why). */
109
+ /** The rest of `processCollection`, run while holding the collection's lease. */
110
+ private processLocked;
111
+ /** `SyncKey 0`: (re)starts the collection with an empty item set. Per [MS-ASCMD] the response carries only the
112
+ * new key; the next round reports every item as an `Add`. */
113
+ private startCollection;
114
+ /**
115
+ * Writes the held set (`held.ids`, given the set `held.loaded` the round started from) and then creates or
116
+ * updates the collection's state row. Returns `false` - after logging - when anything failed (including losing a
117
+ * race for the row): the caller must then not hand out the new key. A `"restart"` empties the held set.
118
+ *
119
+ * **Chunk writes can't be atomic with the state row**, so before any chunk row is touched the state row is first
120
+ * marked `chunked` with both its `SyncKey` and its previous round's key blanked (`invalidateBeforeChunkWrite()`).
121
+ * If anything after that fails - including the final state write, and even when the device never sees this round's
122
+ * Status 3 - no key matches the half-written chunks any more: the device's next `Sync` gets Status 3 and restarts
123
+ * with `SyncKey 0`, which always removes every chunk row of the collection (whether or not the row says `chunked`,
124
+ * so rows orphaned before this rule existed are cleared too). A collection converting from inline to chunked also
125
+ * clears leftover chunk rows first, so an orphan can never collide with the unique `chunkIndex`.
126
+ */
127
+ private saveState;
128
+ /** Marks `stored` chunked with no acceptable `SyncKey` (current or previous) before its chunk rows are written - see
129
+ * `saveState()`. Returns the updated row, whose version the final state write must carry. */
130
+ private invalidateBeforeChunkWrite;
92
131
  private addResponseElement;
93
132
  private statusResponseElement;
133
+ /** Remembers the `dateModified` the device's own write left on `item`, so the write isn't echoed back. */
134
+ private noteWrite;
94
135
  private applyAdd;
95
136
  private applyChange;
96
137
  private applyDelete;
97
- /** Builds one `<Collection>` response element - `handle()` collects one of these per request `<Collection>`
98
- * and wraps the whole set in a single `<Sync><Collections>`. */
138
+ /** Builds one `<Collection>` response element. */
99
139
  private collectionResponse;
100
140
  }
@@ -3,5 +3,8 @@ import { GetItemEstimateCommand, type EstimateCollectionBinding } from "../GetIt
3
3
  * @author Jean-Philippe Steinmetz
4
4
  */
5
5
  export declare class GetItemEstimateCommandMongo extends GetItemEstimateCommand {
6
+ protected folderClass: any;
7
+ protected collectionStateClass: any;
8
+ protected collectionChunkClass: any;
6
9
  protected collectionBindings: Record<string, EstimateCollectionBinding>;
7
10
  }
@@ -6,4 +6,6 @@ export declare class ItemOperationsCommandMongo extends ItemOperationsCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
8
  protected attachmentClass: any;
9
+ protected mailboxClass: any;
10
+ protected auditLogClass: any;
9
11
  }
@@ -5,4 +5,5 @@ import { MeetingResponseCommand } from "../MeetingResponseCommand.js";
5
5
  export declare class MeetingResponseCommandMongo extends MeetingResponseCommand {
6
6
  protected calendarEventClass: any;
7
7
  protected mailboxClass: any;
8
+ protected messageClass: any;
8
9
  }
@@ -0,0 +1,8 @@
1
+ import { PingCommand, type PingCollectionBinding } from "../PingCommand.js";
2
+ /**
3
+ * @author Jean-Philippe Steinmetz
4
+ */
5
+ export declare class PingCommandMongo extends PingCommand {
6
+ protected collectionStateClass: any;
7
+ protected collectionBindings: Record<string, PingCollectionBinding>;
8
+ }
@@ -6,4 +6,6 @@ export declare class SearchCommandMongo extends SearchCommand {
6
6
  protected contactClass: any;
7
7
  protected messageClass: any;
8
8
  protected emailAdapterClass: any;
9
+ protected mailboxClass: any;
10
+ protected auditLogClass: any;
9
11
  }
@@ -5,4 +5,5 @@ import { SendMailCommand } from "../SendMailCommand.js";
5
5
  export declare class SendMailCommandMongo extends SendMailCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
+ protected mailboxClass: any;
8
9
  }
@@ -5,4 +5,5 @@ import { SmartForwardCommand } from "../SmartForwardCommand.js";
5
5
  export declare class SmartForwardCommandMongo extends SmartForwardCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
+ protected mailboxClass: any;
8
9
  }
@@ -5,4 +5,5 @@ import { SmartReplyCommand } from "../SmartReplyCommand.js";
5
5
  export declare class SmartReplyCommandMongo extends SmartReplyCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
+ protected mailboxClass: any;
8
9
  }
@@ -4,5 +4,9 @@ import { SyncCommand, type SyncCollectionBinding } from "../SyncCommand.js";
4
4
  */
5
5
  export declare class SyncCommandMongo extends SyncCommand {
6
6
  protected mailboxClass: any;
7
+ protected auditLogClass: any;
8
+ protected folderClass: any;
9
+ protected collectionStateClass: any;
10
+ protected collectionChunkClass: any;
7
11
  protected collectionBindings: Record<string, SyncCollectionBinding<any>>;
8
12
  }
@@ -7,3 +7,4 @@ export * from "./ItemOperationsCommandMongo.js";
7
7
  export * from "./SearchCommandMongo.js";
8
8
  export * from "./MeetingResponseCommandMongo.js";
9
9
  export * from "./SettingsCommandMongo.js";
10
+ export * from "./PingCommandMongo.js";
@@ -3,5 +3,8 @@ import { GetItemEstimateCommand, type EstimateCollectionBinding } from "../GetIt
3
3
  * @author Jean-Philippe Steinmetz
4
4
  */
5
5
  export declare class GetItemEstimateCommandSQL extends GetItemEstimateCommand {
6
+ protected folderClass: any;
7
+ protected collectionStateClass: any;
8
+ protected collectionChunkClass: any;
6
9
  protected collectionBindings: Record<string, EstimateCollectionBinding>;
7
10
  }
@@ -6,4 +6,6 @@ export declare class ItemOperationsCommandSQL extends ItemOperationsCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
8
  protected attachmentClass: any;
9
+ protected mailboxClass: any;
10
+ protected auditLogClass: any;
9
11
  }
@@ -5,4 +5,5 @@ import { MeetingResponseCommand } from "../MeetingResponseCommand.js";
5
5
  export declare class MeetingResponseCommandSQL extends MeetingResponseCommand {
6
6
  protected calendarEventClass: any;
7
7
  protected mailboxClass: any;
8
+ protected messageClass: any;
8
9
  }
@@ -0,0 +1,8 @@
1
+ import { PingCommand, type PingCollectionBinding } from "../PingCommand.js";
2
+ /**
3
+ * @author Jean-Philippe Steinmetz
4
+ */
5
+ export declare class PingCommandSQL extends PingCommand {
6
+ protected collectionStateClass: any;
7
+ protected collectionBindings: Record<string, PingCollectionBinding>;
8
+ }
@@ -6,4 +6,6 @@ export declare class SearchCommandSQL extends SearchCommand {
6
6
  protected contactClass: any;
7
7
  protected messageClass: any;
8
8
  protected emailAdapterClass: any;
9
+ protected mailboxClass: any;
10
+ protected auditLogClass: any;
9
11
  }
@@ -5,4 +5,5 @@ import { SendMailCommand } from "../SendMailCommand.js";
5
5
  export declare class SendMailCommandSQL extends SendMailCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
+ protected mailboxClass: any;
8
9
  }
@@ -5,4 +5,5 @@ import { SmartForwardCommand } from "../SmartForwardCommand.js";
5
5
  export declare class SmartForwardCommandSQL extends SmartForwardCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
+ protected mailboxClass: any;
8
9
  }
@@ -5,4 +5,5 @@ import { SmartReplyCommand } from "../SmartReplyCommand.js";
5
5
  export declare class SmartReplyCommandSQL extends SmartReplyCommand {
6
6
  protected folderClass: any;
7
7
  protected messageClass: any;
8
+ protected mailboxClass: any;
8
9
  }
@@ -4,5 +4,9 @@ import { SyncCommand, type SyncCollectionBinding } from "../SyncCommand.js";
4
4
  */
5
5
  export declare class SyncCommandSQL extends SyncCommand {
6
6
  protected mailboxClass: any;
7
+ protected auditLogClass: any;
8
+ protected folderClass: any;
9
+ protected collectionStateClass: any;
10
+ protected collectionChunkClass: any;
7
11
  protected collectionBindings: Record<string, SyncCollectionBinding<any>>;
8
12
  }
@@ -7,3 +7,4 @@ export * from "./ItemOperationsCommandSQL.js";
7
7
  export * from "./SearchCommandSQL.js";
8
8
  export * from "./MeetingResponseCommandSQL.js";
9
9
  export * from "./SettingsCommandSQL.js";
10
+ export * from "./PingCommandSQL.js";
@@ -30,9 +30,14 @@ export * from "./codec/WbxmlDecoder.js";
30
30
  export * from "./BaseEasRoute.js";
31
31
  export * from "./BaseDeviceSyncStateRoute.js";
32
32
  export * from "./models/DeviceSyncState.js";
33
+ export * from "./models/EasCollectionState.js";
34
+ export * from "./models/EasCollectionChunk.js";
33
35
  export * from "./jobs/EasDeviceStateCleanupJob.js";
34
36
  export * from "./EasCommandHandler.js";
35
37
  export * from "./EasSyncKeyUtils.js";
38
+ export * from "./EasCollectionSync.js";
39
+ export * from "./EasCollectionStore.js";
40
+ export * from "./EasCollectionLease.js";
36
41
  export * from "./CompactDateTime.js";
37
42
  export * from "./commands/ProvisionCommand.js";
38
43
  export * from "./commands/FolderSyncCommand.js";