@rapidmx/activesync-plugin 1.0.0-beta.2

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 (225) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +57 -0
  3. package/dist/lib/BaseDeviceSyncStateRoute.js +90 -0
  4. package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -0
  5. package/dist/lib/BaseEasRoute.js +213 -0
  6. package/dist/lib/BaseEasRoute.js.map +1 -0
  7. package/dist/lib/CompactDateTime.js +43 -0
  8. package/dist/lib/CompactDateTime.js.map +1 -0
  9. package/dist/lib/EasCommandHandler.js +2 -0
  10. package/dist/lib/EasCommandHandler.js.map +1 -0
  11. package/dist/lib/EasSyncKeyUtils.js +119 -0
  12. package/dist/lib/EasSyncKeyUtils.js.map +1 -0
  13. package/dist/lib/adapters/CalendarSyncAdapter.js +255 -0
  14. package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -0
  15. package/dist/lib/adapters/ContactsSyncAdapter.js +164 -0
  16. package/dist/lib/adapters/ContactsSyncAdapter.js.map +1 -0
  17. package/dist/lib/adapters/EasCollectionSyncAdapter.js +2 -0
  18. package/dist/lib/adapters/EasCollectionSyncAdapter.js.map +1 -0
  19. package/dist/lib/adapters/EmailSyncAdapter.js +269 -0
  20. package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -0
  21. package/dist/lib/adapters/TasksSyncAdapter.js +100 -0
  22. package/dist/lib/adapters/TasksSyncAdapter.js.map +1 -0
  23. package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js +16 -0
  24. package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js.map +1 -0
  25. package/dist/lib/adapters/mongo/index.js +6 -0
  26. package/dist/lib/adapters/mongo/index.js.map +1 -0
  27. package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js +16 -0
  28. package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js.map +1 -0
  29. package/dist/lib/adapters/sql/index.js +6 -0
  30. package/dist/lib/adapters/sql/index.js.map +1 -0
  31. package/dist/lib/codec/WbxmlCodePages.js +706 -0
  32. package/dist/lib/codec/WbxmlCodePages.js.map +1 -0
  33. package/dist/lib/codec/WbxmlDecoder.js +154 -0
  34. package/dist/lib/codec/WbxmlDecoder.js.map +1 -0
  35. package/dist/lib/codec/WbxmlElement.js +31 -0
  36. package/dist/lib/codec/WbxmlElement.js.map +1 -0
  37. package/dist/lib/codec/WbxmlEncoder.js +88 -0
  38. package/dist/lib/codec/WbxmlEncoder.js.map +1 -0
  39. package/dist/lib/commands/ComposeMailCommand.js +187 -0
  40. package/dist/lib/commands/ComposeMailCommand.js.map +1 -0
  41. package/dist/lib/commands/FolderSyncCommand.js +155 -0
  42. package/dist/lib/commands/FolderSyncCommand.js.map +1 -0
  43. package/dist/lib/commands/GetItemEstimateCommand.js +149 -0
  44. package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -0
  45. package/dist/lib/commands/ItemOperationsCommand.js +337 -0
  46. package/dist/lib/commands/ItemOperationsCommand.js.map +1 -0
  47. package/dist/lib/commands/MeetingResponseCommand.js +120 -0
  48. package/dist/lib/commands/MeetingResponseCommand.js.map +1 -0
  49. package/dist/lib/commands/MoveItemsCommand.js +103 -0
  50. package/dist/lib/commands/MoveItemsCommand.js.map +1 -0
  51. package/dist/lib/commands/PingCommand.js +145 -0
  52. package/dist/lib/commands/PingCommand.js.map +1 -0
  53. package/dist/lib/commands/ProvisionCommand.js +164 -0
  54. package/dist/lib/commands/ProvisionCommand.js.map +1 -0
  55. package/dist/lib/commands/ResolveRecipientsCommand.js +123 -0
  56. package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -0
  57. package/dist/lib/commands/SearchCommand.js +234 -0
  58. package/dist/lib/commands/SearchCommand.js.map +1 -0
  59. package/dist/lib/commands/SendMailCommand.js +18 -0
  60. package/dist/lib/commands/SendMailCommand.js.map +1 -0
  61. package/dist/lib/commands/SettingsCommand.js +138 -0
  62. package/dist/lib/commands/SettingsCommand.js.map +1 -0
  63. package/dist/lib/commands/SmartForwardCommand.js +22 -0
  64. package/dist/lib/commands/SmartForwardCommand.js.map +1 -0
  65. package/dist/lib/commands/SmartReplyCommand.js +22 -0
  66. package/dist/lib/commands/SmartReplyCommand.js.map +1 -0
  67. package/dist/lib/commands/SyncCommand.js +406 -0
  68. package/dist/lib/commands/SyncCommand.js.map +1 -0
  69. package/dist/lib/commands/mongo/FolderSyncCommandMongo.js +16 -0
  70. package/dist/lib/commands/mongo/FolderSyncCommandMongo.js.map +1 -0
  71. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +21 -0
  72. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -0
  73. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +18 -0
  74. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -0
  75. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +17 -0
  76. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -0
  77. package/dist/lib/commands/mongo/MoveItemsCommandMongo.js +17 -0
  78. package/dist/lib/commands/mongo/MoveItemsCommandMongo.js.map +1 -0
  79. package/dist/lib/commands/mongo/ResolveRecipientsCommandMongo.js +16 -0
  80. package/dist/lib/commands/mongo/ResolveRecipientsCommandMongo.js.map +1 -0
  81. package/dist/lib/commands/mongo/SearchCommandMongo.js +19 -0
  82. package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -0
  83. package/dist/lib/commands/mongo/SendMailCommandMongo.js +17 -0
  84. package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -0
  85. package/dist/lib/commands/mongo/SettingsCommandMongo.js +16 -0
  86. package/dist/lib/commands/mongo/SettingsCommandMongo.js.map +1 -0
  87. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +17 -0
  88. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -0
  89. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +17 -0
  90. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -0
  91. package/dist/lib/commands/mongo/SyncCommandMongo.js +26 -0
  92. package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -0
  93. package/dist/lib/commands/mongo/index.js +14 -0
  94. package/dist/lib/commands/mongo/index.js.map +1 -0
  95. package/dist/lib/commands/sql/FolderSyncCommandSQL.js +16 -0
  96. package/dist/lib/commands/sql/FolderSyncCommandSQL.js.map +1 -0
  97. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +21 -0
  98. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -0
  99. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +18 -0
  100. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -0
  101. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +17 -0
  102. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -0
  103. package/dist/lib/commands/sql/MoveItemsCommandSQL.js +17 -0
  104. package/dist/lib/commands/sql/MoveItemsCommandSQL.js.map +1 -0
  105. package/dist/lib/commands/sql/ResolveRecipientsCommandSQL.js +16 -0
  106. package/dist/lib/commands/sql/ResolveRecipientsCommandSQL.js.map +1 -0
  107. package/dist/lib/commands/sql/SearchCommandSQL.js +19 -0
  108. package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -0
  109. package/dist/lib/commands/sql/SendMailCommandSQL.js +17 -0
  110. package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -0
  111. package/dist/lib/commands/sql/SettingsCommandSQL.js +16 -0
  112. package/dist/lib/commands/sql/SettingsCommandSQL.js.map +1 -0
  113. package/dist/lib/commands/sql/SmartForwardCommandSQL.js +17 -0
  114. package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -0
  115. package/dist/lib/commands/sql/SmartReplyCommandSQL.js +17 -0
  116. package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -0
  117. package/dist/lib/commands/sql/SyncCommandSQL.js +26 -0
  118. package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -0
  119. package/dist/lib/commands/sql/index.js +14 -0
  120. package/dist/lib/commands/sql/index.js.map +1 -0
  121. package/dist/lib/index.js +58 -0
  122. package/dist/lib/index.js.map +1 -0
  123. package/dist/lib/jobs/EasDeviceStateCleanupJob.js +101 -0
  124. package/dist/lib/jobs/EasDeviceStateCleanupJob.js.map +1 -0
  125. package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js +13 -0
  126. package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js.map +1 -0
  127. package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js +13 -0
  128. package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js.map +1 -0
  129. package/dist/lib/models/DeviceSyncState.js +2 -0
  130. package/dist/lib/models/DeviceSyncState.js.map +1 -0
  131. package/dist/lib/models/mongo/DeviceSyncStateMongo.js +130 -0
  132. package/dist/lib/models/mongo/DeviceSyncStateMongo.js.map +1 -0
  133. package/dist/lib/models/sql/DeviceSyncStateSQL.js +130 -0
  134. package/dist/lib/models/sql/DeviceSyncStateSQL.js.map +1 -0
  135. package/dist/lib/mongo/DeviceSyncStateRouteMongo.js +28 -0
  136. package/dist/lib/mongo/DeviceSyncStateRouteMongo.js.map +1 -0
  137. package/dist/lib/mongo/EasRouteMongo.js +63 -0
  138. package/dist/lib/mongo/EasRouteMongo.js.map +1 -0
  139. package/dist/lib/mongo/index.js +9 -0
  140. package/dist/lib/mongo/index.js.map +1 -0
  141. package/dist/lib/mongo.js +15 -0
  142. package/dist/lib/mongo.js.map +1 -0
  143. package/dist/lib/sql/DeviceSyncStateRouteSQL.js +28 -0
  144. package/dist/lib/sql/DeviceSyncStateRouteSQL.js.map +1 -0
  145. package/dist/lib/sql/EasRouteSQL.js +63 -0
  146. package/dist/lib/sql/EasRouteSQL.js.map +1 -0
  147. package/dist/lib/sql/index.js +9 -0
  148. package/dist/lib/sql/index.js.map +1 -0
  149. package/dist/lib/sql.js +15 -0
  150. package/dist/lib/sql.js.map +1 -0
  151. package/dist/types/BaseDeviceSyncStateRoute.d.ts +34 -0
  152. package/dist/types/BaseEasRoute.d.ts +65 -0
  153. package/dist/types/CompactDateTime.d.ts +22 -0
  154. package/dist/types/EasCommandHandler.d.ts +59 -0
  155. package/dist/types/EasSyncKeyUtils.d.ts +89 -0
  156. package/dist/types/adapters/CalendarSyncAdapter.d.ts +52 -0
  157. package/dist/types/adapters/ContactsSyncAdapter.d.ts +33 -0
  158. package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +55 -0
  159. package/dist/types/adapters/EmailSyncAdapter.d.ts +71 -0
  160. package/dist/types/adapters/TasksSyncAdapter.d.ts +32 -0
  161. package/dist/types/adapters/mongo/EmailSyncAdapterMongo.d.ts +7 -0
  162. package/dist/types/adapters/mongo/index.d.ts +1 -0
  163. package/dist/types/adapters/sql/EmailSyncAdapterSQL.d.ts +7 -0
  164. package/dist/types/adapters/sql/index.d.ts +1 -0
  165. package/dist/types/codec/WbxmlCodePages.d.ts +45 -0
  166. package/dist/types/codec/WbxmlDecoder.d.ts +33 -0
  167. package/dist/types/codec/WbxmlElement.d.ts +43 -0
  168. package/dist/types/codec/WbxmlEncoder.d.ts +20 -0
  169. package/dist/types/commands/ComposeMailCommand.d.ts +50 -0
  170. package/dist/types/commands/FolderSyncCommand.d.ts +25 -0
  171. package/dist/types/commands/GetItemEstimateCommand.d.ts +43 -0
  172. package/dist/types/commands/ItemOperationsCommand.d.ts +72 -0
  173. package/dist/types/commands/MeetingResponseCommand.d.ts +32 -0
  174. package/dist/types/commands/MoveItemsCommand.d.ts +29 -0
  175. package/dist/types/commands/PingCommand.d.ts +33 -0
  176. package/dist/types/commands/ProvisionCommand.d.ts +54 -0
  177. package/dist/types/commands/ResolveRecipientsCommand.d.ts +36 -0
  178. package/dist/types/commands/SearchCommand.d.ts +60 -0
  179. package/dist/types/commands/SendMailCommand.d.ts +10 -0
  180. package/dist/types/commands/SettingsCommand.d.ts +37 -0
  181. package/dist/types/commands/SmartForwardCommand.d.ts +17 -0
  182. package/dist/types/commands/SmartReplyCommand.d.ts +17 -0
  183. package/dist/types/commands/SyncCommand.d.ts +100 -0
  184. package/dist/types/commands/mongo/FolderSyncCommandMongo.d.ts +8 -0
  185. package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +7 -0
  186. package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +9 -0
  187. package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +8 -0
  188. package/dist/types/commands/mongo/MoveItemsCommandMongo.d.ts +8 -0
  189. package/dist/types/commands/mongo/ResolveRecipientsCommandMongo.d.ts +7 -0
  190. package/dist/types/commands/mongo/SearchCommandMongo.d.ts +9 -0
  191. package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +8 -0
  192. package/dist/types/commands/mongo/SettingsCommandMongo.d.ts +7 -0
  193. package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +8 -0
  194. package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +8 -0
  195. package/dist/types/commands/mongo/SyncCommandMongo.d.ts +8 -0
  196. package/dist/types/commands/mongo/index.d.ts +9 -0
  197. package/dist/types/commands/sql/FolderSyncCommandSQL.d.ts +8 -0
  198. package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +7 -0
  199. package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +9 -0
  200. package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +8 -0
  201. package/dist/types/commands/sql/MoveItemsCommandSQL.d.ts +8 -0
  202. package/dist/types/commands/sql/ResolveRecipientsCommandSQL.d.ts +7 -0
  203. package/dist/types/commands/sql/SearchCommandSQL.d.ts +9 -0
  204. package/dist/types/commands/sql/SendMailCommandSQL.d.ts +8 -0
  205. package/dist/types/commands/sql/SettingsCommandSQL.d.ts +7 -0
  206. package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +8 -0
  207. package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +8 -0
  208. package/dist/types/commands/sql/SyncCommandSQL.d.ts +8 -0
  209. package/dist/types/commands/sql/index.d.ts +9 -0
  210. package/dist/types/index.d.ts +53 -0
  211. package/dist/types/jobs/EasDeviceStateCleanupJob.d.ts +26 -0
  212. package/dist/types/jobs/mongo/EasDeviceStateCleanupJobMongo.d.ts +5 -0
  213. package/dist/types/jobs/sql/EasDeviceStateCleanupJobSQL.d.ts +5 -0
  214. package/dist/types/models/DeviceSyncState.d.ts +30 -0
  215. package/dist/types/models/mongo/DeviceSyncStateMongo.d.ts +22 -0
  216. package/dist/types/models/sql/DeviceSyncStateSQL.d.ts +22 -0
  217. package/dist/types/mongo/DeviceSyncStateRouteMongo.d.ts +8 -0
  218. package/dist/types/mongo/EasRouteMongo.d.ts +14 -0
  219. package/dist/types/mongo/index.d.ts +4 -0
  220. package/dist/types/mongo.d.ts +10 -0
  221. package/dist/types/sql/DeviceSyncStateRouteSQL.d.ts +8 -0
  222. package/dist/types/sql/EasRouteSQL.d.ts +14 -0
  223. package/dist/types/sql/index.d.ts +4 -0
  224. package/dist/types/sql.d.ts +10 -0
  225. package/package.json +238 -0
