@mj-biz-apps/common-activity-sync 0.0.0 → 5.37.0

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 (90) hide show
  1. package/dist/ActivitySyncEngine.d.ts +118 -0
  2. package/dist/ActivitySyncEngine.d.ts.map +1 -0
  3. package/dist/ActivitySyncEngine.js +620 -0
  4. package/dist/ActivitySyncEngine.js.map +1 -0
  5. package/dist/BaseActivitySyncExtension.d.ts +99 -0
  6. package/dist/BaseActivitySyncExtension.d.ts.map +1 -0
  7. package/dist/BaseActivitySyncExtension.js +3 -0
  8. package/dist/BaseActivitySyncExtension.js.map +1 -0
  9. package/dist/BaseActivitySyncProvider.d.ts +40 -0
  10. package/dist/BaseActivitySyncProvider.d.ts.map +1 -0
  11. package/dist/BaseActivitySyncProvider.js +76 -0
  12. package/dist/BaseActivitySyncProvider.js.map +1 -0
  13. package/dist/action-result.d.ts +38 -0
  14. package/dist/action-result.d.ts.map +1 -0
  15. package/dist/action-result.js +43 -0
  16. package/dist/action-result.js.map +1 -0
  17. package/dist/entity-names.d.ts +19 -0
  18. package/dist/entity-names.d.ts.map +1 -0
  19. package/dist/entity-names.js +19 -0
  20. package/dist/entity-names.js.map +1 -0
  21. package/dist/extensions.d.ts +37 -0
  22. package/dist/extensions.d.ts.map +1 -0
  23. package/dist/extensions.js +80 -0
  24. package/dist/extensions.js.map +1 -0
  25. package/dist/identity.d.ts +30 -0
  26. package/dist/identity.d.ts.map +1 -0
  27. package/dist/identity.js +90 -0
  28. package/dist/identity.js.map +1 -0
  29. package/dist/index.d.ts +29 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +29 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/load.d.ts +17 -0
  34. package/dist/load.d.ts.map +1 -0
  35. package/dist/load.js +29 -0
  36. package/dist/load.js.map +1 -0
  37. package/dist/manual-log.d.ts +33 -0
  38. package/dist/manual-log.d.ts.map +1 -0
  39. package/dist/manual-log.js +230 -0
  40. package/dist/manual-log.js.map +1 -0
  41. package/dist/participants.d.ts +47 -0
  42. package/dist/participants.d.ts.map +1 -0
  43. package/dist/participants.js +59 -0
  44. package/dist/participants.js.map +1 -0
  45. package/dist/providers/FixtureActivitySyncProvider.d.ts +21 -0
  46. package/dist/providers/FixtureActivitySyncProvider.d.ts.map +1 -0
  47. package/dist/providers/FixtureActivitySyncProvider.js +58 -0
  48. package/dist/providers/FixtureActivitySyncProvider.js.map +1 -0
  49. package/dist/providers/GraphMessageMapper.d.ts +37 -0
  50. package/dist/providers/GraphMessageMapper.d.ts.map +1 -0
  51. package/dist/providers/GraphMessageMapper.js +91 -0
  52. package/dist/providers/GraphMessageMapper.js.map +1 -0
  53. package/dist/providers/MSGraphActivitySyncProvider.d.ts +22 -0
  54. package/dist/providers/MSGraphActivitySyncProvider.d.ts.map +1 -0
  55. package/dist/providers/MSGraphActivitySyncProvider.js +62 -0
  56. package/dist/providers/MSGraphActivitySyncProvider.js.map +1 -0
  57. package/dist/providers/MSGraphCalendarSyncProvider.d.ts +60 -0
  58. package/dist/providers/MSGraphCalendarSyncProvider.d.ts.map +1 -0
  59. package/dist/providers/MSGraphCalendarSyncProvider.js +145 -0
  60. package/dist/providers/MSGraphCalendarSyncProvider.js.map +1 -0
  61. package/dist/qualification.d.ts +91 -0
  62. package/dist/qualification.d.ts.map +1 -0
  63. package/dist/qualification.js +62 -0
  64. package/dist/qualification.js.map +1 -0
  65. package/dist/run.d.ts +67 -0
  66. package/dist/run.d.ts.map +1 -0
  67. package/dist/run.js +84 -0
  68. package/dist/run.js.map +1 -0
  69. package/dist/sql.d.ts +17 -0
  70. package/dist/sql.d.ts.map +1 -0
  71. package/dist/sql.js +50 -0
  72. package/dist/sql.js.map +1 -0
  73. package/dist/stages.d.ts +55 -0
  74. package/dist/stages.d.ts.map +1 -0
  75. package/dist/stages.js +109 -0
  76. package/dist/stages.js.map +1 -0
  77. package/dist/types.d.ts +130 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +38 -0
  80. package/dist/types.js.map +1 -0
  81. package/dist/watermark.d.ts +63 -0
  82. package/dist/watermark.d.ts.map +1 -0
  83. package/dist/watermark.js +103 -0
  84. package/dist/watermark.js.map +1 -0
  85. package/dist/writer.d.ts +132 -0
  86. package/dist/writer.d.ts.map +1 -0
  87. package/dist/writer.js +326 -0
  88. package/dist/writer.js.map +1 -0
  89. package/package.json +32 -5
  90. package/README.md +0 -12
