microsoft-graph-client 1.0.51 → 1.0.53

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 (46) hide show
  1. package/dist/api/calendar.d.ts.map +1 -1
  2. package/dist/api/calendar.js +6 -1
  3. package/dist/api/calendar.js.map +1 -1
  4. package/dist/api/chats.d.ts.map +1 -1
  5. package/dist/api/chats.js +17 -18
  6. package/dist/api/chats.js.map +1 -1
  7. package/dist/api/mail.d.ts.map +1 -1
  8. package/dist/api/mail.js +21 -6
  9. package/dist/api/mail.js.map +1 -1
  10. package/dist/api/mailFolders.d.ts +5 -1
  11. package/dist/api/mailFolders.d.ts.map +1 -1
  12. package/dist/api/mailFolders.js +48 -4
  13. package/dist/api/mailFolders.js.map +1 -1
  14. package/dist/api/messages.d.ts +9 -0
  15. package/dist/api/messages.d.ts.map +1 -1
  16. package/dist/api/messages.js +28 -1
  17. package/dist/api/messages.js.map +1 -1
  18. package/dist/api/transcripts.d.ts.map +1 -1
  19. package/dist/api/transcripts.js +7 -1
  20. package/dist/api/transcripts.js.map +1 -1
  21. package/dist/api/users.d.ts.map +1 -1
  22. package/dist/api/users.js +5 -1
  23. package/dist/api/users.js.map +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/types/mail.d.ts +3 -2
  29. package/dist/types/mail.d.ts.map +1 -1
  30. package/dist/types/mailFolder.d.ts +39 -1
  31. package/dist/types/mailFolder.d.ts.map +1 -1
  32. package/dist/types/message.d.ts +12 -0
  33. package/dist/types/message.d.ts.map +1 -1
  34. package/dist/utils/index.d.ts +4 -1
  35. package/dist/utils/index.d.ts.map +1 -1
  36. package/dist/utils/index.js +2 -1
  37. package/dist/utils/index.js.map +1 -1
  38. package/dist/utils/odata.d.ts +118 -19
  39. package/dist/utils/odata.d.ts.map +1 -1
  40. package/dist/utils/odata.js +136 -20
  41. package/dist/utils/odata.js.map +1 -1
  42. package/dist/utils/retention.d.ts +59 -0
  43. package/dist/utils/retention.d.ts.map +1 -0
  44. package/dist/utils/retention.js +132 -0
  45. package/dist/utils/retention.js.map +1 -0
  46. package/package.json +1 -1
@@ -19,9 +19,21 @@
19
19
  * `?$filter=...'fm & fd'...` and the server parsed the `&` as a query
20
20
  * param separator. See test/probe-encoding.mts for the verification.
21
21
  *
22
- * Use `escapeODataLiteral` when you control the URL building yourself (rare).
23
- * Use `odataFilterValue` when you're going through the SDK's `.filter()` /
24
- * `.query()` methods, which is what every API class in this package does.
22
+ * **Build filter and search strings with the `odataFilter` / `odataSearch`
23
+ * tagged templates.** The tag applies the right layers to every interpolation,
24
+ * so the safe form is the default form instead of something each call site has
25
+ * to remember:
26
+ *
27
+ * filters.push(odataFilter`from/emailAddress/address eq '${params.from}'`);
28
+ *
29
+ * Remembering to apply `odataFilterValue` by hand is exactly what let five call
30
+ * sites interpolate raw values for months. An untagged template
31
+ * literal that opens an OData string literal, or that reaches `.filter()` /
32
+ * `.search()` / `.query()` directly, is therefore an eslint error inside
33
+ * `src/api`.
34
+ *
35
+ * The value helpers below stay exported: they are what the tags are built from,
36
+ * and consumers of this package building their own queries need them.
25
37
  *
26
38
  * @see https://learn.microsoft.com/en-us/graph/filter-query-parameter
27
39
  */