@@ -0,0 +1,63 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ ///////////////////////////////////////////////////////////////////////////////
8
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
9
+ // SPDX-License-Identifier: MPL-2.0
10
+ ///////////////////////////////////////////////////////////////////////////////
11
+ import { MailboxSQL } from "@rapidmx/restapi/sql";
12
+ import { RouteDecorators } from "@rapidrest/service-core";
13
+ import { DeviceSyncStateSQL } from "../models/sql/DeviceSyncStateSQL.js";
14
+ import { BaseEasRoute } from "../BaseEasRoute.js";
15
+ import { ProvisionCommand } from "../commands/ProvisionCommand.js";
16
+ import { PingCommand } from "../commands/PingCommand.js";
17
+ import { FolderSyncCommandSQL } from "../commands/sql/FolderSyncCommandSQL.js";
18
+ import { SyncCommandSQL } from "../commands/sql/SyncCommandSQL.js";
19
+ import { SendMailCommandSQL } from "../commands/sql/SendMailCommandSQL.js";
20
+ import { SmartForwardCommandSQL } from "../commands/sql/SmartForwardCommandSQL.js";
21
+ import { SmartReplyCommandSQL } from "../commands/sql/SmartReplyCommandSQL.js";
22
+ import { ItemOperationsCommandSQL } from "../commands/sql/ItemOperationsCommandSQL.js";
23
+ import { SearchCommandSQL } from "../commands/sql/SearchCommandSQL.js";
24
+ import { MeetingResponseCommandSQL } from "../commands/sql/MeetingResponseCommandSQL.js";
25
+ import { SettingsCommandSQL } from "../commands/sql/SettingsCommandSQL.js";
26
+ import { GetItemEstimateCommandSQL } from "../commands/sql/GetItemEstimateCommandSQL.js";
27
+ import { MoveItemsCommandSQL } from "../commands/sql/MoveItemsCommandSQL.js";
28
+ import { ResolveRecipientsCommandSQL } from "../commands/sql/ResolveRecipientsCommandSQL.js";
29
+ const { Route } = RouteDecorators;
30
+ /**
31
+ * SQL-backed concrete `BaseEasRoute`, mounted at the protocol's well-known path. Exported from this plugin's
32
+ * `./sql` entry point.
33
+ *
34
+ * @author Jean-Philippe Steinmetz
35
+ */
36
+ let EasRouteSQL = class EasRouteSQL extends BaseEasRoute {
37
+ constructor() {
38
+ super(...arguments);
39
+ this.deviceSyncStateClass = DeviceSyncStateSQL;
40
+ this.mailboxClass = MailboxSQL;
41
+ this.commandHandlerClasses = [
42
+ ProvisionCommand,
43
+ FolderSyncCommandSQL,
44
+ SyncCommandSQL,
45
+ SendMailCommandSQL,
46
+ SmartForwardCommandSQL,
47
+ SmartReplyCommandSQL,
48
+ ItemOperationsCommandSQL,
49
+ SearchCommandSQL,
50
+ MeetingResponseCommandSQL,
51
+ SettingsCommandSQL,
52
+ PingCommand,
53
+ GetItemEstimateCommandSQL,
54
+ MoveItemsCommandSQL,
55
+ ResolveRecipientsCommandSQL,
56
+ ];
57
+ }
58
+ };
59
+ EasRouteSQL = __decorate([
60
+ Route("/Microsoft-Server-ActiveSync")
61
+ ], EasRouteSQL);
62
+ export { EasRouteSQL };
63
+ //# sourceMappingURL=EasRouteSQL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EasRouteSQL.js","sourceRoot":"","sources":["../../../src/sql/EasRouteSQL.ts"],"names":[],"mappings":";;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAC7E,OAAO,EAAE,2BAA2B,EAAE,MAAM,gDAAgD,CAAC;AAC7F,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC;AAElC;;;;;GAKG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,YAA4C;IAAtE;;QACO,yBAAoB,GAAQ,kBAAkB,CAAC;QAC/C,iBAAY,GAAQ,UAAU,CAAC;QAC/B,0BAAqB,GAAU;YACrC,gBAAgB;YAChB,oBAAoB;YACpB,cAAc;YACd,kBAAkB;YAClB,sBAAsB;YACtB,oBAAoB;YACpB,wBAAwB;YACxB,gBAAgB;YAChB,yBAAyB;YACzB,kBAAkB;YAClB,WAAW;YACX,yBAAyB;YACzB,mBAAmB;YACnB,2BAA2B;SAC9B,CAAC;IACN,CAAC;CAAA,CAAA;AAnBY,WAAW;IADvB,KAAK,CAAC,8BAA8B,CAAC;GACzB,WAAW,CAmBvB"}
@@ -0,0 +1,9 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ export * from "./EasRouteSQL.js";
6
+ export * from "./DeviceSyncStateRouteSQL.js";
7
+ export * from "../commands/sql/index.js";
8
+ export * from "../adapters/sql/index.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sql/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,kBAAkB,CAAC;AACjC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,15 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * This plugin's `./sql` entry point: exactly the classes a server host loads for a SQL deployment - the
7
+ * mounted routes, the device-state model and its cleanup job. Anything else exported here would be registered
8
+ * by the host too (and an abstract `BackgroundService` would even be started), so the command and adapter
9
+ * classes these routes use stay internal.
10
+ */
11
+ export { EasRouteSQL } from "./sql/EasRouteSQL.js";
12
+ export { DeviceSyncStateRouteSQL } from "./sql/DeviceSyncStateRouteSQL.js";
13
+ export { DeviceSyncStateSQL } from "./models/sql/DeviceSyncStateSQL.js";
14
+ export { EasDeviceStateCleanupJobSQL } from "./jobs/sql/EasDeviceStateCleanupJobSQL.js";
15
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E;;;;;GAKG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type JWTUser } from "@rapidrest/core";
2
+ import { HttpRequest } from "@rapidrest/service-core";
3
+ import type { DeviceSyncState } from "./models/DeviceSyncState.js";
4
+ /**
5
+ * Admin-only trigger for the `RemoteWipe` sub-flow `ProvisionCommand` implements. `restapi` has no route of its
6
+ * own for `DeviceSyncState` (it's a protocol-internal entity, not a domain object a normal API consumer ever
7
+ * lists or edits directly), so this lives here instead, following the exact `trustedRoles`/`UserUtils.hasRoles`
8
+ * gating pattern `BaseMailboxRoute` already uses for its own admin-vs-owner scoping.
9
+ *
10
+ * Setting `remoteWipeRequested: true` (and `provisioned: false` alongside it) is the entire trigger: the next
11
+ * request that device makes of any kind is already forced back through `Provision` by `BaseEasRoute.dispatch()`'s
12
+ * existing 449 gate, at which point `ProvisionCommand.issuePolicy` sees the flag and sends the device a
13
+ * `RemoteWipe` directive instead of a normal policy document - no separate push/notification channel is needed.
14
+ *
15
+ * `deviceSyncStateClass` is supplied by the Mongo/SQL concrete subclasses, the same one-line-per-backend
16
+ * pattern used throughout this library.
17
+ *
18
+ * @author Jean-Philippe Steinmetz
19
+ */
20
+ export declare abstract class BaseDeviceSyncStateRoute<D extends DeviceSyncState> {
21
+ protected abstract deviceSyncStateClass: any;
22
+ protected trustedRoles: string[];
23
+ private _objectFactory?;
24
+ private deviceSyncStateRepo?;
25
+ private getDeviceSyncStateRepo;
26
+ /**
27
+ * Marks `uid` (a `DeviceSyncState`'s own id, not a device id string - the admin looks this up via whatever
28
+ * device-listing view a deployment builds on top of `DeviceSyncState`'s ordinary CRUD, out of scope here)
29
+ * for remote wipe. Optional JSON body `{ accountOnly?: boolean }` — recorded for admin audit only; see
30
+ * `ProvisionCommand`'s own doc comment for why the wire directive sent to the device doesn't distinguish
31
+ * the two.
32
+ */
33
+ remoteWipe(uid: string, req: HttpRequest, user?: JWTUser): Promise<D>;
34
+ }
@@ -0,0 +1,65 @@
1
+ import { type JWTUser } from "@rapidrest/core";
2
+ import { HttpRequest, HttpResponse } from "@rapidrest/service-core";
3
+ import { Mailbox } from "@rapidmx/restapi";
4
+ import { DeviceSyncState } from "./models/DeviceSyncState.js";
5
+ /**
6
+ * Abstract base for the single fixed EAS endpoint (`POST /Microsoft-Server-ActiveSync` by MS-ASHTTP
7
+ * convention, though the concrete path is left to the consuming application to mount via `@Route(...)` — see
8
+ * `BaseMailIngestRoute.ts` for the identical undecorated-base-class pattern this follows). Unlike every other
9
+ * route in this library, EAS dispatches on a `Cmd` query parameter against one URL rather than path-based REST
10
+ * routing, so there is exactly one `@Post()` method here, not one per operation.
11
+ *
12
+ * **Auth**: `@Auth(["jwt"])` — the framework's own default strategy, unchanged from every other route in this
13
+ * app. A real native EAS client obtaining that JWT in the first place (rather than the app's own web/API
14
+ * clients, which already have one) requires an OAuth 2.0 Authorization Server capability this library
15
+ * deliberately does not implement itself — see the architecture plan's "Auth" section for the full reasoning
16
+ * behind this choice over a per-request Basic Auth strategy.
17
+ *
18
+ * **Dispatch flow**: resolve the caller's own `Mailbox` (never a client-supplied one — `resolveCallerMailboxUid`,
19
+ * the same helper `BaseSearchRoute` already uses), find-or-create that (mailbox, device) pair's
20
+ * `DeviceSyncState`, enforce the provisioning gate, decode the WBXML request body (if any), dispatch to the
21
+ * matching registered `EasCommandHandler`, bump `lastSyncAt`, and encode the handler's response back to WBXML.
22
+ *
23
+ * **Command handlers** are supplied via `commandHandlerClasses` (empty by default — this class alone is just
24
+ * the transport skeleton; concrete command support, e.g. `ProvisionCommand`/`FolderSyncCommand`, is added
25
+ * incrementally in later work by having a concrete subclass populate this array) and instantiated once each in
26
+ * `@Init` via `ObjectFactory`, so a handler can `@Inject` its own dependencies like any other DI-managed class
27
+ * in this library.
28
+ *
29
+ * **`OPTIONS` protocol discovery**: real EAS clients conventionally probe `OPTIONS` before their first `POST`
30
+ * to read `MS-ASProtocolVersions`/`MS-ASProtocolCommands` and learn what the server supports - see `options()`
31
+ * below. This requires `@rapidrest/service-core` >=1.5.0, whose global CORS middleware consults
32
+ * `IHttpRouter.hasExplicitOptionsRoute()` before its blanket preflight `204` - confirmed live end to end
33
+ * against a real `service-core` 1.5.0 install (`test/routes/{mongo,sql}/EasRoute.test.ts`'s `OPTIONS`
34
+ * describe block), not assumed.
35
+ *
36
+ * `deviceSyncStateClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses, following the exact
37
+ * one-line-per-backend pattern used throughout this library's other routes/jobs.
38
+ *
39
+ * @author Jean-Philippe Steinmetz
40
+ */
41
+ export declare abstract class BaseEasRoute<D extends DeviceSyncState, M extends Mailbox = Mailbox> {
42
+ protected abstract deviceSyncStateClass: any;
43
+ protected abstract mailboxClass: any;
44
+ /** Command handler classes to instantiate (one each) in `@Init`. Empty until a concrete command lands -
45
+ * every request is then answered with HTTP 501 (see `dispatch()`), which is the correct, honest behavior
46
+ * for a transport skeleton with no commands implemented yet, not a bug to work around. */
47
+ protected commandHandlerClasses: any[];
48
+ private _objectFactory?;
49
+ private deviceSyncStateRepo?;
50
+ private mailboxRepo?;
51
+ private readonly handlers;
52
+ private logger;
53
+ init(): Promise<void>;
54
+ /**
55
+ * Answers a real client's pre-flight `MS-ASProtocolVersions`/`MS-ASProtocolCommands` capability probe -
56
+ * see this class's own doc comment for the `service-core` version dependency this needs to actually run.
57
+ * Deliberately unauthenticated (no `@Auth`): this is capability discovery, not mailbox access, and a real
58
+ * Exchange server answers it the same way regardless of credentials. `MS-ASProtocolCommands` is built from
59
+ * `this.handlers`, not a separately-maintained list, so it can never drift out of sync with the commands a
60
+ * concrete subclass actually registered via `commandHandlerClasses`.
61
+ */
62
+ options(res: HttpResponse): Promise<void>;
63
+ dispatch(req: HttpRequest, res: HttpResponse, user?: JWTUser): Promise<void>;
64
+ private findOrCreateDeviceSyncState;
65
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Formats a date as MS-ASDTYPE's "Compact DateTime" (`YYYYMMDDTHHMMSSZ`, always UTC) - the format
3
+ * `Calendar`/`Tasks` timestamp fields (`StartTime`/`EndTime`/`UtcDueDate`/...) use, distinct from the plain
4
+ * `dateTime` type (`YYYY-MM-DDTHH:MM:SS.MSSZ`, i.e. `Date.prototype.toISOString()`) `Email`'s `DateReceived`
5
+ * uses - confirmed against the published MS-ASDTYPE spec (`2.7.2 Compact DateTime` vs `2.7 dateTime Data
6
+ * Type`), not assumed, since sending the wrong one is exactly the kind of silent-until-a-real-device-connects
7
+ * bug this library's WBXML codec work already ran into once with tag casing (`"Mime"` vs `"MIME"`).
8
+ *
9
+ * Accepts `Date | string` and normalizes via `new Date(...)` - a real, discovered-by-testing gap: fields
10
+ * embedded inside a `simple-json` column (e.g. `RecurrenceRule.until`) round-trip through `JSON.stringify`/
11
+ * `JSON.parse` on the SQL backend, which does not preserve `Date` instances, so `until` comes back as a plain
12
+ * ISO string there even though the Mongo backend (native BSON dates) hands back a real `Date` for the exact
13
+ * same field - a caller passing either must work on both backends without knowing which one it's talking to.
14
+ */
15
+ export declare function toCompactDateTime(date: Date | string): string;
16
+ /**
17
+ * Parses MS-ASDTYPE's "Compact DateTime" (`YYYYMMDDTHHMMSSZ`) back into a `Date` - the reverse of
18
+ * `toCompactDateTime`, needed for `Sync`'s client-originated `Add`/`Change` commands on `Calendar`/`Tasks`.
19
+ * Throws (rather than returning an unvalidated `Date` that would silently carry `NaN`s) for anything not
20
+ * matching the exact expected shape.
21
+ */
22
+ export declare function fromCompactDateTime(value: string): Date;
@@ -0,0 +1,59 @@
1
+ import type { JWTUser } from "@rapidrest/core";
2
+ import type { HttpRequest, RepoUtils } from "@rapidrest/service-core";
3
+ import type { WbxmlElement } from "./codec/WbxmlElement.js";
4
+ import type { DeviceSyncState } from "./models/DeviceSyncState.js";
5
+ /**
6
+ * Everything an `EasCommandHandler` needs to process one dispatched EAS command — assembled once by
7
+ * `BaseEasRoute.dispatch()` per request and handed to whichever handler matches `?Cmd=`.
8
+ *
9
+ * @author Jean-Philippe Steinmetz
10
+ */
11
+ export interface EasCommandContext {
12
+ /** The authenticated caller, per the same `@AuthUser` JWT payload every other route in this library reads. */
13
+ readonly user: JWTUser;
14
+ /** The `Mailbox` this request operates against — resolved server-side from `user` (`ownerUserUid`), never
15
+ * taken from client input. */
16
+ readonly mailboxUid: string;
17
+ /** The client-supplied `?DeviceId=` query value, identifying this device within the mailbox. */
18
+ readonly deviceId: string;
19
+ /** The client-supplied `?DeviceType=` query value (e.g. `iPhone`, `Android`). */
20
+ readonly deviceType: string;
21
+ /** The client-supplied `?PolicyKey=` query value, if present - the provisioning policy key the device is
22
+ * currently operating under. Not yet validated against `deviceSyncState.policyKey` here (deferred to
23
+ * `ProvisionCommand`'s own implementation); handlers that care should compare it themselves for now. */
24
+ readonly policyKey?: string;
25
+ /** This device's persisted sync/provisioning state, looked up (or newly created) by `BaseEasRoute` before
26
+ * dispatch. Handlers read/write cursor and provisioning fields on this directly. */
27
+ readonly deviceSyncState: DeviceSyncState;
28
+ /** The live repo backing `deviceSyncState`'s concrete entity class, for handlers that need to persist a
29
+ * change to it (e.g. a new `SyncKey`, an updated `policyKey`, flipping `provisioned`). Typed `RepoUtils<any>`
30
+ * rather than `RepoUtils<DeviceSyncState>` deliberately - `RepoUtils<D>`'s underlying TypeORM `Repository<D>`
31
+ * is not covariant in `D` (a handful of its methods, e.g. `sum()`, use `D`-dependent conditional types), so
32
+ * `BaseEasRoute`'s own `RepoUtils<D>` (for its concrete `D extends DeviceSyncState`) cannot be narrowed to
33
+ * this field's type without `any` somewhere in between. */
34
+ readonly deviceSyncStateRepo: RepoUtils<any>;
35
+ /** Every query-string parameter on the request, for the handful of commands with additional command-
36
+ * specific query parameters beyond the common four already broken out above. */
37
+ readonly query: Record<string, string | string[]>;
38
+ /** The decoded WBXML request body, or `undefined` for a command sent with an empty body (legal for a few
39
+ * commands, e.g. a bare `GetItemEstimate`-less `Ping` continuation). */
40
+ readonly request?: WbxmlElement;
41
+ /** The raw underlying HTTP request, for the rare handler that needs something this context doesn't
42
+ * already surface (e.g. a header). */
43
+ readonly req: HttpRequest;
44
+ }
45
+ /**
46
+ * One EAS protocol command (`Provision`, `FolderSync`, `Sync`, ...). `BaseEasRoute` builds one instance of
47
+ * each registered handler class in its own `@Init` (via `ObjectFactory`, so a handler can `@Inject` its own
48
+ * dependencies exactly like any other DI-managed class in this library) and dispatches to the one whose
49
+ * `command` matches the request's `?Cmd=` value.
50
+ *
51
+ * @author Jean-Philippe Steinmetz
52
+ */
53
+ export interface EasCommandHandler {
54
+ /** The exact `?Cmd=` value this handler answers to (e.g. `"FolderSync"`). */
55
+ readonly command: string;
56
+ /** Processes the command and returns the WBXML element tree to send back as the response body, or
57
+ * `undefined` for a command whose successful response is legitimately empty. */
58
+ handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
59
+ }
@@ -0,0 +1,89 @@
1
+ import type { RecoverableBaseEntity, RepoUtils } from "@rapidrest/service-core";
2
+ import type { DeviceSyncState } from "./models/DeviceSyncState.js";
3
+ /**
4
+ * A parsed EAS `SyncKey`. The wire value is opaque to the client per spec, so this library encodes it as
5
+ * `"<generation>:<watermarkIso>"` (e.g. `"3:2026-09-04T12:00:00.000Z"`) - a monotonic generation counter (bumped
6
+ * once per successful sync round, satisfying the spec's "the server MUST return a different SyncKey every
7
+ * time" requirement) paired with the `dateModified` watermark that generation was issued at.
8
+ */
9
+ export interface SyncKey {
10
+ generation: number;
11
+ watermark: Date;
12
+ }
13
+ /** Formats a `SyncKey` back into its wire string form. */
14
+ export declare function formatSyncKey(key: SyncKey): string;
15
+ /** Parses a stored/previously-issued `SyncKey` string. Returns `undefined` for a malformed value - callers
16
+ * only ever parse a key this library itself minted and stored (never a raw, unvalidated client value; see
17
+ * `resolveSyncKey`'s doc comment), so `undefined` here signals corrupted persisted state, not client input. */
18
+ export declare function parseSyncKey(value: string): SyncKey | undefined;
19
+ export type SyncKeyResolution = {
20
+ kind: "initial";
21
+ } | {
22
+ kind: "valid";
23
+ key: SyncKey;
24
+ } | {
25
+ kind: "invalid";
26
+ };
27
+ /**
28
+ * Resolves an incoming client `SyncKey` string against the value this library itself previously issued and
29
+ * stored (`storedValue`, e.g. `DeviceSyncState.folderSyncKeys[folderUid]`) — deliberately a plain string
30
+ * equality check, not a re-parse-and-compare of the client's value, so a client that echoes back anything
31
+ * other than the exact opaque string it was handed is treated as `"invalid"` even if it happens to parse.
32
+ *
33
+ * - `"0"` (or empty/missing) from the client is always `"initial"` regardless of `storedValue` — an EAS
34
+ * client legitimately sends this to (re)start a collection from scratch (first-ever sync, or recovering
35
+ * from an `"invalid"` response elsewhere), and the spec requires the server honor it unconditionally.
36
+ * - Otherwise, a match against `storedValue` is `"valid"`; anything else (including `storedValue` being
37
+ * unset, i.e. the server has no record of ever issuing a key for this scope) is `"invalid"` — forcing the
38
+ * client back to `"0"`, per spec, rather than guessing at recovery.
39
+ */
40
+ export declare function resolveSyncKey(clientValue: string | undefined, storedValue: string | undefined): SyncKeyResolution;
41
+ /**
42
+ * Applies `patch` to `deviceSyncState` and persists it - the one correct way any EAS command handler (or
43
+ * `BaseEasRoute` itself) should ever write to a `DeviceSyncState`, replacing the inline
44
+ * `deviceSyncStateRepo.update(...)` calls `ProvisionCommand`/`FolderSyncCommand`/`SyncCommand` used to each
45
+ * repeat individually.
46
+ *
47
+ * This exists because `RepoUtils.update()` does **not** mutate the `existing` object passed to it - confirmed
48
+ * by reading its implementation directly - it only returns a freshly-fetched instance reflecting the write,
49
+ * leaving the caller's in-memory copy (including its optimistic-concurrency `version`) stale. A single write
50
+ * per request is harmless (nothing re-reads the stale copy), but a *second* write to the same `deviceSyncState`
51
+ * later in the same request - e.g. `BaseEasRoute.dispatch()`'s own trailing `lastSyncAt` update, or a second
52
+ * collection's SyncKey in a multi-collection `Sync` - would build its own patch's `version` off that stale
53
+ * value. `RepoUtils.update()` filters its underlying write by exactly that `version`, so the write silently
54
+ * matches zero rows instead of throwing - a silent no-op, not a visible error. Copying `update()`'s own
55
+ * returned result back onto `deviceSyncState` (last line below) keeps every field, including `version`, current
56
+ * for whatever writes this same request still has left to make.
57
+ */
58
+ export declare function persistDeviceSyncState(deviceSyncState: DeviceSyncState, deviceSyncStateRepo: RepoUtils<any>, patch: Record<string, unknown>): Promise<void>;
59
+ /** One page of enumerated changes for a `RecoverableBaseEntity` collection scoped by a single field (e.g.
60
+ * `folderUid` for `Message`/`CalendarEvent`/.../`mailboxUid` for `Folder`), since the last sync at `watermark`. */
61
+ export interface ChangeSet<T extends RecoverableBaseEntity> {
62
+ adds: T[];
63
+ changes: T[];
64
+ deletes: T[];
65
+ /** The new watermark to persist as this generation's cursor - the latest `dateModified` actually included
66
+ * in this page, never simply "now" (which would silently skip any row modified after this page was read
67
+ * but before the caller finishes processing it). Equal to the input `watermark` when nothing changed. */
68
+ newWatermark: Date;
69
+ /** `true` when more changed rows exist beyond `windowSize` - the caller should set the response's
70
+ * `MoreAvailable` flag so the client immediately re-syncs for the rest, rather than waiting a full poll
71
+ * interval. */
72
+ moreAvailable: boolean;
73
+ }
74
+ /**
75
+ * Enumerates `Add`/`Change`/`Delete`s for one scoped collection since `watermark`, for `FolderSyncCommand` and
76
+ * (eventually) `SyncCommand`'s shared cursor mechanism. Requires `T` to be `RecoverableBaseEntity` (soft
77
+ * delete) — see `RecoverableRepoUtils`'s own doc comment for why a plain hard-deleted entity can't support
78
+ * this at all (nothing left to enumerate once a row is actually gone).
79
+ *
80
+ * `RepoUtils.find()` does **not** support an `includeDeleted` option - confirmed by reading its source: unlike
81
+ * `count()`/`findOne()`, it never strips `ModelUtils.buildSearchQuery()`'s default `deleted: false` exclusion
82
+ * back out. Rather than the (harmless-looking but silently no-op) `includeDeleted: true` this function used to
83
+ * pass, deleted rows are fetched via a **second** `find()` call with an explicit, literal `deleted: true` in
84
+ * the query object itself - `buildSearchQuery()`'s exclusion only applies when the caller's query has no
85
+ * `"deleted"` key at all, so supplying one directly (rather than relying on an option `find()` doesn't honor)
86
+ * reliably selects exactly the soft-deleted rows instead. The two result sets are then merged and re-windowed
87
+ * together so `windowSize`/`MoreAvailable` still describe the combined stream, not each half independently.
88
+ */
89
+ export declare function computeChanges<T extends RecoverableBaseEntity>(repo: RepoUtils<T>, scopeField: string, scopeUid: string, watermark: Date, windowSize: number): Promise<ChangeSet<T>>;
@@ -0,0 +1,52 @@
1
+ import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
+ import type { EasCollectionSyncAdapter } from "./EasCollectionSyncAdapter.js";
3
+ import { type CalendarEvent } from "@rapidmx/restapi";
4
+ /**
5
+ * Maps `CalendarEvent` to/from the EAS `Sync` `Calendar` collection class (MS-ASCAL).
6
+ *
7
+ * **Pragmatic subset, deliberately not the full MS-ASCAL semantics**:
8
+ * - `TimeZone` is not emitted - the real element is a base64-encoded binary Win32 `TIME_ZONE_INFORMATION`
9
+ * structure, not a plain IANA string; `CalendarEvent.timezone` (an IANA identifier) can't be losslessly
10
+ * re-encoded into that format without a full IANA-to-Windows zone mapping table, and a real device would
11
+ * rather see no `TimeZone` element (falling back to its own default) than a malformed one.
12
+ * - `Sensitivity` is always reported `0` (Normal) - this library's `CalendarEvent` has no privacy dimension of
13
+ * its own to source a real value from.
14
+ * - Recurrence patterns keyed by an ordinal weekday (MS-ASCAL `Type` 3/6, e.g. "the 2nd Tuesday of the month")
15
+ * are not emitted - see `RECURRENCE_TYPE_CODES`'s own doc comment.
16
+ * - Recurrence exceptions (individually modified/cancelled occurrences of a recurring series) are not synced -
17
+ * deferred, matching this library's "pragmatic subset" precedent elsewhere (e.g. `FolderSyncCommand`'s SyncKey
18
+ * replay-protection gap).
19
+ *
20
+ * @author Jean-Philippe Steinmetz
21
+ */
22
+ export declare class CalendarSyncAdapter implements EasCollectionSyncAdapter<CalendarEvent> {
23
+ readonly collectionClass = "Calendar";
24
+ toApplicationData(event: CalendarEvent): WbxmlElement;
25
+ private recurrenceElement;
26
+ /**
27
+ * Reverse of `toApplicationData`. `timezone`/`status`/`sequence`/`icalUid` have no wire representation at
28
+ * all (see this class's own "pragmatic subset" doc comment for `timezone`; the other three are purely
29
+ * server-managed identifiers/state a client was never sent in the first place) and are never included in
30
+ * the returned partial - `newEntityDefaults()` below supplies `icalUid`/`sequence` for a brand new event
31
+ * (`status`/`timezone` are left at the model's own constructor defaults), and `applyChange` leaves all four
32
+ * untouched by construction (merging onto `existing`).
33
+ *
34
+ * `OrganizerEmail` is required for a new event (there is nowhere else to default it from - this adapter
35
+ * has no mailbox context of its own) - a real calendar client always sends it regardless, since it already
36
+ * knows its own account's address. Omitting it on an `Add` throws, which `SyncCommand.applyAdd` turns into
37
+ * Status `6` ("client has sent a malformed or invalid item"), the spec's own designated code for exactly
38
+ * this case.
39
+ *
40
+ * `Attendees`/`Recurrence` are ghosted as a whole element, like `ContactsSyncAdapter`'s arrays: present at
41
+ * all -> rebuilt entirely from what's there; absent -> left untouched on a `Change`.
42
+ */
43
+ fromApplicationData(el: WbxmlElement): Partial<CalendarEvent>;
44
+ /** `icalUid`/`sequence` have no wire representation on `Add` (see `fromApplicationData`'s own doc comment)
45
+ * - without this, every Sync-created event would fall back to `CalendarEventMongo`/`CalendarEventSQL`'s own
46
+ * constructor default of `icalUid: ""`, violating RFC 5545's uniqueness expectation for `UID`. Mirrors
47
+ * MAPI's identical `RopSaveChangesMessageHandler` pattern (`${crypto.randomUUID()}@mapi`), `@eas` suffix
48
+ * instead. */
49
+ newEntityDefaults(): Partial<CalendarEvent>;
50
+ private attendeeFromElement;
51
+ private recurrenceRuleFromElement;
52
+ }
@@ -0,0 +1,33 @@
1
+ import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
+ import type { EasCollectionSyncAdapter } from "./EasCollectionSyncAdapter.js";
3
+ import { type Contact } from "@rapidmx/restapi";
4
+ /**
5
+ * Maps `Contact` to/from the EAS `Sync` `Contacts` collection class (MS-ASCONTACTS/MS-ASCNTC2). Contacts are
6
+ * also this library's GAL source (see the architecture note on `Contact` itself), but that's `SearchCommand`'s
7
+ * concern, not this adapter's.
8
+ *
9
+ * @author Jean-Philippe Steinmetz
10
+ */
11
+ export declare class ContactsSyncAdapter implements EasCollectionSyncAdapter<Contact> {
12
+ readonly collectionClass = "Contacts";
13
+ toApplicationData(contact: Contact): WbxmlElement;
14
+ private addressElements;
15
+ /**
16
+ * Reverse of `toApplicationData`. Scalar fields (`FileAs`/`FirstName`/.../`JobTitle`, and the `Body`
17
+ * `notes`) are properly ghosted - a field's own tag missing from `el` leaves that `Contact` field
18
+ * untouched. `emails`/`phones`/`addresses` are ghosted only as a **whole group**, not per slot: if *none*
19
+ * of a group's tags are present the group is left untouched, but if *any* one is, the entire group is
20
+ * rebuilt from just what's present in `el` (a real client's own Contacts edit UI typically resends every
21
+ * field it manages anyway, so this only under-preserves data for a client that deliberately sends a
22
+ * single-slot partial update within one of these groups - a documented simplification, not silent data
23
+ * loss for the common case). Emails lose their original `type` on any round trip through a `Change`
24
+ * (rebuilt as `ContactAddressKind.OTHER`) since EAS's own `Email1/2/3Address` tags carry no kind at all,
25
+ * matching `toApplicationData`'s own already-documented encode-side loss of the same information.
26
+ * `categories` is ghosted as its own whole group (same rule as `emails`/`phones`/`addresses`): an absent
27
+ * `Categories` element leaves `Contact.categories` untouched, while a present one - even `<Categories/>`
28
+ * with no `Category` children - rebuilds it from scratch (an empty array clears it).
29
+ */
30
+ fromApplicationData(el: WbxmlElement): Partial<Contact>;
31
+ private addressTags;
32
+ private parseAddress;
33
+ }
@@ -0,0 +1,55 @@
1
+ import type { RecoverableBaseEntity } from "@rapidrest/service-core";
2
+ import type { Mailbox } from "@rapidmx/restapi";
3
+ import type { WbxmlElement } from "../codec/WbxmlElement.js";
4
+ /**
5
+ * Maps one application entity type (`Message`/`Contact`/`CalendarEvent`/`Task`) to and from the EAS `Sync`
6
+ * command's per-collection wire representation. `SyncCommand` itself only knows the generic Add/Change/Delete
7
+ * cursor mechanics (shared with `FolderSyncCommand` via `EasSyncKeyUtils`) - everything entity-specific (which
8
+ * fields go in `ApplicationData`, on which code pages) lives in one adapter per collection type, keyed by the
9
+ * MS-ASCMD `Class` value (`"Email"`, `"Contacts"`, `"Calendar"`, `"Tasks"`) it answers to.
10
+ *
11
+ * `fromApplicationData` is deliberately optional: `SyncCommand` uses its absence as the capability check,
12
+ * answering Status `6` for `Add`/`Change` on a collection whose adapter doesn't implement it, rather than
13
+ * needing a separate flag that could drift out of sync with which adapters actually implement it. Every
14
+ * adapter today (`Email`, `Contacts`, `Calendar`, `Tasks`) implements it.
15
+ *
16
+ * @author Jean-Philippe Steinmetz
17
+ */
18
+ export interface EasCollectionSyncAdapter<T extends RecoverableBaseEntity> {
19
+ /** The MS-ASCMD `Class` value this adapter handles, e.g. `"Email"`. */
20
+ readonly collectionClass: string;
21
+ /** Builds the `<ApplicationData>` element for one `Add`/`Change` command reporting `item`. May return a
22
+ * `Promise` - `EmailSyncAdapter` needs this to resolve `Message.labelUids` against the `Label` repo before
23
+ * rendering `Categories`; every other adapter today returns a plain `WbxmlElement`, which callers `await`
24
+ * through unchanged (the same optional-async shape `fromApplicationData` already established below). */
25
+ toApplicationData(item: T): WbxmlElement | Promise<WbxmlElement>;
26
+ /**
27
+ * Parses one client-originated `Add`/`Change` command's `<ApplicationData>` element (`el`) into a partial
28
+ * entity update. Only fields actually present in `el` are included in the result - an omitted field means
29
+ * "unchanged" (MS-ASCMD's own "ghosted property" rule - see `SyncCommand`'s doc comment), never "clear this
30
+ * field" - which is what lets the same method serve both `Add` (the partial is merged onto a fresh
31
+ * `{mailboxUid, folderUid}` baseline) and `Change` (the partial is merged onto `existing`).
32
+ *
33
+ * May return a `Promise` - `EmailSyncAdapter` needs this for a Draft `Add`/`Change`'s body, which must be
34
+ * written to `BlobStore` before the resulting `bodyBlobKey` is known; every other adapter today returns a
35
+ * plain object, which `await`s through unchanged.
36
+ *
37
+ * @param el The command's `<ApplicationData>` element.
38
+ * @param existing The item being changed, for a `Change` command; `undefined` for `Add`. Adapters that
39
+ * need to know the item's current field values to correctly interpret a partial update - `EmailSyncAdapter`
40
+ * uses this to reuse an existing Draft's `bodyBlobKey` on `Change` rather than minting a new one.
41
+ */
42
+ fromApplicationData?(el: WbxmlElement, existing?: T): Partial<T> | Promise<Partial<T>>;
43
+ /**
44
+ * Supplies default field values for a brand-new entity created via a client-originated `Add`, applied
45
+ * *before* `fromApplicationData`'s own partial is merged on top (so anything the client actually sent
46
+ * always wins). For defaults a fresh entity needs regardless of what the client sent - `CalendarSyncAdapter`
47
+ * uses this for `icalUid`/`sequence`, since EAS's own `Add` command has no wire representation for either
48
+ * (a device doesn't know or send an iCalendar UID) but a stored default of `""` for every Sync-created
49
+ * event (this model's own fallback, see `CalendarEventMongo`'s constructor) would violate RFC 5545's own
50
+ * uniqueness expectation for `UID`. `EmailSyncAdapter` uses `mailbox` to populate a new Draft's `from`.
51
+ * Optional; only implemented where a collection actually needs it - most adapters have no such gap (and
52
+ * ignore the `mailbox` parameter entirely).
53
+ */
54
+ newEntityDefaults?(mailbox: Mailbox): Partial<T>;
55
+ }
@@ -0,0 +1,71 @@
1
+ import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
+ import type { EasCollectionSyncAdapter } from "./EasCollectionSyncAdapter.js";
3
+ import { type Mailbox, type Message } from "@rapidmx/restapi";
4
+ /**
5
+ * Maps `Message` to/from the EAS `Sync` `Email` collection class (MS-ASEMAIL). Only a plain-text preview of the
6
+ * body is included here (`Message.bodyPreview`, always already loaded on the entity, `Truncated: 1`) rather
7
+ * than the full sanitized HTML body from the `BlobStore` - a real device fetches the full body on demand via
8
+ * `ItemOperations`' `Fetch` (see the architecture plan's command table), the same two-step "list, then fetch
9
+ * body" flow every real EAS client already implements for exactly this reason (bodies can be large; a sync
10
+ * window's Add/Change list shouldn't have to pull every one of them from blob storage up front).
11
+ *
12
+ * Also handles client-originated `Add`/`Change` for Drafts (`SyncCommand`'s own doc comment covers why this is
13
+ * the only `Email` write EAS itself allows) - a plain-text-only pragmatic subset: no HTML body, no attachments
14
+ * (mirrors `ComposeMailCommand`'s own already-documented attachment gap). `To`/`Cc`/`Bcc` (the latter MS-ASEMAIL2's
15
+ * own `Bcc` tag) are ghosted independently per recipient type, not as one combined group - a `Change` touching
16
+ * only one of them leaves the others untouched, carried over from `existing.recipients`.
17
+ *
18
+ * Emits MS-ASEMAIL2's `Email2:ConversationId` (read-only - no `fromApplicationData` handling, since EAS itself
19
+ * never lets a client set it) whenever `Message.conversationId` is populated, so a device's threaded-view UI can
20
+ * group messages the same way `BaseMessageRoute.conversations()` does server-side. See `encodeConversationId`'s
21
+ * own doc comment for the wire encoding, and `ItemOperationsCommand`'s `Move` handling for the one place this
22
+ * gets decoded back.
23
+ *
24
+ * Emits MS-ASEMAIL's `Categories`/`Category` (also read-only) from `Message.labelUids`, resolved against the
25
+ * `Label` repo to real display names - a stale `labelUids` entry (the label was since deleted) is silently
26
+ * dropped rather than surfacing as an error, the same "tolerate a dangling reference" stance `SearchCommand`
27
+ * already takes for a stale search-index entry. Deliberately not writable: unlike `Contact.categories` (a
28
+ * plain free-form string array with no separate entity behind it), a `Label` is a real mailbox-scoped entity
29
+ * referenced by uid - a write path would need to resolve category name strings back to `Label`s and create new
30
+ * ones on the fly for names that don't exist yet, real added scope this pragmatic subset defers. One `find()`
31
+ * per message that actually has labels (most won't, since labels are opt-in) - not batched across a whole Sync
32
+ * page/search result set, a documented, modest N+1 tradeoff rather than widening this adapter's own interface
33
+ * further to let a caller pre-resolve names for a whole batch.
34
+ *
35
+ * `labelClass` is supplied by the Mongo/SQL concrete subclasses.
36
+ *
37
+ * @author Jean-Philippe Steinmetz
38
+ */
39
+ export declare abstract class EmailSyncAdapter implements EasCollectionSyncAdapter<Message> {
40
+ readonly collectionClass = "Email";
41
+ protected abstract labelClass: any;
42
+ private _objectFactory?;
43
+ private labelRepo?;
44
+ private blobStore?;
45
+ init(): Promise<void>;
46
+ toApplicationData(message: Message): Promise<WbxmlElement>;
47
+ private resolveLabelNames;
48
+ /**
49
+ * `Message.bodyBlobKey` is documented (see the `Message` interface itself) as holding raw MIME "unmodified
50
+ * from ingestion/send" - `ItemOperationsCommand.fetchMessage` parses it with `simpleParser` unconditionally
51
+ * for every message, Draft or not. A Draft's plain-text body is therefore wrapped in a minimal valid
52
+ * RFC 5322 message here (via `buildPlainTextMime`) rather than stored as bare text, so that contract holds
53
+ * for every consumer, not just this write path - a Draft created/edited via `Sync` must `Fetch` correctly
54
+ * the same way any other message does.
55
+ */
56
+ fromApplicationData(el: WbxmlElement, existing?: Message): Promise<Partial<Message>>;
57
+ /** Defaults for a brand-new Draft created via a client-originated `Add` - `from` is the caller's own
58
+ * mailbox address, per `ComposeMailCommand`'s identical `{ address, type: RecipientType.TO }` shape
59
+ * convention for a `from` field (the `Recipient` struct's `type` is only meaningful for real recipients;
60
+ * it's reused here as a harmless placeholder). */
61
+ newEntityDefaults(mailbox: Mailbox): Partial<Message>;
62
+ }
63
+ /** Encodes `Message.conversationId` (an internal string uid) into the opaque binary blob MS-ASEMAIL2's
64
+ * `Email2:ConversationId` carries on the wire. The spec never mandates any particular binary format for this
65
+ * value - a real Exchange server mints a GUID, but a client only ever compares/echoes it byte-for-byte, never
66
+ * interprets it - so encoding the uid's own UTF-8 bytes directly (rather than hashing into a 16-byte GUID
67
+ * shape) is a valid, simpler choice that `decodeConversationId` can invert exactly, which `ItemOperationsCommand`
68
+ * relies on to resolve an `ItemOperations` `Move`'s `ConversationId` back into this same uid. */
69
+ export declare function encodeConversationId(conversationId: string): Buffer;
70
+ /** Reverse of `encodeConversationId`. */
71
+ export declare function decodeConversationId(opaque: Buffer): string;