package/dist/run.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @fileoverview The run vocabulary: activation, dry run, per-message decisions, and the
3
+ * provider→connection→rule policy chain.
4
+ *
5
+ * Everything here is pure. That is deliberate — these are the decisions a reviewer most needs to be
6
+ * able to reason about without a database, and the ones a dry run exists to preview.
7
+ *
8
+ * @module @mj-biz-apps/common-activity-sync
9
+ */
10
+ /** Mirrors `CK_ActivitySyncConnection_Status`. */
11
+ export type ConnectionStatus = 'Active' | 'Paused' | 'Error' | 'Disabled';
12
+ /** Mirrors `CK_ActivitySyncRun_TriggerType`. */
13
+ export type SyncTriggerType = 'Scheduled' | 'Manual' | 'Webhook' | 'Backfill';
14
+ /** Mirrors `CK_ActivitySyncRunDetail_Decision`. */
15
+ export type SyncDecision = 'Included' | 'Excluded' | 'Duplicate' | 'Failed'
16
+ /** Dry-run outcomes. Nothing was written; this is what WOULD have happened. */
17
+ | 'WouldInclude' | 'WouldExclude';
18
+ /** Mirrors the `SkippedContentPolicy` CHECKs on both the provider type and the connection. */
19
+ export type SkippedContentPolicy = 'None' | 'SubjectEncrypted' | 'FullEncrypted';
20
+ export interface SyncRunOptions {
21
+ /**
22
+ * Evaluate and report without writing.
23
+ *
24
+ * A dry run fetches, qualifies and resolves exactly as a real run does, then writes only the
25
+ * `ActivitySyncRun` and its details — never an `Activity`, never an `ActivityLink`, never an
26
+ * attachment, and it never advances the watermark. It is the only safe way to see what a rule
27
+ * set would do to a real mailbox before pointing it at one, and `CK_ActivitySyncRun_DryRunNoWatermark`
28
+ * enforces the watermark half at the database rather than trusting the engine.
29
+ */
30
+ DryRun: boolean;
31
+ TriggerType: SyncTriggerType;
32
+ /** Hard cap on items fetched this pass. */
33
+ Limit: number;
34
+ }
35
+ /**
36
+ * Whether a connection should sync right now.
37
+ *
38
+ * `Status` is the master switch; `StartAt`/`EndAt` are an activation WINDOW, so a mailbox can be
39
+ * provisioned in advance or retired on a date without anyone remembering to flip a switch. Both
40
+ * bounds are open when null, and the window only ever narrows what `Status` already allows.
41
+ */
42
+ export declare function IsConnectionActive(status: ConnectionStatus, startAt: Date | null, endAt: Date | null, now: Date): boolean;
43
+ /**
44
+ * Resolve a policy down the chain, most specific wins, `null` meaning "inherit".
45
+ *
46
+ * The chain is always provider type → connection → (where it applies) rule. An operator configures
47
+ * storage, encryption and caps ONCE per provider; a connection overrides only when that mailbox is
48
+ * genuinely different. That is the difference between configuring a fleet and configuring a mailbox
49
+ * at a time.
50
+ */
51
+ export declare function ResolvePolicy<T>(providerDefault: T, ...overrides: (T | null | undefined)[]): T;
52
+ /**
53
+ * Whether captured content may be retained for a message the engine declined to ingest.
54
+ *
55
+ * Retention is only ever permissible ENCRYPTED, so a policy above `None` without a key is not a
56
+ * configuration to honour — it is a misconfiguration to refuse. The database enforces the same pair
57
+ * on both ends (`CK_ActivitySyncProviderType_KeyRequired`, `CK_ActivitySyncRunDetail_ContentKey`);
58
+ * this is the runtime half, so the engine fails loudly rather than silently writing plaintext or
59
+ * silently writing nothing.
60
+ */
61
+ export declare function ResolveCapturePlan(policy: SkippedContentPolicy, encryptionKeyID: string | null): {
62
+ Capture: 'None' | 'Subject' | 'Full';
63
+ EncryptionKeyID: string | null;
64
+ };
65
+ /** The dry-run counterpart of a decision, so a preview never claims to have written anything. */
66
+ export declare function AsDryRunDecision(decision: SyncDecision): SyncDecision;
67
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,kDAAkD;AAClD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE1E,gDAAgD;AAChD,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAE9E,mDAAmD;AACnD,MAAM,MAAM,YAAY,GAClB,UAAU,GACV,UAAU,GACV,WAAW,GACX,QAAQ;AACV,+EAA+E;GAC7E,cAAc,GACd,cAAc,CAAC;AAErB,8FAA8F;AAC9F,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC3B;;;;;;;;OAQG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,eAAe,CAAC;IAC7B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,IAAI,GAAG,IAAI,EACpB,KAAK,EAAE,IAAI,GAAG,IAAI,EAClB,GAAG,EAAE,IAAI,GACV,OAAO,CAiBT;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,CAAC,CAQ9F;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,oBAAoB,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,GAC/B;IAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAa1E;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,YAAY,CASrE"}
package/dist/run.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @fileoverview The run vocabulary: activation, dry run, per-message decisions, and the
3
+ * provider→connection→rule policy chain.
4
+ *
5
+ * Everything here is pure. That is deliberate — these are the decisions a reviewer most needs to be
6
+ * able to reason about without a database, and the ones a dry run exists to preview.
7
+ *
8
+ * @module @mj-biz-apps/common-activity-sync
9
+ */
10
+ /**
11
+ * Whether a connection should sync right now.
12
+ *
13
+ * `Status` is the master switch; `StartAt`/`EndAt` are an activation WINDOW, so a mailbox can be
14
+ * provisioned in advance or retired on a date without anyone remembering to flip a switch. Both
15
+ * bounds are open when null, and the window only ever narrows what `Status` already allows.
16
+ */
17
+ export function IsConnectionActive(status, startAt, endAt, now) {
18
+ /**
19
+ * `Error` retries. `Paused` and `Disabled` are a person deciding the mailbox
20
+ * is off and must be honoured. `Error` is a record of the last run and says
21
+ * nothing about whether the next one will work — refusing it latches a
22
+ * throttle into a permanent stop.
23
+ */
24
+ if (status !== 'Active' && status !== 'Error') {
25
+ return false;
26
+ }
27
+ if (startAt !== null && now.getTime() < startAt.getTime()) {
28
+ return false;
29
+ }
30
+ if (endAt !== null && now.getTime() > endAt.getTime()) {
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+ /**
36
+ * Resolve a policy down the chain, most specific wins, `null` meaning "inherit".
37
+ *
38
+ * The chain is always provider type → connection → (where it applies) rule. An operator configures
39
+ * storage, encryption and caps ONCE per provider; a connection overrides only when that mailbox is
40
+ * genuinely different. That is the difference between configuring a fleet and configuring a mailbox
41
+ * at a time.
42
+ */
43
+ export function ResolvePolicy(providerDefault, ...overrides) {
44
+ for (let i = overrides.length - 1; i >= 0; i--) {
45
+ const candidate = overrides[i];
46
+ if (candidate !== null && candidate !== undefined) {
47
+ return candidate;
48
+ }
49
+ }
50
+ return providerDefault;
51
+ }
52
+ /**
53
+ * Whether captured content may be retained for a message the engine declined to ingest.
54
+ *
55
+ * Retention is only ever permissible ENCRYPTED, so a policy above `None` without a key is not a
56
+ * configuration to honour — it is a misconfiguration to refuse. The database enforces the same pair
57
+ * on both ends (`CK_ActivitySyncProviderType_KeyRequired`, `CK_ActivitySyncRunDetail_ContentKey`);
58
+ * this is the runtime half, so the engine fails loudly rather than silently writing plaintext or
59
+ * silently writing nothing.
60
+ */
61
+ export function ResolveCapturePlan(policy, encryptionKeyID) {
62
+ if (policy === 'None') {
63
+ return { Capture: 'None', EncryptionKeyID: null };
64
+ }
65
+ if (encryptionKeyID === null) {
66
+ throw new Error(`SkippedContentPolicy is "${policy}" but no encryption key is configured. Retaining ` +
67
+ `content from a message that was deliberately not ingested is only permissible ` +
68
+ `encrypted — set an encryption key on the provider type or the connection, or set ` +
69
+ `the policy to "None".`);
70
+ }
71
+ return { Capture: policy === 'SubjectEncrypted' ? 'Subject' : 'Full', EncryptionKeyID: encryptionKeyID };
72
+ }
73
+ /** The dry-run counterpart of a decision, so a preview never claims to have written anything. */
74
+ export function AsDryRunDecision(decision) {
75
+ switch (decision) {
76
+ case 'Included':
77
+ return 'WouldInclude';
78
+ case 'Excluded':
79
+ return 'WouldExclude';
80
+ default:
81
+ return decision;
82
+ }
83
+ }
84
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqCH;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAC9B,MAAwB,EACxB,OAAoB,EACpB,KAAkB,EAClB,GAAS;IAET;;;;;OAKG;IACH,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAI,eAAkB,EAAE,GAAG,SAAmC;IACvF,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAChD,OAAO,SAAS,CAAC;QACrB,CAAC;IACL,CAAC;IACD,OAAO,eAAe,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAC9B,MAA4B,EAC5B,eAA8B;IAE9B,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;IACtD,CAAC;IACD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACX,4BAA4B,MAAM,mDAAmD;YACjF,gFAAgF;YAChF,mFAAmF;YACnF,uBAAuB,CAC9B,CAAC;IACN,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,CAAC;AAC7G,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,gBAAgB,CAAC,QAAsB;IACnD,QAAQ,QAAQ,EAAE,CAAC;QACf,KAAK,UAAU;YACX,OAAO,cAAc,CAAC;QAC1B,KAAK,UAAU;YACX,OAAO,cAAc,CAAC;QAC1B;YACI,OAAO,QAAQ,CAAC;IACxB,CAAC;AACL,CAAC"}
package/dist/sql.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Values that reach ExtraFilter. ExtraFilter is a string, not parameters.
3
+ *
4
+ * IDs: reject anything that is not a UUID. Addresses and other free text:
5
+ * coerce, drop NULs, double single quotes. A malformed id must not become a
6
+ * widened result set.
7
+ */
8
+ export declare class InvalidFilterInputError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ export declare function RequireUUID(value: string, field: string): string;
12
+ export declare function RequireUUIDs(values: readonly unknown[] | null | undefined, field: string): string[];
13
+ /** Free text for a SQL string literal (addresses, type codes, external ids). */
14
+ export declare function EscapeText(value: unknown): string;
15
+ export declare function InList(values: readonly unknown[]): string;
16
+ export declare function UuidInList(values: readonly unknown[], field: string): string;
17
+ //# sourceMappingURL=sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,qBAAa,uBAAwB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI9B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAQnG;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIjD;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAKzD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM5E"}
package/dist/sql.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Values that reach ExtraFilter. ExtraFilter is a string, not parameters.
3
+ *
4
+ * IDs: reject anything that is not a UUID. Addresses and other free text:
5
+ * coerce, drop NULs, double single quotes. A malformed id must not become a
6
+ * widened result set.
7
+ */
8
+ const UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
9
+ export class InvalidFilterInputError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'InvalidFilterInputError';
13
+ }
14
+ }
15
+ export function RequireUUID(value, field) {
16
+ if (typeof value !== 'string' || !UUID.test(value)) {
17
+ throw new InvalidFilterInputError(`${field} must be a UUID.`);
18
+ }
19
+ return value;
20
+ }
21
+ export function RequireUUIDs(values, field) {
22
+ if (!values?.length)
23
+ return [];
24
+ return values.map((v) => {
25
+ if (typeof v !== 'string') {
26
+ throw new InvalidFilterInputError(`${field} must be a UUID.`);
27
+ }
28
+ return RequireUUID(v, field);
29
+ });
30
+ }
31
+ /** Free text for a SQL string literal (addresses, type codes, external ids). */
32
+ export function EscapeText(value) {
33
+ return String(value ?? '')
34
+ .replace(/\0/g, '')
35
+ .replace(/'/g, "''");
36
+ }
37
+ export function InList(values) {
38
+ if (values.length === 0) {
39
+ return `'00000000-0000-0000-0000-000000000000'`;
40
+ }
41
+ return values.map((v) => `'${EscapeText(v)}'`).join(',');
42
+ }
43
+ export function UuidInList(values, field) {
44
+ const ids = RequireUUIDs(values, field);
45
+ if (ids.length === 0) {
46
+ return `'00000000-0000-0000-0000-000000000000'`;
47
+ }
48
+ return ids.map((id) => `'${id}'`).join(',');
49
+ }
50
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,IAAI,GACN,+EAA+E,CAAC;AAEpF,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IAC1C,CAAC;CACJ;AAED,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,KAAa;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,uBAAuB,CAAC,GAAG,KAAK,kBAAkB,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAA6C,EAAE,KAAa;IACrF,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IAC/B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACpB,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,IAAI,uBAAuB,CAAC,GAAG,KAAK,kBAAkB,CAAC,CAAC;QAClE,CAAC;QACD,OAAO,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,KAAc;IACrC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,MAA0B;IAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,wCAAwC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAA0B,EAAE,KAAa;IAChE,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO,wCAAwC,CAAC;IACpD,CAAC;IACD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Deterministic qualification stages. Exclusions run first and are absolute.
3
+ */
4
+ import { type ParticipantScope } from './participants.js';
5
+ import type { IQualificationStage, QualificationContext, QualificationVerdict } from './qualification.js';
6
+ import type { NormalizedItem } from './types.js';
7
+ export interface ExclusionRow {
8
+ ID: string;
9
+ IdentityKind: string | null;
10
+ IdentityValue: string;
11
+ ActivitySyncRuleSetID: string | null;
12
+ }
13
+ export interface RuleRow {
14
+ ID: string;
15
+ Sequence: number;
16
+ IsEnabled: boolean;
17
+ Action: 'Include' | 'Exclude';
18
+ Direction: 'Inbound' | 'Outbound' | 'Internal' | null;
19
+ DateFrom: Date | string | null;
20
+ DateTo: Date | string | null;
21
+ ParticipantScope: ParticipantScope | null;
22
+ ActivitySyncRuleSetID: string | null;
23
+ }
24
+ export interface KnownAddressHit {
25
+ Address: string;
26
+ }
27
+ export interface EngineQualificationContext extends QualificationContext {
28
+ Exclusions: ExclusionRow[];
29
+ Rules: RuleRow[];
30
+ InternalDomains: string[];
31
+ KnownAddresses: Map<string, KnownAddressHit>;
32
+ }
33
+ /** Stage 0. An exclusion is not a rule a later Include can outrank. */
34
+ export declare class ExclusionStage implements IQualificationStage {
35
+ readonly Name = "Exclusions";
36
+ readonly RequiresInference = false;
37
+ Evaluate(item: NormalizedItem, context: QualificationContext): Promise<QualificationVerdict>;
38
+ }
39
+ /** Stage 1. Bound rule sets, in sequence. */
40
+ export declare class RulesStage implements IQualificationStage {
41
+ readonly Name = "Rules";
42
+ readonly RequiresInference = false;
43
+ Evaluate(item: NormalizedItem, context: QualificationContext): Promise<QualificationVerdict>;
44
+ }
45
+ /**
46
+ * Stage 2. Exact ContactMethod address match — never a domain match.
47
+ * A hit Includes; no hit abstains so the provider-type default (Exclude for mailboxes) decides.
48
+ */
49
+ export declare class KnownParticipantStage implements IQualificationStage {
50
+ readonly Name = "KnownParticipant";
51
+ readonly RequiresInference = false;
52
+ Evaluate(item: NormalizedItem, context: QualificationContext): Promise<QualificationVerdict>;
53
+ }
54
+ export declare function DefaultDeterministicStages(): IQualificationStage[];
55
+ //# sourceMappingURL=stages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stages.d.ts","sourceRoot":"","sources":["../src/stages.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAA2D,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACnH,OAAO,KAAK,EACR,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,OAAO;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC;IACtD,QAAQ,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA2B,SAAQ,oBAAoB;IACpE,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAChD;AAQD,uEAAuE;AACvE,qBAAa,cAAe,YAAW,mBAAmB;IACtD,SAAgB,IAAI,gBAAgB;IACpC,SAAgB,iBAAiB,SAAS;IAE7B,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAsB5G;AAED,6CAA6C;AAC7C,qBAAa,UAAW,YAAW,mBAAmB;IAClD,SAAgB,IAAI,WAAW;IAC/B,SAAgB,iBAAiB,SAAS;IAE7B,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA0B5G;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,mBAAmB;IAC7D,SAAgB,IAAI,sBAAsB;IAC1C,SAAgB,iBAAiB,SAAS;IAE7B,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAmB5G;AAED,wBAAgB,0BAA0B,IAAI,mBAAmB,EAAE,CAElE"}
package/dist/stages.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Deterministic qualification stages. Exclusions run first and are absolute.
3
+ */
4
+ import { ClassifyParticipants, DomainOf, MatchesParticipantScope } from './participants.js';
5
+ function asDate(value) {
6
+ if (value == null)
7
+ return null;
8
+ const d = value instanceof Date ? value : new Date(value);
9
+ return Number.isNaN(d.getTime()) ? null : d;
10
+ }
11
+ /** Stage 0. An exclusion is not a rule a later Include can outrank. */
12
+ export class ExclusionStage {
13
+ constructor() {
14
+ this.Name = 'Exclusions';
15
+ this.RequiresInference = false;
16
+ }
17
+ async Evaluate(item, context) {
18
+ const ctx = context;
19
+ const addresses = item.Participants.map((p) => p.Address.trim().toLowerCase()).filter(Boolean);
20
+ for (const exclusion of ctx.Exclusions ?? []) {
21
+ const needle = exclusion.IdentityValue.trim().toLowerCase().replace(/^@/, '');
22
+ if (!needle)
23
+ continue;
24
+ for (const address of addresses) {
25
+ const domain = DomainOf(address);
26
+ const identityHit = address === needle || address.endsWith('@' + needle);
27
+ const domainHit = domain === needle;
28
+ if (identityHit || domainHit) {
29
+ return {
30
+ Decision: 'Exclude',
31
+ Reason: `Exclusion ${exclusion.ID} matched ${address}`,
32
+ StageName: this.Name,
33
+ ActivitySyncExclusionID: exclusion.ID,
34
+ };
35
+ }
36
+ }
37
+ }
38
+ return { Decision: 'Undecided', Reason: 'No exclusion matched', StageName: this.Name };
39
+ }
40
+ }
41
+ /** Stage 1. Bound rule sets, in sequence. */
42
+ export class RulesStage {
43
+ constructor() {
44
+ this.Name = 'Rules';
45
+ this.RequiresInference = false;
46
+ }
47
+ async Evaluate(item, context) {
48
+ const ctx = context;
49
+ const rules = [...(ctx.Rules ?? [])]
50
+ .filter((r) => r.IsEnabled)
51
+ .sort((a, b) => a.Sequence - b.Sequence);
52
+ for (const rule of rules) {
53
+ if (rule.Direction && rule.Direction !== item.Direction)
54
+ continue;
55
+ const from = asDate(rule.DateFrom);
56
+ const to = asDate(rule.DateTo);
57
+ if (from && item.StartedAt.getTime() < from.getTime())
58
+ continue;
59
+ if (to && item.StartedAt.getTime() > to.getTime())
60
+ continue;
61
+ if (rule.ParticipantScope && rule.ParticipantScope !== 'Any') {
62
+ const composition = ClassifyParticipants(item.Participants, ctx.InternalDomains ?? []);
63
+ if (!MatchesParticipantScope(rule.ParticipantScope, composition)) {
64
+ continue;
65
+ }
66
+ }
67
+ return {
68
+ Decision: rule.Action,
69
+ Reason: `Rule ${rule.ID} ${rule.Action}`,
70
+ StageName: this.Name,
71
+ ActivitySyncRuleID: rule.ID,
72
+ };
73
+ }
74
+ return { Decision: 'Undecided', Reason: 'No rule matched', StageName: this.Name };
75
+ }
76
+ }
77
+ /**
78
+ * Stage 2. Exact ContactMethod address match — never a domain match.
79
+ * A hit Includes; no hit abstains so the provider-type default (Exclude for mailboxes) decides.
80
+ */
81
+ export class KnownParticipantStage {
82
+ constructor() {
83
+ this.Name = 'KnownParticipant';
84
+ this.RequiresInference = false;
85
+ }
86
+ async Evaluate(item, context) {
87
+ const ctx = context;
88
+ const known = ctx.KnownAddresses ?? new Map();
89
+ for (const participant of item.Participants) {
90
+ const address = participant.Address.trim().toLowerCase();
91
+ if (address && known.has(address)) {
92
+ return {
93
+ Decision: 'Include',
94
+ Reason: `Known contact method ${address}`,
95
+ StageName: this.Name,
96
+ };
97
+ }
98
+ }
99
+ return {
100
+ Decision: 'Undecided',
101
+ Reason: 'No participant matched a stored ContactMethod',
102
+ StageName: this.Name,
103
+ };
104
+ }
105
+ }
106
+ export function DefaultDeterministicStages() {
107
+ return [new ExclusionStage(), new RulesStage(), new KnownParticipantStage()];
108
+ }
109
+ //# sourceMappingURL=stages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stages.js","sourceRoot":"","sources":["../src/stages.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,uBAAuB,EAAyB,MAAM,mBAAmB,CAAC;AAsCnH,SAAS,MAAM,CAAC,KAAuC;IACnD,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,CAAC,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,uEAAuE;AACvE,MAAM,OAAO,cAAc;IAA3B;QACoB,SAAI,GAAG,YAAY,CAAC;QACpB,sBAAiB,GAAG,KAAK,CAAC;IAwB9C,CAAC;IAtBU,KAAK,CAAC,QAAQ,CAAC,IAAoB,EAAE,OAA6B;QACrE,MAAM,GAAG,GAAG,OAAqC,CAAC;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/F,KAAK,MAAM,SAAS,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC9E,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;gBAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;gBACjC,MAAM,WAAW,GAAG,OAAO,KAAK,MAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;gBACzE,MAAM,SAAS,GAAG,MAAM,KAAK,MAAM,CAAC;gBACpC,IAAI,WAAW,IAAI,SAAS,EAAE,CAAC;oBAC3B,OAAO;wBACH,QAAQ,EAAE,SAAS;wBACnB,MAAM,EAAE,aAAa,SAAS,CAAC,EAAE,YAAY,OAAO,EAAE;wBACtD,SAAS,EAAE,IAAI,CAAC,IAAI;wBACpB,uBAAuB,EAAE,SAAS,CAAC,EAAE;qBACxC,CAAC;gBACN,CAAC;YACL,CAAC;QACL,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3F,CAAC;CACJ;AAED,6CAA6C;AAC7C,MAAM,OAAO,UAAU;IAAvB;QACoB,SAAI,GAAG,OAAO,CAAC;QACf,sBAAiB,GAAG,KAAK,CAAC;IA4B9C,CAAC;IA1BU,KAAK,CAAC,QAAQ,CAAC,IAAoB,EAAE,OAA6B;QACrE,MAAM,GAAG,GAAG,OAAqC,CAAC;QAClD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;aAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;gBAAE,SAAS;YAClE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC/B,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;gBAAE,SAAS;YAChE,IAAI,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE;gBAAE,SAAS;YAC5D,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB,KAAK,KAAK,EAAE,CAAC;gBAC3D,MAAM,WAAW,GAAG,oBAAoB,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;gBACvF,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,EAAE,CAAC;oBAC/D,SAAS;gBACb,CAAC;YACL,CAAC;YACD,OAAO;gBACH,QAAQ,EAAE,IAAI,CAAC,MAAM;gBACrB,MAAM,EAAE,QAAQ,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE;gBACxC,SAAS,EAAE,IAAI,CAAC,IAAI;gBACpB,kBAAkB,EAAE,IAAI,CAAC,EAAE;aAC9B,CAAC;QACN,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACtF,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IAAlC;QACoB,SAAI,GAAG,kBAAkB,CAAC;QAC1B,sBAAiB,GAAG,KAAK,CAAC;IAqB9C,CAAC;IAnBU,KAAK,CAAC,QAAQ,CAAC,IAAoB,EAAE,OAA6B;QACrE,MAAM,GAAG,GAAG,OAAqC,CAAC;QAClD,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc,IAAI,IAAI,GAAG,EAAE,CAAC;QAC9C,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1C,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACzD,IAAI,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChC,OAAO;oBACH,QAAQ,EAAE,SAAS;oBACnB,MAAM,EAAE,wBAAwB,OAAO,EAAE;oBACzC,SAAS,EAAE,IAAI,CAAC,IAAI;iBACvB,CAAC;YACN,CAAC;QACL,CAAC;QACD,OAAO;YACH,QAAQ,EAAE,WAAW;YACrB,MAAM,EAAE,+CAA+C;YACvD,SAAS,EAAE,IAAI,CAAC,IAAI;SACvB,CAAC;IACN,CAAC;CACJ;AAED,MAAM,UAAU,0BAA0B;IACtC,OAAO,CAAC,IAAI,cAAc,EAAE,EAAE,IAAI,UAAU,EAAE,EAAE,IAAI,qBAAqB,EAAE,CAAC,CAAC;AACjF,CAAC"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @fileoverview The provider-neutral shapes every activity source speaks.
3
+ *
4
+ * Nothing downstream of a provider knows what produced an item, which is what makes a fixture
5
+ * provider a genuine substitute rather than a mock: the fixture and a live mailbox hand over the
6
+ * same type, so a test exercises the real code path instead of a parallel one.
7
+ *
8
+ * @module @mj-biz-apps/common-activity-sync
9
+ */
10
+ /** Mirrors `CK_Activity_Direction`. */
11
+ export type ActivityDirection = 'Inbound' | 'Outbound' | 'Internal';
12
+ /** Mirrors `CK_ActivityLink_Role`. */
13
+ export type ActivityLinkRole = 'Regarding' | 'Participant' | 'From' | 'To' | 'Cc' | 'Bcc' | 'Organizer' | 'Attendee' | 'LoggedFor';
14
+ /** Mirrors `CK_ActivityLink_IdentityKind`. */
15
+ export type ActivityIdentityKind = 'Email' | 'Phone' | 'ExternalUser';
16
+ /**
17
+ * Runtime value lists for the two unions above, for validating link specs that arrive as data
18
+ * (JSON params on `Common.LogActivity`) rather than as typed code. The compiler checks every
19
+ * member is a legal union value; keep them in lockstep with the CHECK constraints.
20
+ */
21
+ export declare const ACTIVITY_LINK_ROLES: readonly ActivityLinkRole[];
22
+ export declare const ACTIVITY_IDENTITY_KINDS: readonly ActivityIdentityKind[];
23
+ /**
24
+ * The surface a provider reads.
25
+ *
26
+ * This is not decoration — it selects the watermark basis. See {@link WatermarkBasisForKind}.
27
+ */
28
+ export type ActivitySourceKind = 'Message' | 'Calendar' | 'Social' | 'Chat';
29
+ /**
30
+ * How a surface's watermark advances.
31
+ *
32
+ * `ItemTime` — the newest item's own timestamp. Valid only where a timestamp is always in the past.
33
+ * `ObservationTime` — when the source looked. Required wherever an item can be dated in the FUTURE.
34
+ */
35
+ export type WatermarkBasis = 'ItemTime' | 'ObservationTime';
36
+ /** One party on an item, as the source reports them — an address, not yet a person. */
37
+ export interface ItemParticipant {
38
+ /** Lower-cased by the provider. Matching is case-insensitive and normalization belongs at the edge. */
39
+ Address: string;
40
+ Name: string | null;
41
+ Role: ActivityLinkRole;
42
+ /** Which identity column an unresolved link would use. */
43
+ IdentityKind: ActivityIdentityKind;
44
+ }
45
+ /** A provider-neutral item, ready for qualification. */
46
+ export interface NormalizedItem {
47
+ /**
48
+ * The provider's own stable id — half of the dedupe key, paired with `SourceSystem`.
49
+ *
50
+ * For Graph this is the message id, NOT the RFC-822 `Message-ID` header. The distinction
51
+ * matters: the RFC header is stable across mailboxes and the Graph id is not, so keying on the
52
+ * Graph id means a message ingested from two mailboxes yields two rows.
53
+ */
54
+ ExternalID: string;
55
+ /** Groups replies. Becomes `Activity.ExternalThreadID`. */
56
+ ExternalThreadID: string | null;
57
+ /** `ActivityType.Code` — resolved by CODE so renaming a type never breaks ingestion. */
58
+ TypeCode: string;
59
+ /** Becomes `Activity.Title`. */
60
+ Subject: string;
61
+ /** Body or preview. Becomes `Activity.Description`. */
62
+ Body: string | null;
63
+ StartedAt: Date;
64
+ /** Set for calendar items; null for messages. */
65
+ EndedAt: Date | null;
66
+ /** Set for calendar items. */
67
+ Location: string | null;
68
+ Direction: ActivityDirection;
69
+ Participants: ItemParticipant[];
70
+ /**
71
+ * The item was called off and did not happen.
72
+ *
73
+ * First-class rather than something to dig out of `Raw`, because the write branches on it:
74
+ * `CK_Activity_Status` allows `'Cancelled'`, so a cancelled meeting is recorded as cancelled
75
+ * rather than as completed. Always false for messages — a sent mail cannot be un-sent.
76
+ */
77
+ Cancelled: boolean;
78
+ /** The provider payload, verbatim. Stringified into `Activity.Details`. */
79
+ Raw: Record<string, unknown>;
80
+ }
81
+ /** What the engine asks a provider for. */
82
+ export interface ActivitySourceQuery {
83
+ /** The account/mailbox/handle to read, from `ActivitySyncConnection.Mailbox`. */
84
+ Mailbox: string;
85
+ /**
86
+ * The watermark — `ActivitySyncConnection.LastSyncAt`. Null on a first run.
87
+ *
88
+ * ADVISORY TO THE PROVIDER, NOT A GUARANTEE. Several transports expose no date filter at all
89
+ * (MJ's `GetMessagesParams` is `{ Identifier, NumMessages, UnreadOnly, IncludeHeaders }`), so a
90
+ * provider may have to fetch the most recent `Limit` and let the engine discard what it has
91
+ * already seen.
92
+ */
93
+ Since: Date | null;
94
+ /** Hard cap on items returned. */
95
+ Limit: number;
96
+ }
97
+ /** Whatever a provider's transport handed back, before normalization. */
98
+ export interface RawBatch {
99
+ Payloads: Record<string, unknown>[];
100
+ /** Reported, never thrown — a partial batch is still worth filing. */
101
+ Issues: string[];
102
+ }
103
+ /** One provider pass. */
104
+ export interface ActivitySourceBatch {
105
+ Items: NormalizedItem[];
106
+ /**
107
+ * Advanced from the ITEMS (or the observation), never from a bare clock read at the end of the
108
+ * run: a wall-clock watermark skips anything that arrives while the run is in flight, whereas
109
+ * an item-derived one cannot, because it never claims to have seen past the newest thing it
110
+ * actually saw.
111
+ */
112
+ HighWatermark: Date | null;
113
+ Issues: string[];
114
+ /**
115
+ * True when the provider did not complete a look — a throw from FetchRaw / transport / auth —
116
+ * not an empty mailbox and not a deliberate live-fetch refusal. The engine must not treat
117
+ * this as Success or clear LastError.
118
+ */
119
+ Failed?: boolean;
120
+ }
121
+ /**
122
+ * The watermark basis a surface requires.
123
+ *
124
+ * A CALENDAR source must never advance on `max(StartedAt)`. A meeting's start is routinely in the
125
+ * FUTURE, so one December event sets the watermark to December and every meeting created afterwards
126
+ * for an earlier date is filtered out as already-seen — permanently, and with no error. The
127
+ * calendar simply stops ingesting.
128
+ */
129
+ export declare function WatermarkBasisForKind(kind: ActivitySourceKind): WatermarkBasis;
130
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uCAAuC;AACvC,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpE,sCAAsC;AACtC,MAAM,MAAM,gBAAgB,GACtB,WAAW,GACX,aAAa,GACb,MAAM,GACN,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,WAAW,GACX,UAAU,GACV,WAAW,CAAC;AAElB,8CAA8C;AAC9C,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,OAAO,GAAG,cAAc,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAU1D,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAAuC,CAAC;AAE3G;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,iBAAiB,CAAC;AAE5D,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC5B,uGAAuG;IACvG,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,0DAA0D;IAC1D,YAAY,EAAE,oBAAoB,CAAC;CACtC;AAED,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC3B;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,iDAAiD;IACjD,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC;;;;;;OAMG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAChC,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,sEAAsE;IACtE,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yBAAyB;AACzB,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB;;;;;OAKG;IACH,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,GAAG,cAAc,CAE9E"}
package/dist/types.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fileoverview The provider-neutral shapes every activity source speaks.
3
+ *
4
+ * Nothing downstream of a provider knows what produced an item, which is what makes a fixture
5
+ * provider a genuine substitute rather than a mock: the fixture and a live mailbox hand over the
6
+ * same type, so a test exercises the real code path instead of a parallel one.
7
+ *
8
+ * @module @mj-biz-apps/common-activity-sync
9
+ */
10
+ /**
11
+ * Runtime value lists for the two unions above, for validating link specs that arrive as data
12
+ * (JSON params on `Common.LogActivity`) rather than as typed code. The compiler checks every
13
+ * member is a legal union value; keep them in lockstep with the CHECK constraints.
14
+ */
15
+ export const ACTIVITY_LINK_ROLES = [
16
+ 'Regarding',
17
+ 'Participant',
18
+ 'From',
19
+ 'To',
20
+ 'Cc',
21
+ 'Bcc',
22
+ 'Organizer',
23
+ 'Attendee',
24
+ 'LoggedFor',
25
+ ];
26
+ export const ACTIVITY_IDENTITY_KINDS = ['Email', 'Phone', 'ExternalUser'];
27
+ /**
28
+ * The watermark basis a surface requires.
29
+ *
30
+ * A CALENDAR source must never advance on `max(StartedAt)`. A meeting's start is routinely in the
31
+ * FUTURE, so one December event sets the watermark to December and every meeting created afterwards
32
+ * for an earlier date is filtered out as already-seen — permanently, and with no error. The
33
+ * calendar simply stops ingesting.
34
+ */
35
+ export function WatermarkBasisForKind(kind) {
36
+ return kind === 'Calendar' ? 'ObservationTime' : 'ItemTime';
37
+ }
38
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC5D,WAAW;IACX,aAAa;IACb,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,WAAW;IACX,UAAU;IACV,WAAW;CACd,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAoC,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;AA2G3G;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAwB;IAC1D,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC;AAChE,CAAC"}