@@ -29,8 +41,8 @@
29
41
  * Escapes single quotes in an OData string literal.
30
42
  *
31
43
  * Use this only if you are constructing a fully-formed URL yourself and your
32
- * URL builder will handle URL encoding separately. In most cases, prefer
33
- * `odataFilterValue` which combines both layers.
44
+ * URL builder will handle URL encoding separately. In most cases, prefer the
45
+ * `odataFilter` tag, which combines both layers.
34
46
  *
35
47
  * @example
36
48
  * escapeODataLiteral("Let's Talk") // → "Let''s Talk"
@@ -46,29 +58,133 @@ export function escapeODataLiteral(value) {
46
58
  * 1. OData literal escape (`'` → `''`)
47
59
  * 2. URL-encode the result so reserved characters don't break the request URL
48
60
  *
49
- * @example
50
- * // Building a topic filter:
51
- * const value = odataFilterValue("FM & FD");
52
- * client.api('/me/chats').filter(`contains(tolower(topic), '${value}')`).get();
53
- * // The SDK sends: ?$filter=contains(tolower(topic), 'fm%20%26%20fd')
61
+ * Prefer the `odataFilter` tag inside this package; reach for this directly
62
+ * only when you are assembling a value away from the string it lands in.
54
63
  *
55
64
  * @example
56
- * // Filtering by a name with an apostrophe:
57
- * const value = odataFilterValue("O'Brien");
58
- * client.api('/me/chats').filter(`contains(displayName, '${value}')`).get();
59
- * // 'O%27%27Brien' (literal escape gives O''Brien, URL encode gives O%27%27Brien)
65
+ * odataFilterValue('invoice+statements@example.com');
66
+ * // 'invoice%2Bstatements%40example.com'
67
+ * // Without the encoding, the SDK sends a raw `+`, which the server reads as
68
+ * // a space and the filter matches nobody.
60
69
  */
61
70
  export function odataFilterValue(value) {
62
71
  return encodeURIComponent(escapeODataLiteral(value));
63
72
  }
64
73
  /**
65
- * @deprecated Use `escapeODataLiteral` (same behavior) for the literal escape,
66
- * or `odataFilterValue` for the combined literal-escape + URL-encode that you
67
- * almost always want when going through the Graph SDK builders.
74
+ * Prepares a string for safe use as a `$search` value passed through the
75
+ * Microsoft Graph SDK's `.search()` builder.
76
+ *
77
+ * The SDK's `.search()` builder has the exact same non-encoding behavior as
78
+ * `.filter()` (see the module doc above) — it writes the value straight into
79
+ * `$search=...` on the request URL with no URL-encoding. `$search` values are
80
+ * plain quoted phrases, not OData string literals, so there's no literal
81
+ * layer to apply here (unlike `odataFilterValue`) — this is URL-encoding
82
+ * only.
83
+ *
84
+ * **It does not neutralize a literal `"` in the value.** The server percent-
85
+ * decodes `$search` before parsing it as KQL, so an embedded quote is restored
86
+ * and closes the phrase the caller wrapped around it. Verified against Graph:
87
+ * searching `Anthropic" OR from:someone@example.com` comes back as
88
+ * `Syntax error: character ':' is not valid at position 19 in
89
+ * '"Anthropic" OR from:someone@example.com"'` — the server quoting back the
90
+ * decoded string. That fails loud rather than mis-parsing silently, but a
91
+ * caller needing to search for a literal quote cannot express it here. What
92
+ * the encoding does buy is `+`, `&` and `#` surviving the URL.
93
+ *
94
+ * Prefer the `odataSearch` tag inside this package.
95
+ *
96
+ * @example
97
+ * odataSearchValue('from:invoice+statements@example.com');
98
+ * // → 'from%3Ainvoice%2Bstatements%40example.com'
99
+ */
100
+ export function odataSearchValue(value) {
101
+ return encodeURIComponent(value);
102
+ }
103
+ /**
104
+ * An already-built OData fragment, interpolated into a tag verbatim.
105
+ *
106
+ * Produced only by {@link odataRaw}. Carrying a nominal type rather than a bare
107
+ * string is the point: the tags encode anything they are handed, and the only
108
+ * way past that is to say so explicitly at the call site.
109
+ */
110
+ export class ODataRaw {
111
+ fragment;
112
+ constructor(fragment) {
113
+ this.fragment = fragment;
114
+ }
115
+ }
116
+ /**
117
+ * Marks an interpolation as OData *syntax* rather than a *value*, so the tags
118
+ * pass it through unencoded.
119
+ *
120
+ * This is the escape hatch, and it exists for one legitimate shape: composing
121
+ * clauses that the tags already built, where the parentheses, quotes and
122
+ * operators are structure and encoding them would destroy the expression.
123
+ *
124
+ * Never hand it something a caller supplied. If a value reaches `odataRaw`,
125
+ * the encoding is gone and nothing else will catch it.
126
+ *
127
+ * @example
128
+ * const clauses = addresses.map((a) => odataFilter`from/emailAddress/address eq '${a}'`);
129
+ * const anyOf = odataFilter`(${odataRaw(clauses.join(' or '))})`;
130
+ */
131
+ export function odataRaw(fragment) {
132
+ return new ODataRaw(fragment);
133
+ }
134
+ function interpolate(strings, values, encode) {
135
+ let out = strings[0];
136
+ for (let i = 0; i < values.length; i++) {
137
+ const value = values[i];
138
+ out += value instanceof ODataRaw ? value.fragment : encode(String(value));
139
+ out += strings[i + 1];
140
+ }
141
+ return out;
142
+ }
143
+ /**
144
+ * Builds an OData `$filter` expression, escaping and URL-encoding every
145
+ * interpolated value (see {@link odataFilterValue}).
68
146
  *
69
- * Kept temporarily for backwards compatibility with the old name.
147
+ * Write the OData syntax including the quotes around a string literal — in
148
+ * the template; interpolate only values. Numbers, booleans and unquoted
149
+ * datetime literals are safe to interpolate as-is: encoding leaves them intact
150
+ * apart from characters that genuinely have to be encoded, such as the `+` in a
151
+ * timezone offset.
152
+ *
153
+ * @example
154
+ * odataFilter`from/emailAddress/address eq '${'invoice+statements@example.com'}'`;
155
+ * // → "from/emailAddress/address eq 'invoice%2Bstatements%40example.com'"
156
+ */
157
+ export function odataFilter(strings, ...values) {
158
+ return interpolate(strings, values, odataFilterValue);
159
+ }
160
+ /**
161
+ * Builds an OData `$search` expression, URL-encoding every interpolated value
162
+ * (see {@link odataSearchValue}).
163
+ *
164
+ * Write the phrase quotes and any KQL operators in the template; interpolate
165
+ * only values.
166
+ *
167
+ * @example
168
+ * odataSearch`"mail:${'a+b@example.com'}" OR "displayName:${'a+b@example.com'}"`;
169
+ * // → '"mail:a%2Bb%40example.com" OR "displayName:a%2Bb%40example.com"'
170
+ */
171
+ export function odataSearch(strings, ...values) {
172
+ return interpolate(strings, values, odataSearchValue);
173
+ }
174
+ /**
175
+ * Joins already-built clauses into an any-of group, parenthesised only when
176
+ * there is more than one.
177
+ *
178
+ * The parentheses matter: an `or` group spliced into a filter that also has
179
+ * `and` clauses would otherwise bind wrongly and widen the query instead of
180
+ * narrowing it. This exists so that composing clauses — the one legitimate use
181
+ * of {@link odataRaw} — has a name, rather than each new any-of filter reaching
182
+ * for the escape hatch and copying the last call site that did.
183
+ *
184
+ * @example
185
+ * odataAnyOf(addresses.map((a) => odataFilter`from/emailAddress/address eq '${a}'`));
70
186
  */
71
- export function escapeODataString(value) {
72
- return escapeODataLiteral(value);
187
+ export function odataAnyOf(clauses) {
188
+ return clauses.length === 1 ? clauses[0] : odataFilter `(${odataRaw(clauses.join(' or '))})`;
73
189
  }
74
190
  //# sourceMappingURL=odata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"odata.js","sourceRoot":"","sources":["../../src/utils/odata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"odata.js","sourceRoot":"","sources":["../../src/utils/odata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,QAAQ;IACgB;IAAnC,YAAmC,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;CACxD;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAKD,SAAS,WAAW,CAClB,OAA6B,EAC7B,MAA4B,EAC5B,MAAiC;IAEjC,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,GAAG,IAAI,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,OAA6B,EAAE,GAAG,MAA4B;IACxF,OAAO,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,OAA6B,EAAE,GAAG,MAA4B;IACxF,OAAO,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CAAC,OAAiB;IAC1C,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAA,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC;AAC9F,CAAC"}
@@ -0,0 +1,59 @@
1
+ /** MAPI id of PR_POLICY_TAG — the retention tag GUID, stored as bytes. */
2
+ export declare const RETENTION_TAG_PROPERTY = "Binary 0x3019";
3
+ /** MAPI id of PR_RETENTION_PERIOD — days before the tag's action applies. */
4
+ export declare const RETENTION_PERIOD_PROPERTY = "Integer 0x301A";
5
+ /** MAPI id of PR_RETENTION_FLAGS — Exchange bookkeeping; read-only in practice. */
6
+ export declare const RETENTION_FLAGS_PROPERTY = "Integer 0x301D";
7
+ /** `$filter` selecting exactly the three retention properties in an `$expand`. */
8
+ export declare const RETENTION_PROPERTY_FILTER: string;
9
+ /** One entry of a `mailFolder`'s `singleValueExtendedProperties` collection, as Graph returns it. */
10
+ export interface SingleValueExtendedProperty {
11
+ id?: string | null;
12
+ value?: string | null;
13
+ }
14
+ /** One entry as it is *sent* — a `null` value is the delete, so it is not optional. */
15
+ export interface RetentionPropertyPayload {
16
+ id: string;
17
+ value: string | null;
18
+ }
19
+ /** A folder's retention policy, in the terms a person set it in. */
20
+ export interface RetentionPolicy {
21
+ /** Retention tag GUID, in the form Exchange shows it. */
22
+ tag: string;
23
+ /** Days before the tag's action applies, when the folder carries one. */
24
+ periodDays: number | null;
25
+ /** Raw PR_RETENTION_FLAGS. Reported because it is there, not because it means anything. */
26
+ flags: number | null;
27
+ }
28
+ /**
29
+ * Decodes PR_POLICY_TAG's bytes into a GUID string.
30
+ *
31
+ * The bytes are in .NET `Guid` layout — the first three fields little-endian,
32
+ * the last two big-endian — not a flat big-endian read. Reading them flat
33
+ * produces a string that looks like a GUID and is not one: tags read off a real
34
+ * mailbox decode to a valid version-4 GUID under this layout, and to an
35
+ * impossible version nibble when the bytes are read straight through.
36
+ */
37
+ export declare function decodeRetentionTag(base64Value: string): string | null;
38
+ /** Encodes a GUID string back into PR_POLICY_TAG's bytes. Inverse of {@link decodeRetentionTag}. */
39
+ export declare function encodeRetentionTag(guid: string): string;
40
+ /**
41
+ * Reads a folder's retention policy out of its expanded extended properties.
42
+ *
43
+ * Returns `null` when the folder carries no tag. A folder whose policy has been
44
+ * cleared keeps a `PR_RETENTION_FLAGS` of 128, so presence of the properties is
45
+ * not the test — presence of a tag is.
46
+ */
47
+ export declare function parseRetentionPolicy(properties: SingleValueExtendedProperty[] | null | undefined): RetentionPolicy | null;
48
+ /**
49
+ * Builds the `singleValueExtendedProperties` payload for a PATCH.
50
+ *
51
+ * Pass a tag and period to assign a policy, or `null` for both to clear one.
52
+ * `PR_RETENTION_FLAGS` is only ever sent as `null`, on a clear: Exchange owns
53
+ * that value and rewrites whatever is sent.
54
+ */
55
+ export declare function buildRetentionProperties(policy: {
56
+ tag: string | null;
57
+ periodDays: number | null;
58
+ }): RetentionPropertyPayload[];
59
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/utils/retention.ts"],"names":[],"mappings":"AA6BA,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AACtD,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,mBAAmB,CAAC;AAC1D,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAEzD,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,QAUvB,CAAC;AAEhB,qGAAqG;AACrG,MAAM,WAAW,2BAA2B;IAC1C,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,uFAAuF;AACvF,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAID;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE;AAED,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,2BAA2B,EAAE,GAAG,IAAI,GAAG,SAAS,GAC3D,eAAe,GAAG,IAAI,CAsBxB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,wBAAwB,EAAE,CAiB7B"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Mail folder retention policies — Outlook's "Assign Policy".
3
+ *
4
+ * There is no `retentionPolicy` property on `mailFolder`. The policy lives in
5
+ * three MAPI properties reachable through the `singleValueExtendedProperties`
6
+ * navigation property, which is why a scan of the declared `<Property>` list in
7
+ * `$metadata` concludes the feature is missing:
8
+ *
9
+ * PR_POLICY_TAG `Binary 0x3019` the retention tag, a GUID in bytes
10
+ * PR_RETENTION_PERIOD `Integer 0x301A` days before the tag's action applies
11
+ * PR_RETENTION_FLAGS `Integer 0x301D` Exchange's own bookkeeping
12
+ *
13
+ * Three behaviours verified against a live mailbox, each of which shapes the
14
+ * code below:
15
+ *
16
+ * - **Flags do not round-trip.** Write 9, read back 137: Exchange ORs in 128
17
+ * (NeedsRescan) itself. Never assert on them, and never send them back.
18
+ * - **Clearing works only with `value: null`.** An empty string is stored as
19
+ * an empty string, so the property survives and the folder still looks
20
+ * policed. After a clear, `PR_RETENTION_FLAGS` is left behind as 128, so a
21
+ * folder is "policed" when it has a tag, not when it has any of the three.
22
+ * - **An invented tag GUID is accepted silently.** The PATCH succeeds, the
23
+ * value reads back perfectly, and the Managed Folder Assistant then ignores
24
+ * it forever. Every observable signal says a dead policy is live, which is
25
+ * why callers must validate a tag against the ones a mailbox actually uses
26
+ * rather than trusting a successful write.
27
+ */
28
+ import { odataFilter, odataRaw } from './odata.js';
29
+ /** MAPI id of PR_POLICY_TAG — the retention tag GUID, stored as bytes. */
30
+ export const RETENTION_TAG_PROPERTY = 'Binary 0x3019';
31
+ /** MAPI id of PR_RETENTION_PERIOD — days before the tag's action applies. */
32
+ export const RETENTION_PERIOD_PROPERTY = 'Integer 0x301A';
33
+ /** MAPI id of PR_RETENTION_FLAGS — Exchange bookkeeping; read-only in practice. */
34
+ export const RETENTION_FLAGS_PROPERTY = 'Integer 0x301D';
35
+ /** `$filter` selecting exactly the three retention properties in an `$expand`. */
36
+ export const RETENTION_PROPERTY_FILTER = [RETENTION_TAG_PROPERTY, RETENTION_PERIOD_PROPERTY, RETENTION_FLAGS_PROPERTY]
37
+ // odataRaw because these are our own constants, not caller values: encoding
38
+ // them would send `Binary%200x3019`, which matches no property. Going through
39
+ // the tag anyway keeps the last hand-built OData string out of the package
40
+ // that exists to stop hand-built OData strings.
41
+ //
42
+ // Joined rather than composed with odataAnyOf: this sits inside
43
+ // `$expand=singleValueExtendedProperties($filter=…)`, where the parentheses
44
+ // odataAnyOf adds would be a change to a request that already works.
45
+ .map((id) => odataFilter `id eq '${odataRaw(id)}'`)
46
+ .join(' or ');
47
+ const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
48
+ /**
49
+ * Decodes PR_POLICY_TAG's bytes into a GUID string.
50
+ *
51
+ * The bytes are in .NET `Guid` layout — the first three fields little-endian,
52
+ * the last two big-endian — not a flat big-endian read. Reading them flat
53
+ * produces a string that looks like a GUID and is not one: tags read off a real
54
+ * mailbox decode to a valid version-4 GUID under this layout, and to an
55
+ * impossible version nibble when the bytes are read straight through.
56
+ */
57
+ export function decodeRetentionTag(base64Value) {
58
+ const bytes = Buffer.from(base64Value, 'base64');
59
+ if (bytes.length !== 16)
60
+ return null;
61
+ // `bytes` is freshly allocated per call, so reversing sub-ranges in place is
62
+ // contained, and each range is reversed exactly once.
63
+ const hex = (start, end, littleEndian) => (littleEndian ? bytes.subarray(start, end).reverse() : bytes.subarray(start, end)).toString('hex');
64
+ return [hex(0, 4, true), hex(4, 6, true), hex(6, 8, true), hex(8, 10, false), hex(10, 16, false)].join('-');
65
+ }
66
+ /** Encodes a GUID string back into PR_POLICY_TAG's bytes. Inverse of {@link decodeRetentionTag}. */
67
+ export function encodeRetentionTag(guid) {
68
+ if (!GUID_PATTERN.test(guid)) {
69
+ throw new Error(`Not a GUID: "${guid}". A retention tag is a GUID, e.g. 1b2c3d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d.`);
70
+ }
71
+ const bytes = Buffer.from(guid.replace(/-/g, ''), 'hex');
72
+ // The first three fields are little-endian; the last two are already in order.
73
+ bytes.subarray(0, 4).reverse();
74
+ bytes.subarray(4, 6).reverse();
75
+ bytes.subarray(6, 8).reverse();
76
+ return bytes.toString('base64');
77
+ }
78
+ /**
79
+ * Reads a folder's retention policy out of its expanded extended properties.
80
+ *
81
+ * Returns `null` when the folder carries no tag. A folder whose policy has been
82
+ * cleared keeps a `PR_RETENTION_FLAGS` of 128, so presence of the properties is
83
+ * not the test — presence of a tag is.
84
+ */
85
+ export function parseRetentionPolicy(properties) {
86
+ if (!properties?.length)
87
+ return null;
88
+ // Graph lowercases the hex in the ids it returns (`Integer 0x301a`), so match
89
+ // case-insensitively rather than against the constants verbatim.
90
+ const find = (id) => {
91
+ const wanted = id.toLowerCase();
92
+ return properties.find((p) => p.id?.toLowerCase() === wanted)?.value ?? null;
93
+ };
94
+ const rawTag = find(RETENTION_TAG_PROPERTY);
95
+ if (!rawTag)
96
+ return null;
97
+ const tag = decodeRetentionTag(rawTag);
98
+ if (!tag)
99
+ return null;
100
+ const toInt = (value) => {
101
+ if (value === null || value === '')
102
+ return null;
103
+ const n = Number.parseInt(value, 10);
104
+ return Number.isNaN(n) ? null : n;
105
+ };
106
+ return { tag, periodDays: toInt(find(RETENTION_PERIOD_PROPERTY)), flags: toInt(find(RETENTION_FLAGS_PROPERTY)) };
107
+ }
108
+ /**
109
+ * Builds the `singleValueExtendedProperties` payload for a PATCH.
110
+ *
111
+ * Pass a tag and period to assign a policy, or `null` for both to clear one.
112
+ * `PR_RETENTION_FLAGS` is only ever sent as `null`, on a clear: Exchange owns
113
+ * that value and rewrites whatever is sent.
114
+ */
115
+ export function buildRetentionProperties(policy) {
116
+ if (policy.tag === null) {
117
+ return [
118
+ { id: RETENTION_TAG_PROPERTY, value: null },
119
+ { id: RETENTION_PERIOD_PROPERTY, value: null },
120
+ { id: RETENTION_FLAGS_PROPERTY, value: null },
121
+ ];
122
+ }
123
+ return [
124
+ { id: RETENTION_TAG_PROPERTY, value: encodeRetentionTag(policy.tag) },
125
+ // Always sent, `null` included. Omitting it leaves whatever period the
126
+ // previous policy wrote, so assigning a "delete after 7 days" tag to a
127
+ // folder that carried a 30-day one would produce a folder reporting the new
128
+ // tag with the old period — a mismatch every read reports as intentional.
129
+ { id: RETENTION_PERIOD_PROPERTY, value: policy.periodDays === null ? null : String(policy.periodDays) },
130
+ ];
131
+ }
132
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/utils/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAC;AACtD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;AAC1D,mFAAmF;AACnF,MAAM,CAAC,MAAM,wBAAwB,GAAG,gBAAgB,CAAC;AAEzD,kFAAkF;AAClF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,sBAAsB,EAAE,yBAAyB,EAAE,wBAAwB,CAAC;IACpH,4EAA4E;IAC5E,8EAA8E;IAC9E,2EAA2E;IAC3E,gDAAgD;IAChD,EAAE;IACF,gEAAgE;IAChE,4EAA4E;IAC5E,qEAAqE;KACpE,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAA,UAAU,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC;KACjD,IAAI,CAAC,MAAM,CAAC,CAAC;AAwBhB,MAAM,YAAY,GAAG,iEAAiE,CAAC;AAEvF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACrC,6EAA6E;IAC7E,sDAAsD;IACtD,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,GAAW,EAAE,YAAqB,EAAU,EAAE,CACxE,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC9G,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,0EAA0E,CAAC,CAAC;IAClH,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACzD,+EAA+E;IAC/E,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/B,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/B,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/B,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAA4D;IAE5D,IAAI,CAAC,UAAU,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAErC,8EAA8E;IAC9E,iEAAiE;IACjE,MAAM,IAAI,GAAG,CAAC,EAAU,EAAiB,EAAE;QACzC,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;QAChC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,MAAM,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC;IAC/E,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,GAAG,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,KAAK,GAAG,CAAC,KAAoB,EAAiB,EAAE;QACpD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QAChD,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC;IAEF,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC;AACnH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAGxC;IACC,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO;YACL,EAAE,EAAE,EAAE,sBAAsB,EAAE,KAAK,EAAE,IAAI,EAAE;YAC3C,EAAE,EAAE,EAAE,yBAAyB,EAAE,KAAK,EAAE,IAAI,EAAE;YAC9C,EAAE,EAAE,EAAE,wBAAwB,EAAE,KAAK,EAAE,IAAI,EAAE;SAC9C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;QACrE,uEAAuE;QACvE,uEAAuE;QACvE,4EAA4E;QAC5E,0EAA0E;QAC1E,EAAE,EAAE,EAAE,yBAAyB,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE;KACxG,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "microsoft-graph-client",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "publish": true,
5
5
  "description": "TypeScript client library for Microsoft Graph API with built-in authentication",
6
6
  "type": "module",