@jbctechsolutions/mcp-office365 2.5.1 → 4.0.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.
- package/README.md +66 -57
- package/dist/approval/hash.d.ts +8 -8
- package/dist/approval/token-manager.d.ts +37 -19
- package/dist/approval/token-manager.d.ts.map +1 -1
- package/dist/approval/token-manager.js +133 -37
- package/dist/approval/token-manager.js.map +1 -1
- package/dist/approval/types.d.ts +7 -3
- package/dist/approval/types.d.ts.map +1 -1
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +100 -2
- package/dist/cli.js.map +1 -1
- package/dist/database/repository.d.ts +48 -48
- package/dist/database/repository.d.ts.map +1 -1
- package/dist/database/repository.js.map +1 -1
- package/dist/delta/mirror.d.ts +67 -0
- package/dist/delta/mirror.d.ts.map +1 -0
- package/dist/delta/mirror.js +183 -0
- package/dist/delta/mirror.js.map +1 -0
- package/dist/graph/auth/account-id.d.ts +19 -0
- package/dist/graph/auth/account-id.d.ts.map +1 -0
- package/dist/graph/auth/account-id.js +55 -0
- package/dist/graph/auth/account-id.js.map +1 -0
- package/dist/graph/auth/config.d.ts +1 -1
- package/dist/graph/auth/config.d.ts.map +1 -1
- package/dist/graph/auth/config.js +13 -1
- package/dist/graph/auth/config.js.map +1 -1
- package/dist/graph/auth/device-code-flow.d.ts +3 -1
- package/dist/graph/auth/device-code-flow.d.ts.map +1 -1
- package/dist/graph/auth/device-code-flow.js +42 -14
- package/dist/graph/auth/device-code-flow.js.map +1 -1
- package/dist/graph/auth/index.d.ts +1 -0
- package/dist/graph/auth/index.d.ts.map +1 -1
- package/dist/graph/auth/index.js +1 -0
- package/dist/graph/auth/index.js.map +1 -1
- package/dist/graph/auth/token-cache.js +2 -2
- package/dist/graph/auth/token-cache.js.map +1 -1
- package/dist/graph/client/graph-client.d.ts +141 -8
- package/dist/graph/client/graph-client.d.ts.map +1 -1
- package/dist/graph/client/graph-client.js +536 -48
- package/dist/graph/client/graph-client.js.map +1 -1
- package/dist/graph/client/immutable-id-middleware.d.ts +50 -0
- package/dist/graph/client/immutable-id-middleware.d.ts.map +1 -0
- package/dist/graph/client/immutable-id-middleware.js +137 -0
- package/dist/graph/client/immutable-id-middleware.js.map +1 -0
- package/dist/graph/content-readers.d.ts +0 -12
- package/dist/graph/content-readers.d.ts.map +1 -1
- package/dist/graph/content-readers.js +0 -21
- package/dist/graph/content-readers.js.map +1 -1
- package/dist/graph/index.d.ts +5 -6
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/index.js +5 -6
- package/dist/graph/index.js.map +1 -1
- package/dist/graph/mailbox-adapter.d.ts +15 -15
- package/dist/graph/mappers/contact-mapper.d.ts +4 -1
- package/dist/graph/mappers/contact-mapper.d.ts.map +1 -1
- package/dist/graph/mappers/contact-mapper.js +7 -3
- package/dist/graph/mappers/contact-mapper.js.map +1 -1
- package/dist/graph/mappers/email-mapper.d.ts.map +1 -1
- package/dist/graph/mappers/email-mapper.js +7 -4
- package/dist/graph/mappers/email-mapper.js.map +1 -1
- package/dist/graph/mappers/event-mapper.d.ts.map +1 -1
- package/dist/graph/mappers/event-mapper.js +6 -3
- package/dist/graph/mappers/event-mapper.js.map +1 -1
- package/dist/graph/mappers/folder-mapper.d.ts +0 -4
- package/dist/graph/mappers/folder-mapper.d.ts.map +1 -1
- package/dist/graph/mappers/folder-mapper.js +9 -19
- package/dist/graph/mappers/folder-mapper.js.map +1 -1
- package/dist/graph/mappers/index.d.ts +2 -2
- package/dist/graph/mappers/index.d.ts.map +1 -1
- package/dist/graph/mappers/index.js +2 -2
- package/dist/graph/mappers/index.js.map +1 -1
- package/dist/graph/mappers/task-mapper.d.ts +4 -2
- package/dist/graph/mappers/task-mapper.d.ts.map +1 -1
- package/dist/graph/mappers/task-mapper.js +7 -5
- package/dist/graph/mappers/task-mapper.js.map +1 -1
- package/dist/graph/mappers/utils.d.ts +0 -10
- package/dist/graph/mappers/utils.d.ts.map +1 -1
- package/dist/graph/mappers/utils.js +0 -19
- package/dist/graph/mappers/utils.js.map +1 -1
- package/dist/graph/repository.d.ts +409 -276
- package/dist/graph/repository.d.ts.map +1 -1
- package/dist/graph/repository.js +772 -1082
- package/dist/graph/repository.js.map +1 -1
- package/dist/ids/mint.d.ts +31 -0
- package/dist/ids/mint.d.ts.map +1 -0
- package/dist/ids/mint.js +36 -0
- package/dist/ids/mint.js.map +1 -0
- package/dist/ids/next-action.d.ts +29 -0
- package/dist/ids/next-action.d.ts.map +1 -0
- package/dist/ids/next-action.js +58 -0
- package/dist/ids/next-action.js.map +1 -0
- package/dist/ids/resolver.d.ts +31 -0
- package/dist/ids/resolver.d.ts.map +1 -0
- package/dist/ids/resolver.js +52 -0
- package/dist/ids/resolver.js.map +1 -0
- package/dist/ids/schema.d.ts +91 -0
- package/dist/ids/schema.d.ts.map +1 -0
- package/dist/ids/schema.js +131 -0
- package/dist/ids/schema.js.map +1 -0
- package/dist/ids/token.d.ts +62 -0
- package/dist/ids/token.d.ts.map +1 -0
- package/dist/ids/token.js +225 -0
- package/dist/ids/token.js.map +1 -0
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -5925
- package/dist/index.js.map +1 -1
- package/dist/parsers/index.d.ts +0 -1
- package/dist/parsers/index.d.ts.map +1 -1
- package/dist/parsers/index.js +0 -1
- package/dist/parsers/index.js.map +1 -1
- package/dist/registry/all-tools.d.ts +14 -0
- package/dist/registry/all-tools.d.ts.map +1 -0
- package/dist/registry/all-tools.js +68 -0
- package/dist/registry/all-tools.js.map +1 -0
- package/dist/registry/context.d.ts +19 -0
- package/dist/registry/context.d.ts.map +1 -0
- package/dist/registry/context.js +20 -0
- package/dist/registry/context.js.map +1 -0
- package/dist/registry/define-tool.d.ts +15 -0
- package/dist/registry/define-tool.d.ts.map +1 -0
- package/dist/registry/define-tool.js +8 -0
- package/dist/registry/define-tool.js.map +1 -0
- package/dist/registry/elicit-links.d.ts +35 -0
- package/dist/registry/elicit-links.d.ts.map +1 -0
- package/dist/registry/elicit-links.js +102 -0
- package/dist/registry/elicit-links.js.map +1 -0
- package/dist/registry/elicitor.d.ts +24 -0
- package/dist/registry/elicitor.d.ts.map +1 -0
- package/dist/registry/elicitor.js +51 -0
- package/dist/registry/elicitor.js.map +1 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/registry.d.ts +73 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +221 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/registry/types.d.ts +146 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +6 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/search/compiler.d.ts +49 -0
- package/dist/search/compiler.d.ts.map +1 -0
- package/dist/search/compiler.js +168 -0
- package/dist/search/compiler.js.map +1 -0
- package/dist/signature.js +2 -2
- package/dist/signature.js.map +1 -1
- package/dist/state/delta-store.d.ts +85 -0
- package/dist/state/delta-store.d.ts.map +1 -0
- package/dist/state/delta-store.js +124 -0
- package/dist/state/delta-store.js.map +1 -0
- package/dist/state/migrate.d.ts +23 -0
- package/dist/state/migrate.d.ts.map +1 -0
- package/dist/state/migrate.js +81 -0
- package/dist/state/migrate.js.map +1 -0
- package/dist/state/schema.d.ts +26 -0
- package/dist/state/schema.d.ts.map +1 -0
- package/dist/state/schema.js +77 -0
- package/dist/state/schema.js.map +1 -0
- package/dist/state/store.d.ts +155 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +321 -0
- package/dist/state/store.js.map +1 -0
- package/dist/tools/calendar-graph.d.ts +41 -0
- package/dist/tools/calendar-graph.d.ts.map +1 -0
- package/dist/tools/calendar-graph.js +299 -0
- package/dist/tools/calendar-graph.js.map +1 -0
- package/dist/tools/calendar-permissions.d.ts +17 -7
- package/dist/tools/calendar-permissions.d.ts.map +1 -1
- package/dist/tools/calendar-permissions.js +60 -4
- package/dist/tools/calendar-permissions.js.map +1 -1
- package/dist/tools/calendar.d.ts +133 -82
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +249 -136
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/categories.d.ts +14 -4
- package/dist/tools/categories.d.ts.map +1 -1
- package/dist/tools/categories.js +59 -2
- package/dist/tools/categories.js.map +1 -1
- package/dist/tools/checklist-items.d.ts +19 -9
- package/dist/tools/checklist-items.d.ts.map +1 -1
- package/dist/tools/checklist-items.js +71 -5
- package/dist/tools/checklist-items.js.map +1 -1
- package/dist/tools/contact-folders.d.ts +50 -0
- package/dist/tools/contact-folders.d.ts.map +1 -0
- package/dist/tools/contact-folders.js +136 -0
- package/dist/tools/contact-folders.js.map +1 -0
- package/dist/tools/contacts-graph.d.ts +45 -0
- package/dist/tools/contacts-graph.d.ts.map +1 -0
- package/dist/tools/contacts-graph.js +174 -0
- package/dist/tools/contacts-graph.js.map +1 -0
- package/dist/tools/contacts.d.ts +61 -30
- package/dist/tools/contacts.d.ts.map +1 -1
- package/dist/tools/contacts.js +150 -93
- package/dist/tools/contacts.js.map +1 -1
- package/dist/tools/excel.d.ts +20 -10
- package/dist/tools/excel.d.ts.map +1 -1
- package/dist/tools/excel.js +82 -6
- package/dist/tools/excel.js.map +1 -1
- package/dist/tools/focused-overrides.d.ts +14 -4
- package/dist/tools/focused-overrides.d.ts.map +1 -1
- package/dist/tools/focused-overrides.js +59 -2
- package/dist/tools/focused-overrides.js.map +1 -1
- package/dist/tools/index.d.ts +4 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -6
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/linked-resources.d.ts +17 -7
- package/dist/tools/linked-resources.d.ts.map +1 -1
- package/dist/tools/linked-resources.js +60 -4
- package/dist/tools/linked-resources.js.map +1 -1
- package/dist/tools/mail-graph.d.ts +38 -0
- package/dist/tools/mail-graph.d.ts.map +1 -0
- package/dist/tools/mail-graph.js +187 -0
- package/dist/tools/mail-graph.js.map +1 -0
- package/dist/tools/mail-rules.d.ts +20 -8
- package/dist/tools/mail-rules.d.ts.map +1 -1
- package/dist/tools/mail-rules.js +63 -7
- package/dist/tools/mail-rules.js.map +1 -1
- package/dist/tools/mail-send.d.ts +39 -27
- package/dist/tools/mail-send.d.ts.map +1 -1
- package/dist/tools/mail-send.js +249 -24
- package/dist/tools/mail-send.js.map +1 -1
- package/dist/tools/mail.d.ts +91 -68
- package/dist/tools/mail.d.ts.map +1 -1
- package/dist/tools/mail.js +232 -243
- package/dist/tools/mail.js.map +1 -1
- package/dist/tools/mailbox-organization.d.ts +51 -34
- package/dist/tools/mailbox-organization.d.ts.map +1 -1
- package/dist/tools/mailbox-organization.js +318 -35
- package/dist/tools/mailbox-organization.js.map +1 -1
- package/dist/tools/mailbox-settings.d.ts +59 -0
- package/dist/tools/mailbox-settings.d.ts.map +1 -0
- package/dist/tools/mailbox-settings.js +136 -0
- package/dist/tools/mailbox-settings.js.map +1 -0
- package/dist/tools/meetings.d.ts +24 -14
- package/dist/tools/meetings.d.ts.map +1 -1
- package/dist/tools/meetings.js +82 -6
- package/dist/tools/meetings.js.map +1 -1
- package/dist/tools/onedrive.d.ts +26 -16
- package/dist/tools/onedrive.d.ts.map +1 -1
- package/dist/tools/onedrive.js +140 -11
- package/dist/tools/onedrive.js.map +1 -1
- package/dist/tools/onenote.d.ts +63 -0
- package/dist/tools/onenote.d.ts.map +1 -0
- package/dist/tools/onenote.js +221 -0
- package/dist/tools/onenote.js.map +1 -0
- package/dist/tools/people.d.ts +10 -0
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +94 -0
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/planner-visualization.d.ts +15 -5
- package/dist/tools/planner-visualization.d.ts.map +1 -1
- package/dist/tools/planner-visualization.js +58 -4
- package/dist/tools/planner-visualization.js.map +1 -1
- package/dist/tools/planner.d.ts +69 -39
- package/dist/tools/planner.d.ts.map +1 -1
- package/dist/tools/planner.js +231 -23
- package/dist/tools/planner.js.map +1 -1
- package/dist/tools/scheduling.d.ts +12 -0
- package/dist/tools/scheduling.d.ts.map +1 -1
- package/dist/tools/scheduling.js +38 -0
- package/dist/tools/scheduling.js.map +1 -1
- package/dist/tools/shared-mailbox.d.ts +164 -0
- package/dist/tools/shared-mailbox.d.ts.map +1 -0
- package/dist/tools/shared-mailbox.js +318 -0
- package/dist/tools/shared-mailbox.js.map +1 -0
- package/dist/tools/sharepoint-lists.d.ts +131 -0
- package/dist/tools/sharepoint-lists.d.ts.map +1 -0
- package/dist/tools/sharepoint-lists.js +246 -0
- package/dist/tools/sharepoint-lists.js.map +1 -0
- package/dist/tools/sharepoint.d.ts +24 -14
- package/dist/tools/sharepoint.d.ts.map +1 -1
- package/dist/tools/sharepoint.js +85 -9
- package/dist/tools/sharepoint.js.map +1 -1
- package/dist/tools/task-attachments.d.ts +17 -7
- package/dist/tools/task-attachments.d.ts.map +1 -1
- package/dist/tools/task-attachments.js +60 -4
- package/dist/tools/task-attachments.js.map +1 -1
- package/dist/tools/task-lists.d.ts +56 -0
- package/dist/tools/task-lists.d.ts.map +1 -0
- package/dist/tools/task-lists.js +155 -0
- package/dist/tools/task-lists.js.map +1 -0
- package/dist/tools/tasks-graph.d.ts +49 -0
- package/dist/tools/tasks-graph.d.ts.map +1 -0
- package/dist/tools/tasks-graph.js +193 -0
- package/dist/tools/tasks-graph.js.map +1 -0
- package/dist/tools/tasks.d.ts +121 -29
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +136 -84
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/teams.d.ts +57 -46
- package/dist/tools/teams.d.ts.map +1 -1
- package/dist/tools/teams.js +288 -26
- package/dist/tools/teams.js.map +1 -1
- package/dist/tools/what-changed.d.ts +48 -0
- package/dist/tools/what-changed.d.ts.map +1 -0
- package/dist/tools/what-changed.js +149 -0
- package/dist/tools/what-changed.js.map +1 -0
- package/dist/types/calendar.d.ts +1 -1
- package/dist/types/calendar.d.ts.map +1 -1
- package/dist/types/calendar.js.map +1 -1
- package/dist/types/contacts.d.ts +1 -1
- package/dist/types/contacts.d.ts.map +1 -1
- package/dist/types/contacts.js.map +1 -1
- package/dist/types/mail.d.ts +1 -1
- package/dist/types/mail.d.ts.map +1 -1
- package/dist/types/mail.js.map +1 -1
- package/dist/utils/errors.d.ts +110 -38
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +239 -56
- package/dist/utils/errors.js.map +1 -1
- package/dist/visualization/types.d.ts +4 -4
- package/package.json +2 -3
- package/dist/applescript/account-repository.d.ts +0 -30
- package/dist/applescript/account-repository.d.ts.map +0 -1
- package/dist/applescript/account-repository.js +0 -38
- package/dist/applescript/account-repository.js.map +0 -1
- package/dist/applescript/account-scripts.d.ts +0 -21
- package/dist/applescript/account-scripts.d.ts.map +0 -1
- package/dist/applescript/account-scripts.js +0 -180
- package/dist/applescript/account-scripts.js.map +0 -1
- package/dist/applescript/calendar-manager.d.ts +0 -44
- package/dist/applescript/calendar-manager.d.ts.map +0 -1
- package/dist/applescript/calendar-manager.js +0 -92
- package/dist/applescript/calendar-manager.js.map +0 -1
- package/dist/applescript/calendar-writer.d.ts +0 -36
- package/dist/applescript/calendar-writer.d.ts.map +0 -1
- package/dist/applescript/calendar-writer.js +0 -94
- package/dist/applescript/calendar-writer.js.map +0 -1
- package/dist/applescript/content-readers.d.ts +0 -114
- package/dist/applescript/content-readers.d.ts.map +0 -1
- package/dist/applescript/content-readers.js +0 -328
- package/dist/applescript/content-readers.js.map +0 -1
- package/dist/applescript/executor.d.ts +0 -60
- package/dist/applescript/executor.d.ts.map +0 -1
- package/dist/applescript/executor.js +0 -173
- package/dist/applescript/executor.js.map +0 -1
- package/dist/applescript/index.d.ts +0 -20
- package/dist/applescript/index.d.ts.map +0 -1
- package/dist/applescript/index.js +0 -29
- package/dist/applescript/index.js.map +0 -1
- package/dist/applescript/mail-sender.d.ts +0 -38
- package/dist/applescript/mail-sender.d.ts.map +0 -1
- package/dist/applescript/mail-sender.js +0 -67
- package/dist/applescript/mail-sender.js.map +0 -1
- package/dist/applescript/parser.d.ts +0 -235
- package/dist/applescript/parser.d.ts.map +0 -1
- package/dist/applescript/parser.js +0 -496
- package/dist/applescript/parser.js.map +0 -1
- package/dist/applescript/repository.d.ts +0 -64
- package/dist/applescript/repository.d.ts.map +0 -1
- package/dist/applescript/repository.js +0 -444
- package/dist/applescript/repository.js.map +0 -1
- package/dist/applescript/scripts.d.ts +0 -265
- package/dist/applescript/scripts.d.ts.map +0 -1
- package/dist/applescript/scripts.js +0 -1483
- package/dist/applescript/scripts.js.map +0 -1
- package/dist/parsers/olk15.d.ts +0 -87
- package/dist/parsers/olk15.d.ts.map +0 -1
- package/dist/parsers/olk15.js +0 -368
- package/dist/parsers/olk15.js.map +0 -1
- package/dist/tools/notes.d.ts +0 -74
- package/dist/tools/notes.d.ts.map +0 -1
- package/dist/tools/notes.js +0 -136
- package/dist/tools/notes.js.map +0 -1
package/dist/graph/repository.js
CHANGED
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
* Licensed under the MIT License. See LICENSE file in the project root.
|
|
4
4
|
*/
|
|
5
5
|
import { GraphClient } from './client/index.js';
|
|
6
|
-
import { mapMailFolderToRow, mapCalendarToFolderRow, mapMessageToEmailRow, mapEventToEventRow, mapContactToContactRow, mapTaskToTaskRow,
|
|
6
|
+
import { mapMailFolderToRow, mapCalendarToFolderRow, mapMessageToEmailRow, mapEventToEventRow, mapContactToContactRow, mapTaskToTaskRow, } from './mappers/index.js';
|
|
7
|
+
import { currentAccountId } from './auth/index.js';
|
|
8
|
+
import { resolveId } from '../ids/resolver.js';
|
|
9
|
+
import { registerComposite } from '../ids/mint.js';
|
|
10
|
+
import { mintSelfEncoded } from '../ids/token.js';
|
|
11
|
+
import { IdUnknownError, isGraphSdkError } from '../utils/errors.js';
|
|
7
12
|
import { downloadAttachment, getDownloadDir } from './attachments.js';
|
|
8
13
|
import * as fs from 'fs';
|
|
9
14
|
import * as path from 'path';
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
10
16
|
/**
|
|
11
17
|
* Repository implementation using Microsoft Graph API.
|
|
12
18
|
*
|
|
@@ -14,45 +20,179 @@ import * as path from 'path';
|
|
|
14
20
|
*/
|
|
15
21
|
export class GraphRepository {
|
|
16
22
|
client;
|
|
23
|
+
store;
|
|
24
|
+
accountId;
|
|
17
25
|
deltaLinks = new Map();
|
|
18
|
-
|
|
19
|
-
folders: new Map(),
|
|
20
|
-
messages: new Map(),
|
|
21
|
-
conversations: new Map(),
|
|
22
|
-
events: new Map(),
|
|
23
|
-
contacts: new Map(),
|
|
24
|
-
tasks: new Map(),
|
|
25
|
-
taskLists: new Map(),
|
|
26
|
-
attachments: new Map(),
|
|
27
|
-
rules: new Map(),
|
|
28
|
-
contactFolders: new Map(),
|
|
29
|
-
categories: new Map(),
|
|
30
|
-
focusedOverrides: new Map(),
|
|
31
|
-
calendarGroups: new Map(),
|
|
32
|
-
calendars: new Map(),
|
|
33
|
-
calendarPermissions: new Map(),
|
|
34
|
-
teams: new Map(),
|
|
35
|
-
channels: new Map(),
|
|
36
|
-
channelMessages: new Map(),
|
|
37
|
-
chats: new Map(),
|
|
38
|
-
chatMessages: new Map(),
|
|
39
|
-
checklistItems: new Map(),
|
|
40
|
-
linkedResources: new Map(),
|
|
41
|
-
taskAttachments: new Map(),
|
|
42
|
-
plans: new Map(),
|
|
43
|
-
plannerBuckets: new Map(),
|
|
44
|
-
plannerTasks: new Map(),
|
|
45
|
-
plannerTaskDetails: new Map(),
|
|
46
|
-
onlineMeetings: new Map(),
|
|
47
|
-
recordings: new Map(),
|
|
48
|
-
transcripts: new Map(),
|
|
49
|
-
driveItems: new Map(),
|
|
50
|
-
sites: new Map(),
|
|
51
|
-
documentLibraries: new Map(),
|
|
52
|
-
libraryDriveItems: new Map(),
|
|
53
|
-
};
|
|
54
|
-
constructor(deviceCodeCallback) {
|
|
26
|
+
constructor(deviceCodeCallback, store, accountId = currentAccountId) {
|
|
55
27
|
this.client = new GraphClient(deviceCodeCallback);
|
|
28
|
+
this.store = store;
|
|
29
|
+
this.accountId = accountId;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolves any id a tool receives — a durable token, a raw Graph id, or a
|
|
33
|
+
* legacy numeric id — to a live Graph id (U5). Self-encoding tokens decode with
|
|
34
|
+
* no store; alias-backed tokens need {@link store}; a numeric id on Graph is
|
|
35
|
+
* unsupported (D4). Throws a typed error on failure.
|
|
36
|
+
*/
|
|
37
|
+
toGraphId(id, expectedEntityType) {
|
|
38
|
+
return resolveId(id, this.accountId(), this.store, expectedEntityType).graphId;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mints an alias-backed token for a single-Graph-id entity (e.g. team, chat)
|
|
42
|
+
* and records token → graphId in the alias table so it resolves on later calls.
|
|
43
|
+
* Alias-backed (not self-encoding) so the token is short and account-scoped: a
|
|
44
|
+
* cold store yields ID_UNKNOWN rather than leaking a decodable tenant-global id.
|
|
45
|
+
*/
|
|
46
|
+
mintAlias(entityType, graphId) {
|
|
47
|
+
if (this.store == null) {
|
|
48
|
+
// Production always has a store (index.ts) and degraded mode still yields a
|
|
49
|
+
// non-null in-memory one, so this is only reachable from a store-less
|
|
50
|
+
// embedding. Fail loudly rather than mint a token nothing can resolve.
|
|
51
|
+
throw new IdUnknownError(`${entityType} (durable state store unavailable)`, 'Alias-backed ids require the durable state store; construct the repository with one.');
|
|
52
|
+
}
|
|
53
|
+
return registerComposite(this.store, {
|
|
54
|
+
entityType,
|
|
55
|
+
parts: { id: graphId },
|
|
56
|
+
graphId,
|
|
57
|
+
accountId: this.accountId(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Mints an alias-backed token for a composite entity whose Graph URL needs a
|
|
62
|
+
* tuple of ids (e.g. channel {teamId, channelId}). The tuple is the canonical
|
|
63
|
+
* key AND is stored JSON-encoded as the resolved value, so {@link toGraphParts}
|
|
64
|
+
* can recover every field.
|
|
65
|
+
*/
|
|
66
|
+
mintAliasComposite(entityType, parts) {
|
|
67
|
+
if (this.store == null) {
|
|
68
|
+
throw new IdUnknownError(`<no store: cannot mint ${entityType}>`);
|
|
69
|
+
}
|
|
70
|
+
return registerComposite(this.store, {
|
|
71
|
+
entityType,
|
|
72
|
+
parts,
|
|
73
|
+
graphId: JSON.stringify(parts),
|
|
74
|
+
accountId: this.accountId(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolves a composite token to its identifying tuple. The alias row stores the
|
|
79
|
+
* tuple JSON-encoded; a raw (non-token) string can't carry a tuple, so anything
|
|
80
|
+
* that doesn't decode to a JSON object carrying every required key is an unusable
|
|
81
|
+
* id (ID_UNKNOWN). Generic over the key set so callers get a precisely-typed
|
|
82
|
+
* result — every requested field is a guaranteed non-empty string.
|
|
83
|
+
*/
|
|
84
|
+
toGraphParts(id, entityType, keys) {
|
|
85
|
+
const raw = this.toGraphId(id, entityType);
|
|
86
|
+
let parsed;
|
|
87
|
+
try {
|
|
88
|
+
parsed = JSON.parse(raw);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new IdUnknownError(String(id));
|
|
92
|
+
}
|
|
93
|
+
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
94
|
+
throw new IdUnknownError(String(id));
|
|
95
|
+
}
|
|
96
|
+
const obj = parsed;
|
|
97
|
+
const out = {};
|
|
98
|
+
for (const key of keys) {
|
|
99
|
+
const value = obj[key];
|
|
100
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
101
|
+
throw new IdUnknownError(String(id));
|
|
102
|
+
}
|
|
103
|
+
out[key] = value;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
// ===========================================================================
|
|
108
|
+
// Cache Resolvers (auto-fetch parent on cache miss)
|
|
109
|
+
// ===========================================================================
|
|
110
|
+
async resolveTeamId(teamId) {
|
|
111
|
+
// tm_ tokens resolve from the alias store. On a cold miss (never listed this
|
|
112
|
+
// session, or a lost store) re-list — listTeamsAsync deterministically
|
|
113
|
+
// re-mints and re-stores the same token — then resolve again.
|
|
114
|
+
try {
|
|
115
|
+
return this.toGraphId(teamId, 'team');
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
if (e instanceof IdUnknownError) {
|
|
119
|
+
await this.listTeamsAsync();
|
|
120
|
+
return this.toGraphId(teamId, 'team');
|
|
121
|
+
}
|
|
122
|
+
throw e;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async resolvePlanId(planId) {
|
|
126
|
+
// pl_ tokens resolve from the alias store. On a cold miss (never listed this
|
|
127
|
+
// session, or a lost store) re-list — listPlansAsync deterministically
|
|
128
|
+
// re-mints and re-stores the same token — then resolve again.
|
|
129
|
+
try {
|
|
130
|
+
return this.toGraphId(planId, 'plan');
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
if (e instanceof IdUnknownError) {
|
|
134
|
+
await this.listPlansAsync();
|
|
135
|
+
return this.toGraphId(planId, 'plan');
|
|
136
|
+
}
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** True for an HTTP 412 (Precondition Failed) — the `If-Match` etag we sent no
|
|
141
|
+
* longer matches the entity's current etag. */
|
|
142
|
+
isPreconditionFailed(e) {
|
|
143
|
+
return isGraphSdkError(e) && e.statusCode === 412;
|
|
144
|
+
}
|
|
145
|
+
/** Extracts the OData etag from a fetched entity, defaulting to `''` when absent. */
|
|
146
|
+
extractEtag(entity) {
|
|
147
|
+
return entity['@odata.etag'] ?? '';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Fetches a fresh etag immediately before a write (U5b-5 — the Planner etag is
|
|
151
|
+
* mutable and per-sub-resource, so it can't ride in the durable token or a
|
|
152
|
+
* cache) and retries the write once with a re-fetched etag on a 412, covering
|
|
153
|
+
* the narrow race between the fetch and the write.
|
|
154
|
+
*
|
|
155
|
+
* CONCURRENCY SEMANTIC (deliberate, last-writer-wins): because MCP tool calls
|
|
156
|
+
* are stateless, the etag the caller observed at an earlier `get_*` cannot be
|
|
157
|
+
* carried into a later `update_*`. We therefore re-read the etag at write time,
|
|
158
|
+
* which means a concurrent edit landing between the caller's read and their
|
|
159
|
+
* write is NOT detected — the write overwrites it. This is the intended
|
|
160
|
+
* trade-off (it eliminates the spurious 412s the old cached-etag path produced
|
|
161
|
+
* for a lone editor); Planner writes are not cross-read conflict-protected.
|
|
162
|
+
*/
|
|
163
|
+
async withFreshEtag(fetchEtag, write) {
|
|
164
|
+
// An empty If-Match is never a valid write intent — fail loudly rather than
|
|
165
|
+
// send `If-Match: ''` (which would 400/412 confusingly, or on a lenient
|
|
166
|
+
// endpoint become an unconditional overwrite).
|
|
167
|
+
const fetch = async () => {
|
|
168
|
+
const etag = await fetchEtag();
|
|
169
|
+
if (etag.length === 0) {
|
|
170
|
+
throw new Error('Cannot perform a conditional write: the entity returned no @odata.etag.');
|
|
171
|
+
}
|
|
172
|
+
return etag;
|
|
173
|
+
};
|
|
174
|
+
const etag = await fetch();
|
|
175
|
+
try {
|
|
176
|
+
return await write(etag);
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
if (this.isPreconditionFailed(e)) {
|
|
180
|
+
return await write(await fetch());
|
|
181
|
+
}
|
|
182
|
+
throw e;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async resolveChatId(chatId) {
|
|
186
|
+
try {
|
|
187
|
+
return this.toGraphId(chatId, 'chat');
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
if (e instanceof IdUnknownError) {
|
|
191
|
+
await this.listChatsAsync();
|
|
192
|
+
return this.toGraphId(chatId, 'chat');
|
|
193
|
+
}
|
|
194
|
+
throw e;
|
|
195
|
+
}
|
|
56
196
|
}
|
|
57
197
|
// ===========================================================================
|
|
58
198
|
// Folders
|
|
@@ -64,31 +204,15 @@ export class GraphRepository {
|
|
|
64
204
|
throw new Error('Use listFoldersAsync() for Graph repository');
|
|
65
205
|
}
|
|
66
206
|
async listFoldersAsync() {
|
|
207
|
+
// Rows carry self-encoding fd_ tokens (the mapper mints them) — no cache.
|
|
67
208
|
const folders = await this.client.listMailFolders();
|
|
68
|
-
// Update ID cache
|
|
69
|
-
for (const folder of folders) {
|
|
70
|
-
if (folder.id != null) {
|
|
71
|
-
const numericId = hashStringToNumber(folder.id);
|
|
72
|
-
this.idCache.folders.set(numericId, folder.id);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
209
|
return folders.map(mapMailFolderToRow);
|
|
76
210
|
}
|
|
77
211
|
getFolder(_id) {
|
|
78
212
|
throw new Error('Use getFolderAsync() for Graph repository');
|
|
79
213
|
}
|
|
80
214
|
async getFolderAsync(id) {
|
|
81
|
-
const graphId = this.
|
|
82
|
-
if (graphId == null) {
|
|
83
|
-
// Try to find it by listing all folders
|
|
84
|
-
await this.listFoldersAsync();
|
|
85
|
-
const refreshedGraphId = this.idCache.folders.get(id);
|
|
86
|
-
if (refreshedGraphId == null) {
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
const folder = await this.client.getMailFolder(refreshedGraphId);
|
|
90
|
-
return folder != null ? mapMailFolderToRow(folder) : undefined;
|
|
91
|
-
}
|
|
215
|
+
const graphId = this.toGraphId(id, 'folder');
|
|
92
216
|
const folder = await this.client.getMailFolder(graphId);
|
|
93
217
|
return folder != null ? mapMailFolderToRow(folder) : undefined;
|
|
94
218
|
}
|
|
@@ -99,58 +223,23 @@ export class GraphRepository {
|
|
|
99
223
|
throw new Error('Use listEmailsAsync() for Graph repository');
|
|
100
224
|
}
|
|
101
225
|
async listEmailsAsync(folderId, limit, offset) {
|
|
102
|
-
const graphFolderId = this.
|
|
103
|
-
if (graphFolderId == null) {
|
|
104
|
-
// Refresh folder cache
|
|
105
|
-
await this.listFoldersAsync();
|
|
106
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
107
|
-
if (refreshedId == null) {
|
|
108
|
-
return [];
|
|
109
|
-
}
|
|
110
|
-
return this.listEmailsWithGraphId(refreshedId, limit, offset);
|
|
111
|
-
}
|
|
226
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
112
227
|
return this.listEmailsWithGraphId(graphFolderId, limit, offset);
|
|
113
228
|
}
|
|
114
229
|
async listEmailsWithGraphId(folderId, limit, offset) {
|
|
230
|
+
// Rows carry self-encoding em_ tokens (the mapper mints them) — no cache.
|
|
115
231
|
const messages = await this.client.listMessages(folderId, limit, offset);
|
|
116
|
-
// Update ID cache
|
|
117
|
-
for (const message of messages) {
|
|
118
|
-
if (message.id != null) {
|
|
119
|
-
const numericId = hashStringToNumber(message.id);
|
|
120
|
-
this.idCache.messages.set(numericId, message.id);
|
|
121
|
-
}
|
|
122
|
-
if (message.conversationId != null) {
|
|
123
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
232
|
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
127
233
|
}
|
|
128
234
|
listUnreadEmails(_folderId, _limit, _offset) {
|
|
129
235
|
throw new Error('Use listUnreadEmailsAsync() for Graph repository');
|
|
130
236
|
}
|
|
131
237
|
async listUnreadEmailsAsync(folderId, limit, offset) {
|
|
132
|
-
const graphFolderId = this.
|
|
133
|
-
if (graphFolderId == null) {
|
|
134
|
-
await this.listFoldersAsync();
|
|
135
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
136
|
-
if (refreshedId == null) {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
return this.listUnreadEmailsWithGraphId(refreshedId, limit, offset);
|
|
140
|
-
}
|
|
238
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
141
239
|
return this.listUnreadEmailsWithGraphId(graphFolderId, limit, offset);
|
|
142
240
|
}
|
|
143
241
|
async listUnreadEmailsWithGraphId(folderId, limit, offset) {
|
|
144
242
|
const messages = await this.client.listUnreadMessages(folderId, limit, offset);
|
|
145
|
-
for (const message of messages) {
|
|
146
|
-
if (message.id != null) {
|
|
147
|
-
const numericId = hashStringToNumber(message.id);
|
|
148
|
-
this.idCache.messages.set(numericId, message.id);
|
|
149
|
-
}
|
|
150
|
-
if (message.conversationId != null) {
|
|
151
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
243
|
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
155
244
|
}
|
|
156
245
|
searchEmails(_query, _limit) {
|
|
@@ -158,96 +247,49 @@ export class GraphRepository {
|
|
|
158
247
|
}
|
|
159
248
|
async searchEmailsAsync(query, limit) {
|
|
160
249
|
const messages = await this.client.searchMessages(query, limit);
|
|
161
|
-
for (const message of messages) {
|
|
162
|
-
if (message.id != null) {
|
|
163
|
-
const numericId = hashStringToNumber(message.id);
|
|
164
|
-
this.idCache.messages.set(numericId, message.id);
|
|
165
|
-
}
|
|
166
|
-
if (message.conversationId != null) {
|
|
167
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
250
|
return messages.map((m) => mapMessageToEmailRow(m));
|
|
171
251
|
}
|
|
172
252
|
searchEmailsInFolder(_folderId, _query, _limit) {
|
|
173
253
|
throw new Error('Use searchEmailsInFolderAsync() for Graph repository');
|
|
174
254
|
}
|
|
175
255
|
async searchEmailsInFolderAsync(folderId, query, limit) {
|
|
176
|
-
const graphFolderId = this.
|
|
177
|
-
if (graphFolderId == null) {
|
|
178
|
-
await this.listFoldersAsync();
|
|
179
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
180
|
-
if (refreshedId == null) {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
return this.searchEmailsInFolderWithGraphId(refreshedId, query, limit);
|
|
184
|
-
}
|
|
256
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
185
257
|
return this.searchEmailsInFolderWithGraphId(graphFolderId, query, limit);
|
|
186
258
|
}
|
|
187
259
|
async searchEmailsInFolderWithGraphId(folderId, query, limit) {
|
|
188
260
|
const messages = await this.client.searchMessagesInFolder(folderId, query, limit);
|
|
189
|
-
for (const message of messages) {
|
|
190
|
-
if (message.id != null) {
|
|
191
|
-
const numericId = hashStringToNumber(message.id);
|
|
192
|
-
this.idCache.messages.set(numericId, message.id);
|
|
193
|
-
}
|
|
194
|
-
if (message.conversationId != null) {
|
|
195
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
261
|
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
199
262
|
}
|
|
200
263
|
/**
|
|
201
|
-
*
|
|
264
|
+
* Structured advanced search (U7 / D9). Runs a compiled query on the correct
|
|
265
|
+
* Graph mechanism ($filter / quoted $search / /search/query), then caches IDs
|
|
266
|
+
* and maps to EmailRow[] exactly like the raw-KQL path it replaces.
|
|
202
267
|
*/
|
|
203
|
-
async
|
|
204
|
-
const messages = await this.
|
|
205
|
-
for (const msg of messages) {
|
|
206
|
-
if (msg.id != null) {
|
|
207
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
208
|
-
}
|
|
209
|
-
if (msg.conversationId != null) {
|
|
210
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
268
|
+
async searchEmailsStructuredAsync(compiled, limit) {
|
|
269
|
+
const messages = await this.runStructuredSearch(compiled, limit);
|
|
213
270
|
return messages.map((m) => mapMessageToEmailRow(m));
|
|
214
271
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const messages = await this.client.searchMessagesKqlInFolder(graphFolderId, query, limit);
|
|
224
|
-
for (const msg of messages) {
|
|
225
|
-
if (msg.id != null) {
|
|
226
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
227
|
-
}
|
|
228
|
-
if (msg.conversationId != null) {
|
|
229
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
230
|
-
}
|
|
272
|
+
runStructuredSearch(compiled, limit) {
|
|
273
|
+
switch (compiled.mechanism) {
|
|
274
|
+
case 'filter':
|
|
275
|
+
return this.client.searchMessagesFilter(compiled.filter, limit);
|
|
276
|
+
case 'search':
|
|
277
|
+
return this.client.searchMessagesSearchValue(compiled.search, limit);
|
|
278
|
+
case 'searchQuery':
|
|
279
|
+
return this.client.searchMessagesQuery(compiled.kql, limit);
|
|
231
280
|
}
|
|
232
|
-
return messages.map((m) => mapMessageToEmailRow(m));
|
|
233
281
|
}
|
|
234
282
|
async checkNewEmailsAsync(folderId) {
|
|
235
|
-
const graphFolderId = this.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
283
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
284
|
+
// Key the delta cursor by the RESOLVED Graph id, not the input string: a
|
|
285
|
+
// folder can be addressed by its fd_ token or its raw Graph id, and both
|
|
286
|
+
// must share one cursor — otherwise the second form triggers a fresh initial
|
|
287
|
+
// sync and re-reports already-seen mail as new.
|
|
288
|
+
const existingDeltaLink = this.deltaLinks.get(graphFolderId);
|
|
239
289
|
const isInitialSync = existingDeltaLink == null;
|
|
240
290
|
const { messages, deltaLink } = await this.client.getMessagesDelta(graphFolderId, existingDeltaLink);
|
|
241
291
|
if (deltaLink) {
|
|
242
|
-
this.deltaLinks.set(
|
|
243
|
-
}
|
|
244
|
-
for (const msg of messages) {
|
|
245
|
-
if (msg.id != null) {
|
|
246
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
247
|
-
}
|
|
248
|
-
if (msg.conversationId != null) {
|
|
249
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
250
|
-
}
|
|
292
|
+
this.deltaLinks.set(graphFolderId, deltaLink);
|
|
251
293
|
}
|
|
252
294
|
const activeMessages = messages.filter((m) => m['@removed'] == null);
|
|
253
295
|
return {
|
|
@@ -258,61 +300,8 @@ export class GraphRepository {
|
|
|
258
300
|
getEmail(_id) {
|
|
259
301
|
throw new Error('Use getEmailAsync() for Graph repository');
|
|
260
302
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Populates the message ID cache by listing messages from mail folders.
|
|
263
|
-
* Used as a fallback when getEmailAsync is called with an ID not yet in cache
|
|
264
|
-
* (e.g. after server restart or when list_emails/search_emails wasn't called first).
|
|
265
|
-
*/
|
|
266
|
-
async refreshMessageCacheForGetEmail(targetId) {
|
|
267
|
-
let folders;
|
|
268
|
-
try {
|
|
269
|
-
folders = await this.listFoldersAsync();
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
if (folders.length === 0)
|
|
275
|
-
return false;
|
|
276
|
-
const MESSAGE_LIMIT_PER_FOLDER = 100;
|
|
277
|
-
const MAX_FOLDERS_TO_SCAN = 15;
|
|
278
|
-
for (let i = 0; i < Math.min(folders.length, MAX_FOLDERS_TO_SCAN); i++) {
|
|
279
|
-
const folder = folders[i];
|
|
280
|
-
const graphFolderId = this.idCache.folders.get(folder.id);
|
|
281
|
-
if (graphFolderId == null)
|
|
282
|
-
continue;
|
|
283
|
-
let messages;
|
|
284
|
-
try {
|
|
285
|
-
messages = await this.client.listMessages(graphFolderId, MESSAGE_LIMIT_PER_FOLDER, 0);
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
if (!Array.isArray(messages))
|
|
291
|
-
continue;
|
|
292
|
-
for (const message of messages) {
|
|
293
|
-
if (message.id != null) {
|
|
294
|
-
const numericId = hashStringToNumber(message.id);
|
|
295
|
-
this.idCache.messages.set(numericId, message.id);
|
|
296
|
-
if (numericId === targetId)
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
if (message.conversationId != null) {
|
|
300
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return this.idCache.messages.has(targetId);
|
|
305
|
-
}
|
|
306
303
|
async getEmailAsync(id) {
|
|
307
|
-
|
|
308
|
-
if (graphId == null) {
|
|
309
|
-
const found = await this.refreshMessageCacheForGetEmail(id);
|
|
310
|
-
if (found)
|
|
311
|
-
graphId = this.idCache.messages.get(id) ?? undefined;
|
|
312
|
-
}
|
|
313
|
-
if (graphId == null) {
|
|
314
|
-
return undefined;
|
|
315
|
-
}
|
|
304
|
+
const graphId = this.toGraphId(id, 'message');
|
|
316
305
|
const message = await this.client.getMessage(graphId);
|
|
317
306
|
return message != null ? mapMessageToEmailRow(message) : undefined;
|
|
318
307
|
}
|
|
@@ -327,16 +316,7 @@ export class GraphRepository {
|
|
|
327
316
|
throw new Error('Use getUnreadCountByFolderAsync() for Graph repository');
|
|
328
317
|
}
|
|
329
318
|
async getUnreadCountByFolderAsync(folderId) {
|
|
330
|
-
const graphId = this.
|
|
331
|
-
if (graphId == null) {
|
|
332
|
-
await this.listFoldersAsync();
|
|
333
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
334
|
-
if (refreshedId == null) {
|
|
335
|
-
return 0;
|
|
336
|
-
}
|
|
337
|
-
const folder = await this.client.getMailFolder(refreshedId);
|
|
338
|
-
return folder?.unreadItemCount ?? 0;
|
|
339
|
-
}
|
|
319
|
+
const graphId = this.toGraphId(folderId, 'folder');
|
|
340
320
|
const folder = await this.client.getMailFolder(graphId);
|
|
341
321
|
return folder?.unreadItemCount ?? 0;
|
|
342
322
|
}
|
|
@@ -346,27 +326,19 @@ export class GraphRepository {
|
|
|
346
326
|
/**
|
|
347
327
|
* Lists all messages in a conversation thread.
|
|
348
328
|
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
329
|
+
* Resolves the message id to its Graph id (durable `em_` token or raw Graph
|
|
330
|
+
* id), fetches the message to read its raw Graph conversationId, then queries
|
|
331
|
+
* for all messages with that ID. No cache required.
|
|
351
332
|
*/
|
|
352
333
|
async listConversationAsync(messageId, limit) {
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
334
|
+
const graphId = this.toGraphId(messageId, 'message');
|
|
335
|
+
const message = await this.client.getMessage(graphId);
|
|
336
|
+
if (message == null)
|
|
337
|
+
throw new Error('Message not found');
|
|
338
|
+
const convId = message.conversationId;
|
|
339
|
+
if (convId == null)
|
|
357
340
|
throw new Error('Message has no conversation ID');
|
|
358
|
-
const
|
|
359
|
-
if (graphConversationId == null)
|
|
360
|
-
throw new Error('Conversation ID not found in cache. Try fetching the email first to populate the cache.');
|
|
361
|
-
const messages = await this.client.listConversationMessages(graphConversationId, limit);
|
|
362
|
-
for (const msg of messages) {
|
|
363
|
-
if (msg.id != null) {
|
|
364
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
365
|
-
}
|
|
366
|
-
if (msg.conversationId != null) {
|
|
367
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
341
|
+
const messages = await this.client.listConversationMessages(convId, limit);
|
|
370
342
|
return messages.map((m) => mapMessageToEmailRow(m));
|
|
371
343
|
}
|
|
372
344
|
// ===========================================================================
|
|
@@ -376,72 +348,39 @@ export class GraphRepository {
|
|
|
376
348
|
throw new Error('Use listCalendarsAsync() for Graph repository');
|
|
377
349
|
}
|
|
378
350
|
async listCalendarsAsync() {
|
|
351
|
+
// Rows carry self-encoding fd_ tokens (the mapper mints them) — no cache.
|
|
379
352
|
const calendars = await this.client.listCalendars();
|
|
380
|
-
for (const calendar of calendars) {
|
|
381
|
-
if (calendar.id != null) {
|
|
382
|
-
const numericId = hashStringToNumber(calendar.id);
|
|
383
|
-
this.idCache.folders.set(numericId, calendar.id);
|
|
384
|
-
this.idCache.calendars.set(numericId, calendar.id);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
353
|
return calendars.map(mapCalendarToFolderRow);
|
|
388
354
|
}
|
|
389
355
|
listEvents(_limit) {
|
|
390
356
|
throw new Error('Use listEventsAsync() for Graph repository');
|
|
391
357
|
}
|
|
392
358
|
async listEventsAsync(limit) {
|
|
359
|
+
// Rows carry self-encoding ev_ tokens (the mapper mints them) — no cache.
|
|
393
360
|
const events = await this.client.listEvents(limit);
|
|
394
|
-
for (const event of events) {
|
|
395
|
-
if (event.id != null) {
|
|
396
|
-
const numericId = hashStringToNumber(event.id);
|
|
397
|
-
this.idCache.events.set(numericId, event.id);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
361
|
return events.map((e) => mapEventToEventRow(e));
|
|
401
362
|
}
|
|
402
363
|
listEventsByFolder(_folderId, _limit) {
|
|
403
364
|
throw new Error('Use listEventsByFolderAsync() for Graph repository');
|
|
404
365
|
}
|
|
405
366
|
async listEventsByFolderAsync(folderId, limit) {
|
|
406
|
-
const graphCalendarId = this.
|
|
407
|
-
if (graphCalendarId == null) {
|
|
408
|
-
return this.listEventsAsync(limit);
|
|
409
|
-
}
|
|
367
|
+
const graphCalendarId = this.toGraphId(folderId, 'folder');
|
|
410
368
|
const events = await this.client.listEvents(limit, graphCalendarId);
|
|
411
|
-
for (const event of events) {
|
|
412
|
-
if (event.id != null) {
|
|
413
|
-
const numericId = hashStringToNumber(event.id);
|
|
414
|
-
this.idCache.events.set(numericId, event.id);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
369
|
return events.map((e) => mapEventToEventRow(e, graphCalendarId));
|
|
418
370
|
}
|
|
419
371
|
searchEvents(_query, _startDate, _endDate, _limit) {
|
|
420
372
|
throw new Error('Use searchEventsAsync() for Graph repository');
|
|
421
373
|
}
|
|
422
374
|
async searchEventsAsync(query, startDate, endDate, limit) {
|
|
423
|
-
// Graph doesn't have direct event search, so we filter client-side
|
|
375
|
+
// Graph doesn't have direct event search, so we filter client-side on the
|
|
376
|
+
// Graph events (by subject) before mapping to rows.
|
|
424
377
|
const start = startDate != null ? new Date(startDate) : undefined;
|
|
425
378
|
const end = endDate != null ? new Date(endDate) : undefined;
|
|
426
379
|
const events = await this.client.listEvents(1000, undefined, start, end);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
let rows = events.map((e) => mapEventToEventRow(e));
|
|
434
|
-
// Filter by title client-side if query provided
|
|
435
|
-
if (query != null) {
|
|
436
|
-
const queryLower = query.toLowerCase();
|
|
437
|
-
rows = rows.filter((row) => {
|
|
438
|
-
// EventRow doesn't have title, so we need to check the original event
|
|
439
|
-
const originalEvent = events.find((e) => e.id != null && hashStringToNumber(e.id) === row.id);
|
|
440
|
-
const subject = originalEvent?.subject?.toLowerCase() ?? '';
|
|
441
|
-
return subject.includes(queryLower);
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
return rows.slice(0, limit);
|
|
380
|
+
const matched = query != null
|
|
381
|
+
? events.filter((e) => (e.subject?.toLowerCase() ?? '').includes(query.toLowerCase()))
|
|
382
|
+
: events;
|
|
383
|
+
return matched.slice(0, limit).map((e) => mapEventToEventRow(e));
|
|
445
384
|
}
|
|
446
385
|
listEventsByDateRange(_startDate, _endDate, _limit) {
|
|
447
386
|
throw new Error('Use listEventsByDateRangeAsync() for Graph repository');
|
|
@@ -450,36 +389,23 @@ export class GraphRepository {
|
|
|
450
389
|
const start = new Date(startDate * 1000);
|
|
451
390
|
const end = new Date(endDate * 1000);
|
|
452
391
|
const events = await this.client.listEvents(limit, undefined, start, end);
|
|
453
|
-
for (const event of events) {
|
|
454
|
-
if (event.id != null) {
|
|
455
|
-
const numericId = hashStringToNumber(event.id);
|
|
456
|
-
this.idCache.events.set(numericId, event.id);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
392
|
return events.map((e) => mapEventToEventRow(e));
|
|
460
393
|
}
|
|
461
394
|
getEvent(_id) {
|
|
462
395
|
throw new Error('Use getEventAsync() for Graph repository');
|
|
463
396
|
}
|
|
464
397
|
async getEventAsync(id) {
|
|
465
|
-
const graphId = this.
|
|
466
|
-
if (graphId == null) {
|
|
467
|
-
return undefined;
|
|
468
|
-
}
|
|
398
|
+
const graphId = this.toGraphId(id, 'event');
|
|
469
399
|
const event = await this.client.getEvent(graphId);
|
|
470
400
|
return event != null ? mapEventToEventRow(event) : undefined;
|
|
471
401
|
}
|
|
402
|
+
/** Resolves an event id (durable `ev_` token or raw Graph id) to its Graph id. */
|
|
403
|
+
getEventGraphId(id) {
|
|
404
|
+
return this.toGraphId(id, 'event');
|
|
405
|
+
}
|
|
472
406
|
async listEventInstancesAsync(eventId, startDate, endDate) {
|
|
473
|
-
const graphId = this.
|
|
474
|
-
if (graphId == null) {
|
|
475
|
-
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
476
|
-
}
|
|
407
|
+
const graphId = this.toGraphId(eventId, 'event');
|
|
477
408
|
const instances = await this.client.listEventInstances(graphId, startDate, endDate);
|
|
478
|
-
for (const inst of instances) {
|
|
479
|
-
if (inst.id != null) {
|
|
480
|
-
this.idCache.events.set(hashStringToNumber(inst.id), inst.id);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
409
|
return instances.map((e) => mapEventToEventRow(e));
|
|
484
410
|
}
|
|
485
411
|
// ===========================================================================
|
|
@@ -489,13 +415,9 @@ export class GraphRepository {
|
|
|
489
415
|
throw new Error('Use listContactsAsync() for Graph repository');
|
|
490
416
|
}
|
|
491
417
|
async listContactsAsync(limit, offset) {
|
|
418
|
+
// Rows carry self-encoding `ct_` tokens (the mapper mints them), so there is
|
|
419
|
+
// no numeric-id cache to populate — the token decodes to the Graph id.
|
|
492
420
|
const contacts = await this.client.listContacts(limit, offset);
|
|
493
|
-
for (const contact of contacts) {
|
|
494
|
-
if (contact.id != null) {
|
|
495
|
-
const numericId = hashStringToNumber(contact.id);
|
|
496
|
-
this.idCache.contacts.set(numericId, contact.id);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
421
|
return contacts.map(mapContactToContactRow);
|
|
500
422
|
}
|
|
501
423
|
searchContacts(_query, _limit) {
|
|
@@ -503,25 +425,20 @@ export class GraphRepository {
|
|
|
503
425
|
}
|
|
504
426
|
async searchContactsAsync(query, limit) {
|
|
505
427
|
const contacts = await this.client.searchContacts(query, limit);
|
|
506
|
-
for (const contact of contacts) {
|
|
507
|
-
if (contact.id != null) {
|
|
508
|
-
const numericId = hashStringToNumber(contact.id);
|
|
509
|
-
this.idCache.contacts.set(numericId, contact.id);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
428
|
return contacts.map(mapContactToContactRow);
|
|
513
429
|
}
|
|
514
430
|
getContact(_id) {
|
|
515
431
|
throw new Error('Use getContactAsync() for Graph repository');
|
|
516
432
|
}
|
|
517
433
|
async getContactAsync(id) {
|
|
518
|
-
const graphId = this.
|
|
519
|
-
if (graphId == null) {
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
434
|
+
const graphId = this.toGraphId(id, 'contact');
|
|
522
435
|
const contact = await this.client.getContact(graphId);
|
|
523
436
|
return contact != null ? mapContactToContactRow(contact) : undefined;
|
|
524
437
|
}
|
|
438
|
+
/** Resolves a contact id (durable `ct_` token or raw Graph id) to its Graph id. */
|
|
439
|
+
getContactGraphId(id) {
|
|
440
|
+
return this.toGraphId(id, 'contact');
|
|
441
|
+
}
|
|
525
442
|
// ===========================================================================
|
|
526
443
|
// Contact Folders
|
|
527
444
|
// ===========================================================================
|
|
@@ -529,10 +446,8 @@ export class GraphRepository {
|
|
|
529
446
|
const folders = await this.client.listContactFolders();
|
|
530
447
|
return folders.map((folder) => {
|
|
531
448
|
const graphId = folder.id;
|
|
532
|
-
const numericId = hashStringToNumber(graphId);
|
|
533
|
-
this.idCache.contactFolders.set(numericId, graphId);
|
|
534
449
|
return {
|
|
535
|
-
id:
|
|
450
|
+
id: this.mintAlias('contactFolder', graphId),
|
|
536
451
|
name: folder.displayName ?? '',
|
|
537
452
|
parentFolderId: folder.parentFolderId ?? null,
|
|
538
453
|
};
|
|
@@ -540,47 +455,30 @@ export class GraphRepository {
|
|
|
540
455
|
}
|
|
541
456
|
async createContactFolderAsync(name) {
|
|
542
457
|
const created = await this.client.createContactFolder(name);
|
|
543
|
-
|
|
544
|
-
const numericId = hashStringToNumber(graphId);
|
|
545
|
-
this.idCache.contactFolders.set(numericId, graphId);
|
|
546
|
-
return numericId;
|
|
458
|
+
return this.mintAlias('contactFolder', created.id);
|
|
547
459
|
}
|
|
548
460
|
async deleteContactFolderAsync(folderId) {
|
|
549
|
-
const graphId = this.
|
|
550
|
-
if (graphId == null)
|
|
551
|
-
throw new Error(`Contact folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
461
|
+
const graphId = this.toGraphId(folderId, 'contactFolder');
|
|
552
462
|
await this.client.deleteContactFolder(graphId);
|
|
553
|
-
this.idCache.contactFolders.delete(folderId);
|
|
554
463
|
}
|
|
555
464
|
async listContactsInFolderAsync(folderId, limit = 100) {
|
|
556
|
-
const graphId = this.
|
|
557
|
-
if (graphId == null)
|
|
558
|
-
throw new Error(`Contact folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
465
|
+
const graphId = this.toGraphId(folderId, 'contactFolder');
|
|
559
466
|
const contacts = await this.client.listContactsInFolder(graphId, limit);
|
|
560
|
-
return contacts.map(
|
|
561
|
-
if (c.id != null) {
|
|
562
|
-
this.idCache.contacts.set(hashStringToNumber(c.id), c.id);
|
|
563
|
-
}
|
|
564
|
-
return mapContactToContactRow(c);
|
|
565
|
-
});
|
|
467
|
+
return contacts.map(mapContactToContactRow);
|
|
566
468
|
}
|
|
567
469
|
// ===========================================================================
|
|
568
470
|
// Contact Photos
|
|
569
471
|
// ===========================================================================
|
|
570
472
|
async getContactPhotoAsync(contactId) {
|
|
571
|
-
const graphId = this.
|
|
572
|
-
if (graphId == null)
|
|
573
|
-
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
473
|
+
const graphId = this.toGraphId(contactId, 'contact');
|
|
574
474
|
const photoData = await this.client.getContactPhoto(graphId);
|
|
575
475
|
const downloadDir = getDownloadDir();
|
|
576
|
-
const filePath = path.join(downloadDir, `contact-${
|
|
476
|
+
const filePath = path.join(downloadDir, `contact-${createHash('sha1').update(graphId).digest('hex').slice(0, 16)}-photo.jpg`);
|
|
577
477
|
fs.writeFileSync(filePath, Buffer.from(photoData));
|
|
578
478
|
return { filePath, contentType: 'image/jpeg' };
|
|
579
479
|
}
|
|
580
480
|
async setContactPhotoAsync(contactId, filePath) {
|
|
581
|
-
const graphId = this.
|
|
582
|
-
if (graphId == null)
|
|
583
|
-
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
481
|
+
const graphId = this.toGraphId(contactId, 'contact');
|
|
584
482
|
const photoData = fs.readFileSync(filePath);
|
|
585
483
|
const ext = path.extname(filePath).toLowerCase();
|
|
586
484
|
const contentType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
@@ -594,66 +492,65 @@ export class GraphRepository {
|
|
|
594
492
|
}
|
|
595
493
|
async listTasksAsync(limit, offset) {
|
|
596
494
|
const tasks = await this.client.listAllTasks(limit, offset, true);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
return tasks.map(mapTaskToTaskRow);
|
|
495
|
+
return tasks
|
|
496
|
+
.filter((task) => task.id != null && task.taskListId != null)
|
|
497
|
+
.map((task) => {
|
|
498
|
+
const listTok = this.mintAlias('taskList', task.taskListId);
|
|
499
|
+
const taskTok = this.mintAliasComposite('task', { taskListId: task.taskListId, taskId: task.id });
|
|
500
|
+
return mapTaskToTaskRow({ ...task }, taskTok, listTok);
|
|
501
|
+
});
|
|
606
502
|
}
|
|
607
503
|
listIncompleteTasks(_limit, _offset) {
|
|
608
504
|
throw new Error('Use listIncompleteTasksAsync() for Graph repository');
|
|
609
505
|
}
|
|
610
506
|
async listIncompleteTasksAsync(limit, offset) {
|
|
611
507
|
const tasks = await this.client.listAllTasks(limit, offset, false);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
return tasks.map(mapTaskToTaskRow);
|
|
508
|
+
return tasks
|
|
509
|
+
.filter((task) => task.id != null && task.taskListId != null)
|
|
510
|
+
.map((task) => {
|
|
511
|
+
const listTok = this.mintAlias('taskList', task.taskListId);
|
|
512
|
+
const taskTok = this.mintAliasComposite('task', { taskListId: task.taskListId, taskId: task.id });
|
|
513
|
+
return mapTaskToTaskRow({ ...task }, taskTok, listTok);
|
|
514
|
+
});
|
|
621
515
|
}
|
|
622
516
|
searchTasks(_query, _limit) {
|
|
623
517
|
throw new Error('Use searchTasksAsync() for Graph repository');
|
|
624
518
|
}
|
|
625
519
|
async searchTasksAsync(query, limit) {
|
|
626
520
|
const tasks = await this.client.searchTasks(query, limit);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
521
|
+
// Tasks without a taskListId can't form a {taskListId, taskId} composite
|
|
522
|
+
// token — skip them, matching the current cache-miss guard.
|
|
523
|
+
return tasks
|
|
524
|
+
.filter((task) => task.id != null && task.taskListId != null)
|
|
525
|
+
.map((task) => {
|
|
526
|
+
const listTok = this.mintAlias('taskList', task.taskListId);
|
|
527
|
+
const taskTok = this.mintAliasComposite('task', { taskListId: task.taskListId, taskId: task.id });
|
|
528
|
+
return mapTaskToTaskRow({ ...task }, taskTok, listTok);
|
|
529
|
+
});
|
|
634
530
|
}
|
|
635
531
|
getTask(_id) {
|
|
636
532
|
throw new Error('Use getTaskAsync() for Graph repository');
|
|
637
533
|
}
|
|
638
534
|
async getTaskAsync(id) {
|
|
639
|
-
|
|
640
|
-
|
|
535
|
+
let taskInfo;
|
|
536
|
+
try {
|
|
537
|
+
taskInfo = this.toGraphParts(id, 'task', ['taskListId', 'taskId']);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
641
540
|
return undefined;
|
|
642
541
|
}
|
|
643
542
|
const task = await this.client.getTask(taskInfo.taskListId, taskInfo.taskId);
|
|
644
543
|
if (task == null) {
|
|
645
544
|
return undefined;
|
|
646
545
|
}
|
|
647
|
-
return mapTaskToTaskRow({ ...task, taskListId: taskInfo.taskListId });
|
|
546
|
+
return mapTaskToTaskRow({ ...task, taskListId: taskInfo.taskListId }, String(id), this.mintAlias('taskList', taskInfo.taskListId));
|
|
648
547
|
}
|
|
649
548
|
async listTaskListsAsync() {
|
|
650
549
|
const lists = await this.client.listTaskLists();
|
|
651
550
|
return lists.map((list) => {
|
|
652
551
|
const graphId = list.id;
|
|
653
|
-
const numericId = hashStringToNumber(graphId);
|
|
654
|
-
this.idCache.taskLists.set(numericId, graphId);
|
|
655
552
|
return {
|
|
656
|
-
id:
|
|
553
|
+
id: this.mintAlias('taskList', graphId),
|
|
657
554
|
name: list.displayName ?? '',
|
|
658
555
|
isDefault: list.wellknownListName === 'defaultList',
|
|
659
556
|
};
|
|
@@ -694,37 +591,33 @@ export class GraphRepository {
|
|
|
694
591
|
return this.client;
|
|
695
592
|
}
|
|
696
593
|
/**
|
|
697
|
-
*
|
|
594
|
+
* Resolves a draft id (durable `em_` token or raw Graph id) to its Graph id
|
|
595
|
+
* (satisfies IMailSendRepository). Drafts are messages.
|
|
698
596
|
*/
|
|
699
597
|
getGraphIdForDraft(draftId) {
|
|
700
|
-
return this.
|
|
598
|
+
return this.toGraphId(draftId, 'message');
|
|
701
599
|
}
|
|
702
600
|
/**
|
|
703
|
-
* Gets
|
|
601
|
+
* Gets task info (Graph taskListId/taskId) from a durable `td_` token.
|
|
704
602
|
*/
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
case 'event':
|
|
712
|
-
return this.idCache.events.get(numericId);
|
|
713
|
-
case 'contact':
|
|
714
|
-
return this.idCache.contacts.get(numericId);
|
|
603
|
+
getTaskInfo(id) {
|
|
604
|
+
try {
|
|
605
|
+
return this.toGraphParts(id, 'task', ['taskListId', 'taskId']);
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
return undefined;
|
|
715
609
|
}
|
|
716
610
|
}
|
|
717
611
|
/**
|
|
718
|
-
* Gets task
|
|
719
|
-
*/
|
|
720
|
-
getTaskInfo(numericId) {
|
|
721
|
-
return this.idCache.tasks.get(numericId);
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* Gets the Graph string ID for a task list from a numeric ID.
|
|
612
|
+
* Gets the Graph string ID for a task list from a durable `tl_` token.
|
|
725
613
|
*/
|
|
726
|
-
getTaskListGraphId(
|
|
727
|
-
|
|
614
|
+
getTaskListGraphId(id) {
|
|
615
|
+
try {
|
|
616
|
+
return this.toGraphId(id, 'taskList');
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
728
621
|
}
|
|
729
622
|
// ===========================================================================
|
|
730
623
|
// Write Operations (Async)
|
|
@@ -771,42 +664,28 @@ export class GraphRepository {
|
|
|
771
664
|
}
|
|
772
665
|
// Async implementations
|
|
773
666
|
async moveEmailAsync(emailId, destinationFolderId) {
|
|
774
|
-
const graphMessageId = this.
|
|
775
|
-
const graphFolderId = this.
|
|
776
|
-
if (graphMessageId == null)
|
|
777
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
778
|
-
if (graphFolderId == null)
|
|
779
|
-
throw new Error(`Folder ID ${destinationFolderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
667
|
+
const graphMessageId = this.toGraphId(emailId, 'message');
|
|
668
|
+
const graphFolderId = this.toGraphId(destinationFolderId, 'folder');
|
|
780
669
|
await this.client.moveMessage(graphMessageId, graphFolderId);
|
|
781
670
|
}
|
|
782
671
|
async deleteEmailAsync(emailId) {
|
|
783
|
-
const graphId = this.
|
|
784
|
-
if (graphId == null)
|
|
785
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
672
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
786
673
|
await this.client.deleteMessage(graphId);
|
|
787
674
|
}
|
|
788
675
|
async archiveEmailAsync(emailId) {
|
|
789
|
-
const graphId = this.
|
|
790
|
-
if (graphId == null)
|
|
791
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
676
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
792
677
|
await this.client.archiveMessage(graphId);
|
|
793
678
|
}
|
|
794
679
|
async junkEmailAsync(emailId) {
|
|
795
|
-
const graphId = this.
|
|
796
|
-
if (graphId == null)
|
|
797
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
680
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
798
681
|
await this.client.junkMessage(graphId);
|
|
799
682
|
}
|
|
800
683
|
async markEmailReadAsync(emailId, isRead) {
|
|
801
|
-
const graphId = this.
|
|
802
|
-
if (graphId == null)
|
|
803
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
684
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
804
685
|
await this.client.updateMessage(graphId, { isRead });
|
|
805
686
|
}
|
|
806
687
|
async setEmailFlagAsync(emailId, flagStatus) {
|
|
807
|
-
const graphId = this.
|
|
808
|
-
if (graphId == null)
|
|
809
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
688
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
810
689
|
const flagStatusMap = {
|
|
811
690
|
0: 'notFlagged',
|
|
812
691
|
1: 'flagged',
|
|
@@ -817,55 +696,35 @@ export class GraphRepository {
|
|
|
817
696
|
});
|
|
818
697
|
}
|
|
819
698
|
async setEmailCategoriesAsync(emailId, categories) {
|
|
820
|
-
const graphId = this.
|
|
821
|
-
if (graphId == null)
|
|
822
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
699
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
823
700
|
await this.client.updateMessage(graphId, { categories });
|
|
824
701
|
}
|
|
825
702
|
async setEmailImportanceAsync(emailId, importance) {
|
|
826
|
-
const graphId = this.
|
|
827
|
-
if (graphId == null)
|
|
828
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
703
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
829
704
|
await this.client.updateMessage(graphId, { importance });
|
|
830
705
|
}
|
|
831
706
|
async createFolderAsync(name, parentFolderId) {
|
|
832
707
|
const graphParentId = parentFolderId != null
|
|
833
|
-
? this.
|
|
708
|
+
? this.toGraphId(parentFolderId, 'folder')
|
|
834
709
|
: undefined;
|
|
835
|
-
const folder = await this.client.createMailFolder(name, graphParentId
|
|
836
|
-
// Update cache with new folder
|
|
837
|
-
if (folder.id != null) {
|
|
838
|
-
const numericId = hashStringToNumber(folder.id);
|
|
839
|
-
this.idCache.folders.set(numericId, folder.id);
|
|
840
|
-
}
|
|
710
|
+
const folder = await this.client.createMailFolder(name, graphParentId);
|
|
841
711
|
return mapMailFolderToRow(folder);
|
|
842
712
|
}
|
|
843
713
|
async deleteFolderAsync(folderId) {
|
|
844
|
-
const graphId = this.
|
|
845
|
-
if (graphId == null)
|
|
846
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
714
|
+
const graphId = this.toGraphId(folderId, 'folder');
|
|
847
715
|
await this.client.deleteMailFolder(graphId);
|
|
848
|
-
this.idCache.folders.delete(folderId);
|
|
849
716
|
}
|
|
850
717
|
async renameFolderAsync(folderId, newName) {
|
|
851
|
-
const graphId = this.
|
|
852
|
-
if (graphId == null)
|
|
853
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
718
|
+
const graphId = this.toGraphId(folderId, 'folder');
|
|
854
719
|
await this.client.renameMailFolder(graphId, newName);
|
|
855
720
|
}
|
|
856
721
|
async moveFolderAsync(folderId, destinationParentId) {
|
|
857
|
-
const graphFolderId = this.
|
|
858
|
-
const graphParentId = this.
|
|
859
|
-
if (graphFolderId == null)
|
|
860
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
861
|
-
if (graphParentId == null)
|
|
862
|
-
throw new Error(`Parent folder ID ${destinationParentId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
722
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
723
|
+
const graphParentId = this.toGraphId(destinationParentId, 'folder');
|
|
863
724
|
await this.client.moveMailFolder(graphFolderId, graphParentId);
|
|
864
725
|
}
|
|
865
726
|
async emptyFolderAsync(folderId) {
|
|
866
|
-
const graphId = this.
|
|
867
|
-
if (graphId == null)
|
|
868
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
727
|
+
const graphId = this.toGraphId(folderId, 'folder');
|
|
869
728
|
await this.client.emptyMailFolder(graphId);
|
|
870
729
|
}
|
|
871
730
|
// ===========================================================================
|
|
@@ -875,7 +734,7 @@ export class GraphRepository {
|
|
|
875
734
|
* Creates a new draft message.
|
|
876
735
|
*
|
|
877
736
|
* Converts email address strings to Recipient objects, calls the Graph client,
|
|
878
|
-
*
|
|
737
|
+
* and returns a durable `em_` token plus the raw Graph id.
|
|
879
738
|
*/
|
|
880
739
|
async createDraftAsync(params) {
|
|
881
740
|
const toRecipients = (params.to ?? []).map(addr => ({
|
|
@@ -895,19 +754,15 @@ export class GraphRepository {
|
|
|
895
754
|
bccRecipients,
|
|
896
755
|
});
|
|
897
756
|
const graphId = draft.id;
|
|
898
|
-
|
|
899
|
-
this.idCache.messages.set(numericId, graphId);
|
|
900
|
-
return { numericId, graphId };
|
|
757
|
+
return { token: mintSelfEncoded('message', graphId), graphId };
|
|
901
758
|
}
|
|
902
759
|
/**
|
|
903
760
|
* Updates an existing draft message.
|
|
904
761
|
*
|
|
905
|
-
*
|
|
762
|
+
* Resolves the draft id to its Graph id, then calls the client.
|
|
906
763
|
*/
|
|
907
764
|
async updateDraftAsync(draftId, updates) {
|
|
908
|
-
const graphId = this.
|
|
909
|
-
if (graphId == null)
|
|
910
|
-
throw new Error(`Message ID ${draftId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
765
|
+
const graphId = this.toGraphId(draftId, 'message');
|
|
911
766
|
await this.client.updateDraft(graphId, updates);
|
|
912
767
|
}
|
|
913
768
|
/**
|
|
@@ -921,12 +776,10 @@ export class GraphRepository {
|
|
|
921
776
|
/**
|
|
922
777
|
* Sends an existing draft message.
|
|
923
778
|
*
|
|
924
|
-
*
|
|
779
|
+
* Resolves the id to its Graph id, then calls the client.
|
|
925
780
|
*/
|
|
926
781
|
async sendDraftAsync(draftId) {
|
|
927
|
-
const graphId = this.
|
|
928
|
-
if (graphId == null)
|
|
929
|
-
throw new Error(`Message ID ${draftId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
782
|
+
const graphId = this.toGraphId(draftId, 'message');
|
|
930
783
|
await this.client.sendDraft(graphId);
|
|
931
784
|
}
|
|
932
785
|
/**
|
|
@@ -955,24 +808,20 @@ export class GraphRepository {
|
|
|
955
808
|
/**
|
|
956
809
|
* Replies to a message (or replies all).
|
|
957
810
|
*
|
|
958
|
-
*
|
|
811
|
+
* Resolves the id to its Graph id, then calls the client.
|
|
959
812
|
*/
|
|
960
813
|
async replyMessageAsync(messageId, comment, replyAll) {
|
|
961
|
-
const graphId = this.
|
|
962
|
-
if (graphId == null)
|
|
963
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
814
|
+
const graphId = this.toGraphId(messageId, 'message');
|
|
964
815
|
await this.client.replyMessage(graphId, comment, replyAll);
|
|
965
816
|
}
|
|
966
817
|
/**
|
|
967
818
|
* Forwards a message to specified recipients.
|
|
968
819
|
*
|
|
969
|
-
*
|
|
970
|
-
*
|
|
820
|
+
* Resolves the id to its Graph id, converts recipient email strings to
|
|
821
|
+
* Recipient objects, then calls the client.
|
|
971
822
|
*/
|
|
972
823
|
async forwardMessageAsync(messageId, toRecipients, comment) {
|
|
973
|
-
const graphId = this.
|
|
974
|
-
if (graphId == null)
|
|
975
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
824
|
+
const graphId = this.toGraphId(messageId, 'message');
|
|
976
825
|
const recipients = toRecipients.map(addr => ({
|
|
977
826
|
emailAddress: { address: addr },
|
|
978
827
|
}));
|
|
@@ -984,45 +833,33 @@ export class GraphRepository {
|
|
|
984
833
|
/**
|
|
985
834
|
* Creates a reply (or reply-all) draft for a message.
|
|
986
835
|
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
836
|
+
* Resolves the source message id to its Graph id, creates the draft via the
|
|
837
|
+
* client, and optionally updates the body.
|
|
989
838
|
*
|
|
990
|
-
* @returns
|
|
839
|
+
* @returns A durable `em_` token and the raw Graph id of the new draft.
|
|
991
840
|
*/
|
|
992
841
|
async replyAsDraftAsync(messageId, replyAll = false, comment, bodyType = 'text') {
|
|
993
|
-
const graphMessageId = this.
|
|
994
|
-
|
|
995
|
-
|
|
842
|
+
const graphMessageId = this.toGraphId(messageId, 'message');
|
|
843
|
+
// Pass comment/body through createReply so the quoted thread is preserved
|
|
844
|
+
const body = comment != null ? { contentType: bodyType, content: comment } : undefined;
|
|
996
845
|
const draft = replyAll
|
|
997
|
-
? await this.client.createReplyAllDraft(graphMessageId)
|
|
998
|
-
: await this.client.createReplyDraft(graphMessageId);
|
|
846
|
+
? await this.client.createReplyAllDraft(graphMessageId, undefined, body)
|
|
847
|
+
: await this.client.createReplyDraft(graphMessageId, undefined, body);
|
|
999
848
|
const graphId = draft.id;
|
|
1000
|
-
|
|
1001
|
-
this.idCache.messages.set(numericId, graphId);
|
|
1002
|
-
if (comment != null) {
|
|
1003
|
-
await this.client.updateDraft(graphId, {
|
|
1004
|
-
body: { contentType: bodyType, content: comment },
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
return { numericId, graphId };
|
|
849
|
+
return { token: mintSelfEncoded('message', graphId), graphId };
|
|
1008
850
|
}
|
|
1009
851
|
/**
|
|
1010
852
|
* Creates a forward draft for a message.
|
|
1011
853
|
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
* recipients and body.
|
|
854
|
+
* Resolves the source message id to its Graph id, creates the draft via the
|
|
855
|
+
* client, and optionally updates the recipients and body.
|
|
1015
856
|
*
|
|
1016
|
-
* @returns
|
|
857
|
+
* @returns A durable `em_` token and the raw Graph id of the new draft.
|
|
1017
858
|
*/
|
|
1018
859
|
async forwardAsDraftAsync(messageId, toRecipients, comment, bodyType = 'text') {
|
|
1019
|
-
const graphMessageId = this.
|
|
1020
|
-
if (graphMessageId == null)
|
|
1021
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
860
|
+
const graphMessageId = this.toGraphId(messageId, 'message');
|
|
1022
861
|
const draft = await this.client.createForwardDraft(graphMessageId);
|
|
1023
862
|
const graphId = draft.id;
|
|
1024
|
-
const numericId = hashStringToNumber(graphId);
|
|
1025
|
-
this.idCache.messages.set(numericId, graphId);
|
|
1026
863
|
const updates = {};
|
|
1027
864
|
if (toRecipients != null && toRecipients.length > 0) {
|
|
1028
865
|
updates.toRecipients = toRecipients.map(addr => ({
|
|
@@ -1035,7 +872,7 @@ export class GraphRepository {
|
|
|
1035
872
|
if (Object.keys(updates).length > 0) {
|
|
1036
873
|
await this.client.updateDraft(graphId, updates);
|
|
1037
874
|
}
|
|
1038
|
-
return {
|
|
875
|
+
return { token: mintSelfEncoded('message', graphId), graphId };
|
|
1039
876
|
}
|
|
1040
877
|
// ---------------------------------------------------------------------------
|
|
1041
878
|
// Calendar Scheduling
|
|
@@ -1077,26 +914,18 @@ export class GraphRepository {
|
|
|
1077
914
|
/**
|
|
1078
915
|
* Lists attachments for a given email.
|
|
1079
916
|
*
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
* and caches it in idCache.attachments with { messageId, attachmentId }.
|
|
917
|
+
* Resolves the id to its Graph id, calls client.listAttachments, and mints
|
|
918
|
+
* an `at_` composite alias token ({ messageId, attachmentId }) per item.
|
|
1083
919
|
*
|
|
1084
920
|
* @returns Array of attachment metadata objects.
|
|
1085
921
|
*/
|
|
1086
922
|
async listAttachmentsAsync(emailId) {
|
|
1087
|
-
const graphMessageId = this.
|
|
1088
|
-
if (graphMessageId == null)
|
|
1089
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
923
|
+
const graphMessageId = this.toGraphId(emailId, 'message');
|
|
1090
924
|
const attachments = await this.client.listAttachments(graphMessageId);
|
|
1091
925
|
return attachments.map((att) => {
|
|
1092
926
|
const attId = att.id ?? '';
|
|
1093
|
-
const numericId = hashStringToNumber(attId);
|
|
1094
|
-
this.idCache.attachments.set(numericId, {
|
|
1095
|
-
messageId: graphMessageId,
|
|
1096
|
-
attachmentId: attId,
|
|
1097
|
-
});
|
|
1098
927
|
return {
|
|
1099
|
-
id:
|
|
928
|
+
id: this.mintAliasComposite('attachment', { messageId: graphMessageId, attachmentId: attId }),
|
|
1100
929
|
name: att.name ?? '',
|
|
1101
930
|
size: att.size ?? 0,
|
|
1102
931
|
contentType: att.contentType ?? 'application/octet-stream',
|
|
@@ -1107,16 +936,14 @@ export class GraphRepository {
|
|
|
1107
936
|
/**
|
|
1108
937
|
* Downloads an attachment for a given email.
|
|
1109
938
|
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
939
|
+
* Resolves the `at_` composite token to { messageId, attachmentId }, then
|
|
940
|
+
* delegates to the downloadAttachment helper which fetches the content and
|
|
941
|
+
* writes it to disk.
|
|
1113
942
|
*
|
|
1114
943
|
* @returns Metadata about the downloaded file including its local path.
|
|
1115
944
|
*/
|
|
1116
945
|
async downloadAttachmentAsync(attachmentId) {
|
|
1117
|
-
const cached = this.
|
|
1118
|
-
if (cached == null)
|
|
1119
|
-
throw new Error(`Attachment ID ${attachmentId} not found in cache. Call list_attachments first.`);
|
|
946
|
+
const cached = this.toGraphParts(attachmentId, 'attachment', ['messageId', 'attachmentId']);
|
|
1120
947
|
return downloadAttachment(this.client, cached.messageId, cached.attachmentId);
|
|
1121
948
|
}
|
|
1122
949
|
// ===========================================================================
|
|
@@ -1162,13 +989,11 @@ export class GraphRepository {
|
|
|
1162
989
|
graphEvent.onlineMeetingProvider = params.online_meeting_provider ?? 'teamsForBusiness';
|
|
1163
990
|
}
|
|
1164
991
|
const graphCalendarId = params.calendarId != null
|
|
1165
|
-
? this.
|
|
992
|
+
? this.toGraphId(params.calendarId, 'folder')
|
|
1166
993
|
: undefined;
|
|
1167
994
|
const created = await this.client.createEvent(graphEvent, graphCalendarId);
|
|
1168
995
|
const graphId = created.id;
|
|
1169
|
-
|
|
1170
|
-
this.idCache.events.set(numericId, graphId);
|
|
1171
|
-
return numericId;
|
|
996
|
+
return mintSelfEncoded('event', graphId);
|
|
1172
997
|
}
|
|
1173
998
|
/**
|
|
1174
999
|
* Updates an existing calendar event.
|
|
@@ -1177,9 +1002,7 @@ export class GraphRepository {
|
|
|
1177
1002
|
* client.updateEvent(). Throws if the event is not cached.
|
|
1178
1003
|
*/
|
|
1179
1004
|
async updateEventAsync(eventId, updates) {
|
|
1180
|
-
const graphId = this.
|
|
1181
|
-
if (graphId == null)
|
|
1182
|
-
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1005
|
+
const graphId = this.toGraphId(eventId, 'event');
|
|
1183
1006
|
await this.client.updateEvent(graphId, updates);
|
|
1184
1007
|
}
|
|
1185
1008
|
/**
|
|
@@ -1190,11 +1013,8 @@ export class GraphRepository {
|
|
|
1190
1013
|
* Throws if the event is not cached.
|
|
1191
1014
|
*/
|
|
1192
1015
|
async deleteEventAsync(eventId) {
|
|
1193
|
-
const graphId = this.
|
|
1194
|
-
if (graphId == null)
|
|
1195
|
-
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1016
|
+
const graphId = this.toGraphId(eventId, 'event');
|
|
1196
1017
|
await this.client.deleteEvent(graphId);
|
|
1197
|
-
this.idCache.events.delete(eventId);
|
|
1198
1018
|
}
|
|
1199
1019
|
/**
|
|
1200
1020
|
* Responds to a calendar event invitation.
|
|
@@ -1203,9 +1023,7 @@ export class GraphRepository {
|
|
|
1203
1023
|
* client.respondToEvent(). Throws if the event is not cached.
|
|
1204
1024
|
*/
|
|
1205
1025
|
async respondToEventAsync(eventId, response, sendResponse, comment) {
|
|
1206
|
-
const graphId = this.
|
|
1207
|
-
if (graphId == null)
|
|
1208
|
-
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1026
|
+
const graphId = this.toGraphId(eventId, 'event');
|
|
1209
1027
|
await this.client.respondToEvent(graphId, response, sendResponse, comment);
|
|
1210
1028
|
}
|
|
1211
1029
|
// ===========================================================================
|
|
@@ -1250,9 +1068,7 @@ export class GraphRepository {
|
|
|
1250
1068
|
}
|
|
1251
1069
|
const created = await this.client.createContact(graphContact);
|
|
1252
1070
|
const graphId = created.id;
|
|
1253
|
-
|
|
1254
|
-
this.idCache.contacts.set(numericId, graphId);
|
|
1255
|
-
return numericId;
|
|
1071
|
+
return mintSelfEncoded('contact', graphId);
|
|
1256
1072
|
}
|
|
1257
1073
|
/**
|
|
1258
1074
|
* Updates an existing contact.
|
|
@@ -1261,9 +1077,7 @@ export class GraphRepository {
|
|
|
1261
1077
|
* client.updateContact(). Throws if the contact is not cached.
|
|
1262
1078
|
*/
|
|
1263
1079
|
async updateContactAsync(contactId, updates) {
|
|
1264
|
-
const graphId = this.
|
|
1265
|
-
if (graphId == null)
|
|
1266
|
-
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1080
|
+
const graphId = this.toGraphId(contactId, 'contact');
|
|
1267
1081
|
await this.client.updateContact(graphId, updates);
|
|
1268
1082
|
}
|
|
1269
1083
|
/**
|
|
@@ -1274,11 +1088,8 @@ export class GraphRepository {
|
|
|
1274
1088
|
* Throws if the contact is not cached.
|
|
1275
1089
|
*/
|
|
1276
1090
|
async deleteContactAsync(contactId) {
|
|
1277
|
-
const graphId = this.
|
|
1278
|
-
if (graphId == null)
|
|
1279
|
-
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1091
|
+
const graphId = this.toGraphId(contactId, 'contact');
|
|
1280
1092
|
await this.client.deleteContact(graphId);
|
|
1281
|
-
this.idCache.contacts.delete(contactId);
|
|
1282
1093
|
}
|
|
1283
1094
|
// ===========================================================================
|
|
1284
1095
|
// Task Write Operations (Async)
|
|
@@ -1286,14 +1097,12 @@ export class GraphRepository {
|
|
|
1286
1097
|
/**
|
|
1287
1098
|
* Creates a new task in a task list.
|
|
1288
1099
|
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1100
|
+
* Resolves the `tl_` task list token to a Graph ID, builds a Graph API task
|
|
1101
|
+
* object from the given params, calls client.createTask(), and returns a
|
|
1102
|
+
* durable `td_` token for the created task.
|
|
1292
1103
|
*/
|
|
1293
1104
|
async createTaskAsync(params) {
|
|
1294
|
-
const graphListId = this.
|
|
1295
|
-
if (graphListId == null)
|
|
1296
|
-
throw new Error(`Task list ID ${params.task_list_id} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1105
|
+
const graphListId = this.toGraphId(params.task_list_id, 'taskList');
|
|
1297
1106
|
const graphTask = {
|
|
1298
1107
|
title: params.title,
|
|
1299
1108
|
};
|
|
@@ -1339,22 +1148,14 @@ export class GraphRepository {
|
|
|
1339
1148
|
};
|
|
1340
1149
|
}
|
|
1341
1150
|
const created = await this.client.createTask(graphListId, graphTask);
|
|
1342
|
-
|
|
1343
|
-
const numericId = hashStringToNumber(graphId);
|
|
1344
|
-
this.idCache.tasks.set(numericId, { taskListId: graphListId, taskId: graphId });
|
|
1345
|
-
return numericId;
|
|
1151
|
+
return this.mintAliasComposite('task', { taskListId: graphListId, taskId: created.id });
|
|
1346
1152
|
}
|
|
1347
1153
|
/**
|
|
1348
1154
|
* Updates an existing task.
|
|
1349
|
-
*
|
|
1350
|
-
* Looks up the Graph task info from idCache.tasks, then calls
|
|
1351
|
-
* client.updateTask(). Throws if the task is not cached.
|
|
1352
1155
|
*/
|
|
1353
1156
|
async updateTaskAsync(taskId, updates) {
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1356
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1357
|
-
await this.client.updateTask(taskInfo.taskListId, taskInfo.taskId, updates);
|
|
1157
|
+
const { taskListId, taskId: gTaskId } = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1158
|
+
await this.client.updateTask(taskListId, gTaskId, updates);
|
|
1358
1159
|
}
|
|
1359
1160
|
/**
|
|
1360
1161
|
* Marks a task as completed.
|
|
@@ -1373,64 +1174,42 @@ export class GraphRepository {
|
|
|
1373
1174
|
}
|
|
1374
1175
|
/**
|
|
1375
1176
|
* Deletes a task.
|
|
1376
|
-
*
|
|
1377
|
-
* Looks up the Graph task info from idCache.tasks, calls
|
|
1378
|
-
* client.deleteTask(), and removes the entry from idCache.
|
|
1379
|
-
* Throws if the task is not cached.
|
|
1380
1177
|
*/
|
|
1381
1178
|
async deleteTaskAsync(taskId) {
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1385
|
-
await this.client.deleteTask(taskInfo.taskListId, taskInfo.taskId);
|
|
1386
|
-
this.idCache.tasks.delete(taskId);
|
|
1179
|
+
const { taskListId, taskId: gTaskId } = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1180
|
+
await this.client.deleteTask(taskListId, gTaskId);
|
|
1387
1181
|
}
|
|
1388
1182
|
/**
|
|
1389
1183
|
* Creates a new task list.
|
|
1390
|
-
*
|
|
1391
|
-
* Calls client.createTaskList(), caches the resulting ID in
|
|
1392
|
-
* idCache.taskLists, and returns a numeric ID.
|
|
1393
1184
|
*/
|
|
1394
1185
|
async createTaskListAsync(displayName) {
|
|
1395
1186
|
const created = await this.client.createTaskList(displayName);
|
|
1396
|
-
|
|
1397
|
-
const numericId = hashStringToNumber(graphId);
|
|
1398
|
-
this.idCache.taskLists.set(numericId, graphId);
|
|
1399
|
-
return numericId;
|
|
1187
|
+
return this.mintAlias('taskList', created.id);
|
|
1400
1188
|
}
|
|
1401
1189
|
/**
|
|
1402
1190
|
* Renames a task list.
|
|
1403
1191
|
*/
|
|
1404
1192
|
async renameTaskListAsync(listId, name) {
|
|
1405
|
-
const graphId = this.
|
|
1406
|
-
if (graphId == null)
|
|
1407
|
-
throw new Error(`Task list ID ${listId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1193
|
+
const graphId = this.toGraphId(listId, 'taskList');
|
|
1408
1194
|
await this.client.updateTaskList(graphId, { displayName: name });
|
|
1409
1195
|
}
|
|
1410
1196
|
/**
|
|
1411
1197
|
* Deletes a task list.
|
|
1412
1198
|
*/
|
|
1413
1199
|
async deleteTaskListAsync(listId) {
|
|
1414
|
-
const graphId = this.
|
|
1415
|
-
if (graphId == null)
|
|
1416
|
-
throw new Error(`Task list ID ${listId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1200
|
+
const graphId = this.toGraphId(listId, 'taskList');
|
|
1417
1201
|
await this.client.deleteTaskList(graphId);
|
|
1418
|
-
this.idCache.taskLists.delete(listId);
|
|
1419
1202
|
}
|
|
1420
1203
|
// ===========================================================================
|
|
1421
1204
|
// Checklist Items
|
|
1422
1205
|
// ===========================================================================
|
|
1423
1206
|
async listChecklistItemsAsync(taskId) {
|
|
1424
|
-
const taskInfo = this.
|
|
1425
|
-
if (taskInfo == null)
|
|
1426
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1207
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1427
1208
|
const items = await this.client.listChecklistItems(taskInfo.taskListId, taskInfo.taskId);
|
|
1428
1209
|
return items.map((item) => {
|
|
1429
1210
|
const graphId = item.id;
|
|
1430
|
-
const numericId = hashStringToNumber(graphId);
|
|
1431
|
-
this.idCache.checklistItems.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId });
|
|
1432
1211
|
return {
|
|
1433
|
-
id:
|
|
1212
|
+
id: this.mintAliasComposite('checklistItem', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId }),
|
|
1434
1213
|
displayName: item.displayName ?? '',
|
|
1435
1214
|
isChecked: item.isChecked ?? false,
|
|
1436
1215
|
createdDateTime: item.createdDateTime ?? '',
|
|
@@ -1438,19 +1217,12 @@ export class GraphRepository {
|
|
|
1438
1217
|
});
|
|
1439
1218
|
}
|
|
1440
1219
|
async createChecklistItemAsync(taskId, displayName, isChecked = false) {
|
|
1441
|
-
const taskInfo = this.
|
|
1442
|
-
if (taskInfo == null)
|
|
1443
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1220
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1444
1221
|
const item = await this.client.createChecklistItem(taskInfo.taskListId, taskInfo.taskId, displayName, isChecked);
|
|
1445
|
-
|
|
1446
|
-
const numericId = hashStringToNumber(graphId);
|
|
1447
|
-
this.idCache.checklistItems.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId });
|
|
1448
|
-
return numericId;
|
|
1222
|
+
return this.mintAliasComposite('checklistItem', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: item.id });
|
|
1449
1223
|
}
|
|
1450
1224
|
async updateChecklistItemAsync(checklistItemId, updates) {
|
|
1451
|
-
const cached = this.
|
|
1452
|
-
if (cached == null)
|
|
1453
|
-
throw new Error(`Checklist item ID ${checklistItemId} not found in cache. Try listing checklist items first.`);
|
|
1225
|
+
const cached = this.toGraphParts(checklistItemId, 'checklistItem', ['taskListId', 'taskId', 'checklistItemId']);
|
|
1454
1226
|
const graphUpdates = {};
|
|
1455
1227
|
if (updates.displayName != null)
|
|
1456
1228
|
graphUpdates['displayName'] = updates.displayName;
|
|
@@ -1459,26 +1231,19 @@ export class GraphRepository {
|
|
|
1459
1231
|
await this.client.updateChecklistItem(cached.taskListId, cached.taskId, cached.checklistItemId, graphUpdates);
|
|
1460
1232
|
}
|
|
1461
1233
|
async deleteChecklistItemAsync(checklistItemId) {
|
|
1462
|
-
const cached = this.
|
|
1463
|
-
if (cached == null)
|
|
1464
|
-
throw new Error(`Checklist item ID ${checklistItemId} not found in cache. Try listing checklist items first.`);
|
|
1234
|
+
const cached = this.toGraphParts(checklistItemId, 'checklistItem', ['taskListId', 'taskId', 'checklistItemId']);
|
|
1465
1235
|
await this.client.deleteChecklistItem(cached.taskListId, cached.taskId, cached.checklistItemId);
|
|
1466
|
-
this.idCache.checklistItems.delete(checklistItemId);
|
|
1467
1236
|
}
|
|
1468
1237
|
// ===========================================================================
|
|
1469
1238
|
// Linked Resources
|
|
1470
1239
|
// ===========================================================================
|
|
1471
1240
|
async listLinkedResourcesAsync(taskId) {
|
|
1472
|
-
const taskInfo = this.
|
|
1473
|
-
if (taskInfo == null)
|
|
1474
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1241
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1475
1242
|
const items = await this.client.listLinkedResources(taskInfo.taskListId, taskInfo.taskId);
|
|
1476
1243
|
return items.map((item) => {
|
|
1477
1244
|
const graphId = item.id;
|
|
1478
|
-
const numericId = hashStringToNumber(graphId);
|
|
1479
|
-
this.idCache.linkedResources.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId });
|
|
1480
1245
|
return {
|
|
1481
|
-
id:
|
|
1246
|
+
id: this.mintAliasComposite('linkedResource', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId }),
|
|
1482
1247
|
webUrl: item.webUrl ?? '',
|
|
1483
1248
|
applicationName: item.applicationName ?? '',
|
|
1484
1249
|
displayName: item.displayName ?? '',
|
|
@@ -1486,36 +1251,24 @@ export class GraphRepository {
|
|
|
1486
1251
|
});
|
|
1487
1252
|
}
|
|
1488
1253
|
async createLinkedResourceAsync(taskId, webUrl, applicationName, displayName) {
|
|
1489
|
-
const taskInfo = this.
|
|
1490
|
-
if (taskInfo == null)
|
|
1491
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1254
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1492
1255
|
const item = await this.client.createLinkedResource(taskInfo.taskListId, taskInfo.taskId, webUrl, applicationName, displayName);
|
|
1493
|
-
|
|
1494
|
-
const numericId = hashStringToNumber(graphId);
|
|
1495
|
-
this.idCache.linkedResources.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId });
|
|
1496
|
-
return numericId;
|
|
1256
|
+
return this.mintAliasComposite('linkedResource', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: item.id });
|
|
1497
1257
|
}
|
|
1498
1258
|
async deleteLinkedResourceAsync(linkedResourceId) {
|
|
1499
|
-
const cached = this.
|
|
1500
|
-
if (cached == null)
|
|
1501
|
-
throw new Error(`Linked resource ID ${linkedResourceId} not found in cache. Try listing linked resources first.`);
|
|
1259
|
+
const cached = this.toGraphParts(linkedResourceId, 'linkedResource', ['taskListId', 'taskId', 'linkedResourceId']);
|
|
1502
1260
|
await this.client.deleteLinkedResource(cached.taskListId, cached.taskId, cached.linkedResourceId);
|
|
1503
|
-
this.idCache.linkedResources.delete(linkedResourceId);
|
|
1504
1261
|
}
|
|
1505
1262
|
// ===========================================================================
|
|
1506
1263
|
// Task Attachments
|
|
1507
1264
|
// ===========================================================================
|
|
1508
1265
|
async listTaskAttachmentsAsync(taskId) {
|
|
1509
|
-
const taskInfo = this.
|
|
1510
|
-
if (taskInfo == null)
|
|
1511
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1266
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1512
1267
|
const items = await this.client.listTaskAttachments(taskInfo.taskListId, taskInfo.taskId);
|
|
1513
1268
|
return items.map((item) => {
|
|
1514
1269
|
const graphId = item.id;
|
|
1515
|
-
const numericId = hashStringToNumber(graphId);
|
|
1516
|
-
this.idCache.taskAttachments.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId });
|
|
1517
1270
|
return {
|
|
1518
|
-
id:
|
|
1271
|
+
id: this.mintAliasComposite('taskAttachment', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId }),
|
|
1519
1272
|
name: item['name'] ?? '',
|
|
1520
1273
|
size: item.size ?? 0,
|
|
1521
1274
|
contentType: item.contentType ?? '',
|
|
@@ -1523,21 +1276,13 @@ export class GraphRepository {
|
|
|
1523
1276
|
});
|
|
1524
1277
|
}
|
|
1525
1278
|
async createTaskAttachmentAsync(taskId, name, contentBytes, contentType) {
|
|
1526
|
-
const taskInfo = this.
|
|
1527
|
-
if (taskInfo == null)
|
|
1528
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1279
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1529
1280
|
const item = await this.client.createTaskAttachment(taskInfo.taskListId, taskInfo.taskId, name, contentBytes, contentType);
|
|
1530
|
-
|
|
1531
|
-
const numericId = hashStringToNumber(graphId);
|
|
1532
|
-
this.idCache.taskAttachments.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId });
|
|
1533
|
-
return numericId;
|
|
1281
|
+
return this.mintAliasComposite('taskAttachment', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: item.id });
|
|
1534
1282
|
}
|
|
1535
1283
|
async deleteTaskAttachmentAsync(taskAttachmentId) {
|
|
1536
|
-
const cached = this.
|
|
1537
|
-
if (cached == null)
|
|
1538
|
-
throw new Error(`Task attachment ID ${taskAttachmentId} not found in cache. Try listing task attachments first.`);
|
|
1284
|
+
const cached = this.toGraphParts(taskAttachmentId, 'taskAttachment', ['taskListId', 'taskId', 'attachmentId']);
|
|
1539
1285
|
await this.client.deleteTaskAttachment(cached.taskListId, cached.taskId, cached.attachmentId);
|
|
1540
|
-
this.idCache.taskAttachments.delete(taskAttachmentId);
|
|
1541
1286
|
}
|
|
1542
1287
|
// ===========================================================================
|
|
1543
1288
|
// Mail Rules (Async)
|
|
@@ -1549,10 +1294,8 @@ export class GraphRepository {
|
|
|
1549
1294
|
const rules = await this.client.listMailRules();
|
|
1550
1295
|
return rules.map((rule) => {
|
|
1551
1296
|
const graphId = rule.id;
|
|
1552
|
-
const numericId = hashStringToNumber(graphId);
|
|
1553
|
-
this.idCache.rules.set(numericId, graphId);
|
|
1554
1297
|
return {
|
|
1555
|
-
id:
|
|
1298
|
+
id: this.mintAlias('mailRule', graphId),
|
|
1556
1299
|
displayName: rule.displayName ?? '',
|
|
1557
1300
|
sequence: rule.sequence ?? 0,
|
|
1558
1301
|
isEnabled: rule.isEnabled ?? true,
|
|
@@ -1566,20 +1309,14 @@ export class GraphRepository {
|
|
|
1566
1309
|
*/
|
|
1567
1310
|
async createMailRuleAsync(rule) {
|
|
1568
1311
|
const created = await this.client.createMailRule(rule);
|
|
1569
|
-
|
|
1570
|
-
const numericId = hashStringToNumber(graphId);
|
|
1571
|
-
this.idCache.rules.set(numericId, graphId);
|
|
1572
|
-
return numericId;
|
|
1312
|
+
return this.mintAlias('mailRule', created.id);
|
|
1573
1313
|
}
|
|
1574
1314
|
/**
|
|
1575
1315
|
* Deletes an inbox mail rule.
|
|
1576
1316
|
*/
|
|
1577
1317
|
async deleteMailRuleAsync(ruleId) {
|
|
1578
|
-
const graphId = this.
|
|
1579
|
-
if (graphId == null)
|
|
1580
|
-
throw new Error(`Rule ID ${ruleId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1318
|
+
const graphId = this.toGraphId(ruleId, 'mailRule');
|
|
1581
1319
|
await this.client.deleteMailRule(graphId);
|
|
1582
|
-
this.idCache.rules.delete(ruleId);
|
|
1583
1320
|
}
|
|
1584
1321
|
// ===========================================================================
|
|
1585
1322
|
// Automatic Replies (Out of Office)
|
|
@@ -1659,10 +1396,8 @@ export class GraphRepository {
|
|
|
1659
1396
|
const categories = await this.client.listMasterCategories();
|
|
1660
1397
|
return categories.map((cat) => {
|
|
1661
1398
|
const graphId = cat.id;
|
|
1662
|
-
const numericId = hashStringToNumber(graphId);
|
|
1663
|
-
this.idCache.categories.set(numericId, graphId);
|
|
1664
1399
|
return {
|
|
1665
|
-
id:
|
|
1400
|
+
id: this.mintAlias('category', graphId),
|
|
1666
1401
|
name: cat.displayName ?? '',
|
|
1667
1402
|
color: cat.color ?? 'none',
|
|
1668
1403
|
};
|
|
@@ -1673,20 +1408,14 @@ export class GraphRepository {
|
|
|
1673
1408
|
*/
|
|
1674
1409
|
async createCategoryAsync(name, color) {
|
|
1675
1410
|
const created = await this.client.createMasterCategory(name, color);
|
|
1676
|
-
|
|
1677
|
-
const numericId = hashStringToNumber(graphId);
|
|
1678
|
-
this.idCache.categories.set(numericId, graphId);
|
|
1679
|
-
return numericId;
|
|
1411
|
+
return this.mintAlias('category', created.id);
|
|
1680
1412
|
}
|
|
1681
1413
|
/**
|
|
1682
1414
|
* Deletes a master category.
|
|
1683
1415
|
*/
|
|
1684
1416
|
async deleteCategoryAsync(categoryId) {
|
|
1685
|
-
const graphId = this.
|
|
1686
|
-
if (graphId == null)
|
|
1687
|
-
throw new Error(`Category ID ${categoryId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1417
|
+
const graphId = this.toGraphId(categoryId, 'category');
|
|
1688
1418
|
await this.client.deleteMasterCategory(graphId);
|
|
1689
|
-
this.idCache.categories.delete(categoryId);
|
|
1690
1419
|
}
|
|
1691
1420
|
// ===========================================================================
|
|
1692
1421
|
// Focused Inbox Overrides
|
|
@@ -1698,10 +1427,8 @@ export class GraphRepository {
|
|
|
1698
1427
|
const overrides = await this.client.listFocusedOverrides();
|
|
1699
1428
|
return overrides.map((o) => {
|
|
1700
1429
|
const graphId = o.id;
|
|
1701
|
-
const numericId = hashStringToNumber(graphId);
|
|
1702
|
-
this.idCache.focusedOverrides.set(numericId, graphId);
|
|
1703
1430
|
return {
|
|
1704
|
-
id:
|
|
1431
|
+
id: this.mintAlias('focusedOverride', graphId),
|
|
1705
1432
|
senderAddress: o.senderEmailAddress?.address ?? '',
|
|
1706
1433
|
classifyAs: o.classifyAs ?? '',
|
|
1707
1434
|
};
|
|
@@ -1712,20 +1439,14 @@ export class GraphRepository {
|
|
|
1712
1439
|
*/
|
|
1713
1440
|
async createFocusedOverrideAsync(senderAddress, classifyAs) {
|
|
1714
1441
|
const created = await this.client.createFocusedOverride(senderAddress, classifyAs);
|
|
1715
|
-
|
|
1716
|
-
const numericId = hashStringToNumber(graphId);
|
|
1717
|
-
this.idCache.focusedOverrides.set(numericId, graphId);
|
|
1718
|
-
return numericId;
|
|
1442
|
+
return this.mintAlias('focusedOverride', created.id);
|
|
1719
1443
|
}
|
|
1720
1444
|
/**
|
|
1721
1445
|
* Deletes a focused inbox override.
|
|
1722
1446
|
*/
|
|
1723
1447
|
async deleteFocusedOverrideAsync(overrideId) {
|
|
1724
|
-
const graphId = this.
|
|
1725
|
-
if (graphId == null)
|
|
1726
|
-
throw new Error(`Focused override ID ${overrideId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1448
|
+
const graphId = this.toGraphId(overrideId, 'focusedOverride');
|
|
1727
1449
|
await this.client.deleteFocusedOverride(graphId);
|
|
1728
|
-
this.idCache.focusedOverrides.delete(overrideId);
|
|
1729
1450
|
}
|
|
1730
1451
|
// ===========================================================================
|
|
1731
1452
|
// Message Headers & MIME
|
|
@@ -1734,21 +1455,17 @@ export class GraphRepository {
|
|
|
1734
1455
|
* Gets internet message headers for an email.
|
|
1735
1456
|
*/
|
|
1736
1457
|
async getMessageHeadersAsync(emailId) {
|
|
1737
|
-
const graphId = this.
|
|
1738
|
-
if (graphId == null)
|
|
1739
|
-
throw new Error(`Email ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1458
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
1740
1459
|
return await this.client.getMessageHeaders(graphId);
|
|
1741
1460
|
}
|
|
1742
1461
|
/**
|
|
1743
1462
|
* Gets the MIME content of a message and saves it as an .eml file.
|
|
1744
1463
|
*/
|
|
1745
1464
|
async getMessageMimeAsync(emailId) {
|
|
1746
|
-
const graphId = this.
|
|
1747
|
-
if (graphId == null)
|
|
1748
|
-
throw new Error(`Email ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1465
|
+
const graphId = this.toGraphId(emailId, 'message');
|
|
1749
1466
|
const mime = await this.client.getMessageMime(graphId);
|
|
1750
1467
|
const downloadDir = getDownloadDir();
|
|
1751
|
-
const filePath = path.join(downloadDir, `email-${
|
|
1468
|
+
const filePath = path.join(downloadDir, `email-${createHash('sha1').update(graphId).digest('hex').slice(0, 16)}.eml`);
|
|
1752
1469
|
fs.writeFileSync(filePath, mime, 'utf-8');
|
|
1753
1470
|
return { filePath };
|
|
1754
1471
|
}
|
|
@@ -1779,15 +1496,16 @@ export class GraphRepository {
|
|
|
1779
1496
|
// ===========================================================================
|
|
1780
1497
|
/**
|
|
1781
1498
|
* Lists all calendar groups.
|
|
1499
|
+
*
|
|
1500
|
+
* Orphan entity: no Graph URL takes a calendar-group id as a path segment,
|
|
1501
|
+
* so this returns the raw Graph id string rather than minting a token.
|
|
1782
1502
|
*/
|
|
1783
1503
|
async listCalendarGroupsAsync() {
|
|
1784
1504
|
const groups = await this.client.listCalendarGroups();
|
|
1785
1505
|
return groups.map((group) => {
|
|
1786
1506
|
const graphId = group.id;
|
|
1787
|
-
const numericId = hashStringToNumber(graphId);
|
|
1788
|
-
this.idCache.calendarGroups.set(numericId, graphId);
|
|
1789
1507
|
return {
|
|
1790
|
-
id:
|
|
1508
|
+
id: graphId,
|
|
1791
1509
|
name: group.name ?? '',
|
|
1792
1510
|
classId: group.classId?.toString() ?? '',
|
|
1793
1511
|
};
|
|
@@ -1798,10 +1516,7 @@ export class GraphRepository {
|
|
|
1798
1516
|
*/
|
|
1799
1517
|
async createCalendarGroupAsync(name) {
|
|
1800
1518
|
const created = await this.client.createCalendarGroup(name);
|
|
1801
|
-
|
|
1802
|
-
const numericId = hashStringToNumber(graphId);
|
|
1803
|
-
this.idCache.calendarGroups.set(numericId, graphId);
|
|
1804
|
-
return numericId;
|
|
1519
|
+
return created.id;
|
|
1805
1520
|
}
|
|
1806
1521
|
// ===========================================================================
|
|
1807
1522
|
// Calendar Permissions
|
|
@@ -1810,17 +1525,12 @@ export class GraphRepository {
|
|
|
1810
1525
|
* Lists all permissions for a calendar.
|
|
1811
1526
|
*/
|
|
1812
1527
|
async listCalendarPermissionsAsync(calendarId) {
|
|
1813
|
-
const graphCalendarId = this.
|
|
1814
|
-
if (graphCalendarId == null) {
|
|
1815
|
-
throw new Error(`Calendar ID ${calendarId} not found in cache. Please call list_calendars first.`);
|
|
1816
|
-
}
|
|
1528
|
+
const graphCalendarId = this.toGraphId(calendarId, 'folder');
|
|
1817
1529
|
const permissions = await this.client.listCalendarPermissions(graphCalendarId);
|
|
1818
1530
|
return permissions.map((perm) => {
|
|
1819
1531
|
const graphPermId = perm.id;
|
|
1820
|
-
const numericId = hashStringToNumber(graphPermId);
|
|
1821
|
-
this.idCache.calendarPermissions.set(numericId, { calendarId: graphCalendarId, permissionId: graphPermId });
|
|
1822
1532
|
return {
|
|
1823
|
-
id:
|
|
1533
|
+
id: this.mintAliasComposite('calendarPermission', { calendarId: graphCalendarId, permissionId: graphPermId }),
|
|
1824
1534
|
emailAddress: perm.emailAddress?.address ?? '',
|
|
1825
1535
|
role: perm.role ?? 'none',
|
|
1826
1536
|
isRemovable: perm.isRemovable ?? false,
|
|
@@ -1832,10 +1542,7 @@ export class GraphRepository {
|
|
|
1832
1542
|
* Creates a calendar permission (shares a calendar with someone).
|
|
1833
1543
|
*/
|
|
1834
1544
|
async createCalendarPermissionAsync(calendarId, email, role) {
|
|
1835
|
-
const graphCalendarId = this.
|
|
1836
|
-
if (graphCalendarId == null) {
|
|
1837
|
-
throw new Error(`Calendar ID ${calendarId} not found in cache. Please call list_calendars first.`);
|
|
1838
|
-
}
|
|
1545
|
+
const graphCalendarId = this.toGraphId(calendarId, 'folder');
|
|
1839
1546
|
const permission = await this.client.createCalendarPermission(graphCalendarId, {
|
|
1840
1547
|
emailAddress: {
|
|
1841
1548
|
address: email,
|
|
@@ -1843,21 +1550,14 @@ export class GraphRepository {
|
|
|
1843
1550
|
},
|
|
1844
1551
|
role,
|
|
1845
1552
|
});
|
|
1846
|
-
|
|
1847
|
-
const numericId = hashStringToNumber(graphPermId);
|
|
1848
|
-
this.idCache.calendarPermissions.set(numericId, { calendarId: graphCalendarId, permissionId: graphPermId });
|
|
1849
|
-
return numericId;
|
|
1553
|
+
return this.mintAliasComposite('calendarPermission', { calendarId: graphCalendarId, permissionId: permission.id });
|
|
1850
1554
|
}
|
|
1851
1555
|
/**
|
|
1852
1556
|
* Deletes a calendar permission.
|
|
1853
1557
|
*/
|
|
1854
1558
|
async deleteCalendarPermissionAsync(permissionId) {
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
throw new Error(`Calendar permission ID ${permissionId} not found in cache. Please call list_calendar_permissions first.`);
|
|
1858
|
-
}
|
|
1859
|
-
await this.client.deleteCalendarPermission(cached.calendarId, cached.permissionId);
|
|
1860
|
-
this.idCache.calendarPermissions.delete(permissionId);
|
|
1559
|
+
const { calendarId, permissionId: permId } = this.toGraphParts(permissionId, 'calendarPermission', ['calendarId', 'permissionId']);
|
|
1560
|
+
await this.client.deleteCalendarPermission(calendarId, permId);
|
|
1861
1561
|
}
|
|
1862
1562
|
// ===========================================================================
|
|
1863
1563
|
// Room Lists & Rooms
|
|
@@ -1892,80 +1592,61 @@ export class GraphRepository {
|
|
|
1892
1592
|
const teams = await this.client.listJoinedTeams();
|
|
1893
1593
|
return teams.map((team) => {
|
|
1894
1594
|
const graphId = team.id;
|
|
1895
|
-
|
|
1896
|
-
this.idCache.teams.set(numericId, graphId);
|
|
1897
|
-
return { id: numericId, name: team.displayName ?? '', description: team.description ?? '' };
|
|
1595
|
+
return { id: this.mintAlias('team', graphId), name: team.displayName ?? '', description: team.description ?? '' };
|
|
1898
1596
|
});
|
|
1899
1597
|
}
|
|
1900
1598
|
/**
|
|
1901
1599
|
* Lists all channels in a team with cached numeric IDs.
|
|
1902
1600
|
*/
|
|
1903
1601
|
async listChannelsAsync(teamId) {
|
|
1904
|
-
const graphTeamId = this.
|
|
1905
|
-
if (graphTeamId == null)
|
|
1906
|
-
throw new Error(`Team ID ${teamId} not found in cache. Try listing teams first.`);
|
|
1602
|
+
const graphTeamId = await this.resolveTeamId(teamId);
|
|
1907
1603
|
const channels = await this.client.listChannels(graphTeamId);
|
|
1908
1604
|
return channels.map((ch) => {
|
|
1909
1605
|
const graphId = ch.id;
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
return { id: numericId, name: ch.displayName ?? '', description: ch.description ?? '', membershipType: ch.membershipType ?? 'standard' };
|
|
1606
|
+
const id = this.mintAliasComposite('channel', { teamId: graphTeamId, channelId: graphId });
|
|
1607
|
+
return { id, name: ch.displayName ?? '', description: ch.description ?? '', membershipType: ch.membershipType ?? 'standard' };
|
|
1913
1608
|
});
|
|
1914
1609
|
}
|
|
1915
1610
|
/**
|
|
1916
|
-
* Gets a specific channel by
|
|
1611
|
+
* Gets a specific channel by durable cn_ token.
|
|
1917
1612
|
*/
|
|
1918
1613
|
async getChannelAsync(channelId) {
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
const ch = await this.client.getChannel(cached.teamId, cached.channelId);
|
|
1923
|
-
return { id: channelId, name: ch.displayName ?? '', description: ch.description ?? '', membershipType: ch.membershipType ?? 'standard', webUrl: ch.webUrl ?? '' };
|
|
1614
|
+
const { teamId, channelId: graphChannelId } = this.toGraphParts(channelId, 'channel', ['teamId', 'channelId']);
|
|
1615
|
+
const ch = await this.client.getChannel(teamId, graphChannelId);
|
|
1616
|
+
return { id: String(channelId), name: ch.displayName ?? '', description: ch.description ?? '', membershipType: ch.membershipType ?? 'standard', webUrl: ch.webUrl ?? '' };
|
|
1924
1617
|
}
|
|
1925
1618
|
/**
|
|
1926
1619
|
* Creates a new channel in a team.
|
|
1927
1620
|
*/
|
|
1928
1621
|
async createChannelAsync(teamId, name, description) {
|
|
1929
|
-
const graphTeamId = this.
|
|
1930
|
-
if (graphTeamId == null)
|
|
1931
|
-
throw new Error(`Team ID ${teamId} not found in cache. Try listing teams first.`);
|
|
1622
|
+
const graphTeamId = await this.resolveTeamId(teamId);
|
|
1932
1623
|
const ch = await this.client.createChannel(graphTeamId, name, description);
|
|
1933
|
-
|
|
1934
|
-
const numericId = hashStringToNumber(graphId);
|
|
1935
|
-
this.idCache.channels.set(numericId, { teamId: graphTeamId, channelId: graphId });
|
|
1936
|
-
return numericId;
|
|
1624
|
+
return this.mintAliasComposite('channel', { teamId: graphTeamId, channelId: ch.id });
|
|
1937
1625
|
}
|
|
1938
1626
|
/**
|
|
1939
1627
|
* Updates a channel's properties.
|
|
1940
1628
|
*/
|
|
1941
1629
|
async updateChannelAsync(channelId, updates) {
|
|
1942
|
-
const
|
|
1943
|
-
if (cached == null)
|
|
1944
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1630
|
+
const { teamId, channelId: graphChannelId } = this.toGraphParts(channelId, 'channel', ['teamId', 'channelId']);
|
|
1945
1631
|
const graphUpdates = {};
|
|
1946
1632
|
if (updates.name != null)
|
|
1947
1633
|
graphUpdates['displayName'] = updates.name;
|
|
1948
1634
|
if (updates.description != null)
|
|
1949
1635
|
graphUpdates['description'] = updates.description;
|
|
1950
|
-
await this.client.updateChannel(
|
|
1636
|
+
await this.client.updateChannel(teamId, graphChannelId, graphUpdates);
|
|
1951
1637
|
}
|
|
1952
1638
|
/**
|
|
1953
1639
|
* Deletes a channel.
|
|
1954
1640
|
*/
|
|
1955
1641
|
async deleteChannelAsync(channelId) {
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1959
|
-
await this.client.deleteChannel(cached.teamId, cached.channelId);
|
|
1960
|
-
this.idCache.channels.delete(channelId);
|
|
1642
|
+
const { teamId, channelId: graphChannelId } = this.toGraphParts(channelId, 'channel', ['teamId', 'channelId']);
|
|
1643
|
+
await this.client.deleteChannel(teamId, graphChannelId);
|
|
1961
1644
|
}
|
|
1962
1645
|
/**
|
|
1963
1646
|
* Lists members of a team.
|
|
1964
1647
|
*/
|
|
1965
1648
|
async listTeamMembersAsync(teamId) {
|
|
1966
|
-
const graphTeamId = this.
|
|
1967
|
-
if (graphTeamId == null)
|
|
1968
|
-
throw new Error(`Team ID ${teamId} not found in cache. Try listing teams first.`);
|
|
1649
|
+
const graphTeamId = await this.resolveTeamId(teamId);
|
|
1969
1650
|
const members = await this.client.listTeamMembers(graphTeamId);
|
|
1970
1651
|
return members.map((m) => ({
|
|
1971
1652
|
id: m.id ?? '',
|
|
@@ -1981,16 +1662,12 @@ export class GraphRepository {
|
|
|
1981
1662
|
* Lists recent messages in a channel.
|
|
1982
1663
|
*/
|
|
1983
1664
|
async listChannelMessagesAsync(channelId, limit = 25) {
|
|
1984
|
-
const
|
|
1985
|
-
|
|
1986
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1987
|
-
const messages = await this.client.listChannelMessages(cached.teamId, cached.channelId, limit);
|
|
1665
|
+
const { teamId, channelId: graphChannelId } = this.toGraphParts(channelId, 'channel', ['teamId', 'channelId']);
|
|
1666
|
+
const messages = await this.client.listChannelMessages(teamId, graphChannelId, limit);
|
|
1988
1667
|
return messages.map((msg) => {
|
|
1989
1668
|
const graphId = msg.id;
|
|
1990
|
-
const numericId = hashStringToNumber(graphId);
|
|
1991
|
-
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
1992
1669
|
return {
|
|
1993
|
-
id:
|
|
1670
|
+
id: this.mintAliasComposite('channelMessage', { teamId, channelId: graphChannelId, messageId: graphId }),
|
|
1994
1671
|
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
1995
1672
|
senderEmail: msg.from?.user?.email ?? '',
|
|
1996
1673
|
bodyPreview: msg.body?.content?.substring(0, 200) ?? '',
|
|
@@ -2004,18 +1681,21 @@ export class GraphRepository {
|
|
|
2004
1681
|
* Gets a specific channel message with its replies.
|
|
2005
1682
|
*/
|
|
2006
1683
|
async getChannelMessageAsync(messageId) {
|
|
2007
|
-
const
|
|
2008
|
-
if (cached == null)
|
|
2009
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
1684
|
+
const { teamId, channelId, messageId: graphMessageId } = this.toGraphParts(messageId, 'channelMessage', ['teamId', 'channelId', 'messageId']);
|
|
2010
1685
|
const [msg, repliesRaw] = await Promise.all([
|
|
2011
|
-
this.client.getChannelMessage(
|
|
2012
|
-
this.client.listChannelMessageReplies(
|
|
1686
|
+
this.client.getChannelMessage(teamId, channelId, graphMessageId),
|
|
1687
|
+
this.client.listChannelMessageReplies(teamId, channelId, graphMessageId),
|
|
2013
1688
|
]);
|
|
2014
1689
|
const replies = repliesRaw.map((r) => {
|
|
2015
1690
|
const rGraphId = r.id;
|
|
2016
|
-
|
|
1691
|
+
// Reply ids are display-only: a reply is a child entity addressed at
|
|
1692
|
+
// /messages/{parentId}/replies/{replyId}, but this xm_ token (like the
|
|
1693
|
+
// pre-migration numeric id before it) carries only {teamId, channelId,
|
|
1694
|
+
// messageId}, so feeding it back to a message op resolves then 404s. No
|
|
1695
|
+
// tool consumes a reply id — react/reply take the parent message id.
|
|
1696
|
+
// Reply-level operations are tracked in issue #50.
|
|
2017
1697
|
return {
|
|
2018
|
-
id:
|
|
1698
|
+
id: this.mintAliasComposite('channelMessage', { teamId, channelId, messageId: rGraphId }),
|
|
2019
1699
|
senderName: r.from?.user?.displayName ?? r.from?.application?.displayName ?? '',
|
|
2020
1700
|
senderEmail: r.from?.user?.email ?? '',
|
|
2021
1701
|
bodyContent: r.body?.content ?? '',
|
|
@@ -2024,7 +1704,7 @@ export class GraphRepository {
|
|
|
2024
1704
|
};
|
|
2025
1705
|
});
|
|
2026
1706
|
return {
|
|
2027
|
-
id: messageId,
|
|
1707
|
+
id: String(messageId),
|
|
2028
1708
|
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2029
1709
|
senderEmail: msg.from?.user?.email ?? '',
|
|
2030
1710
|
bodyContent: msg.body?.content ?? '',
|
|
@@ -2037,27 +1717,18 @@ export class GraphRepository {
|
|
|
2037
1717
|
* Sends a new message to a channel.
|
|
2038
1718
|
*/
|
|
2039
1719
|
async sendChannelMessageAsync(channelId, body, contentType = 'html') {
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
2043
|
-
const msg = await this.client.sendChannelMessage(cached.teamId, cached.channelId, body, contentType);
|
|
1720
|
+
const { teamId, channelId: graphChannelId } = this.toGraphParts(channelId, 'channel', ['teamId', 'channelId']);
|
|
1721
|
+
const msg = await this.client.sendChannelMessage(teamId, graphChannelId, body, contentType);
|
|
2044
1722
|
const graphId = msg.id;
|
|
2045
|
-
|
|
2046
|
-
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2047
|
-
return numericId;
|
|
1723
|
+
return this.mintAliasComposite('channelMessage', { teamId, channelId: graphChannelId, messageId: graphId });
|
|
2048
1724
|
}
|
|
2049
1725
|
/**
|
|
2050
1726
|
* Replies to a channel message.
|
|
2051
1727
|
*/
|
|
2052
1728
|
async replyToChannelMessageAsync(messageId, body, contentType = 'html') {
|
|
2053
|
-
const
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
const reply = await this.client.replyToChannelMessage(cached.teamId, cached.channelId, cached.messageId, body, contentType);
|
|
2057
|
-
const graphId = reply.id;
|
|
2058
|
-
const numericId = hashStringToNumber(graphId);
|
|
2059
|
-
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2060
|
-
return numericId;
|
|
1729
|
+
const { teamId, channelId, messageId: graphMessageId } = this.toGraphParts(messageId, 'channelMessage', ['teamId', 'channelId', 'messageId']);
|
|
1730
|
+
const reply = await this.client.replyToChannelMessage(teamId, channelId, graphMessageId, body, contentType);
|
|
1731
|
+
return this.mintAliasComposite('channelMessage', { teamId, channelId, messageId: reply.id });
|
|
2061
1732
|
}
|
|
2062
1733
|
// ===========================================================================
|
|
2063
1734
|
// Chats
|
|
@@ -2066,14 +1737,12 @@ export class GraphRepository {
|
|
|
2066
1737
|
const chats = await this.client.listChats(limit);
|
|
2067
1738
|
return chats.map((chat) => {
|
|
2068
1739
|
const graphId = chat.id;
|
|
2069
|
-
const numericId = hashStringToNumber(graphId);
|
|
2070
|
-
this.idCache.chats.set(numericId, graphId);
|
|
2071
1740
|
const chatRecord = chat;
|
|
2072
1741
|
const preview = chatRecord.lastMessagePreview;
|
|
2073
1742
|
const previewBody = preview?.body;
|
|
2074
1743
|
const previewContent = previewBody?.content?.substring(0, 200) ?? '';
|
|
2075
1744
|
return {
|
|
2076
|
-
id:
|
|
1745
|
+
id: this.mintAlias('chat', graphId),
|
|
2077
1746
|
topic: chat.topic ?? '',
|
|
2078
1747
|
chatType: chat.chatType ?? 'oneOnOne',
|
|
2079
1748
|
lastMessagePreview: previewContent,
|
|
@@ -2082,12 +1751,10 @@ export class GraphRepository {
|
|
|
2082
1751
|
});
|
|
2083
1752
|
}
|
|
2084
1753
|
async getChatAsync(chatId) {
|
|
2085
|
-
const graphId = this.
|
|
2086
|
-
if (graphId == null)
|
|
2087
|
-
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
1754
|
+
const graphId = await this.resolveChatId(chatId);
|
|
2088
1755
|
const chat = await this.client.getChat(graphId);
|
|
2089
1756
|
return {
|
|
2090
|
-
id: chatId,
|
|
1757
|
+
id: String(chatId),
|
|
2091
1758
|
topic: chat.topic ?? '',
|
|
2092
1759
|
chatType: chat.chatType ?? 'oneOnOne',
|
|
2093
1760
|
createdDateTime: chat.createdDateTime ?? '',
|
|
@@ -2095,16 +1762,12 @@ export class GraphRepository {
|
|
|
2095
1762
|
};
|
|
2096
1763
|
}
|
|
2097
1764
|
async listChatMessagesAsync(chatId, limit = 25) {
|
|
2098
|
-
const graphChatId = this.
|
|
2099
|
-
if (graphChatId == null)
|
|
2100
|
-
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
1765
|
+
const graphChatId = await this.resolveChatId(chatId);
|
|
2101
1766
|
const messages = await this.client.listChatMessages(graphChatId, limit);
|
|
2102
1767
|
return messages.map((msg) => {
|
|
2103
1768
|
const graphId = msg.id;
|
|
2104
|
-
const numericId = hashStringToNumber(graphId);
|
|
2105
|
-
this.idCache.chatMessages.set(numericId, { chatId: graphChatId, messageId: graphId });
|
|
2106
1769
|
return {
|
|
2107
|
-
id:
|
|
1770
|
+
id: this.mintAliasComposite('chatMessage', { chatId: graphChatId, messageId: graphId }),
|
|
2108
1771
|
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2109
1772
|
senderEmail: msg.from?.user?.email ?? '',
|
|
2110
1773
|
bodyPreview: msg.body?.content?.substring(0, 200) ?? '',
|
|
@@ -2115,14 +1778,10 @@ export class GraphRepository {
|
|
|
2115
1778
|
});
|
|
2116
1779
|
}
|
|
2117
1780
|
async sendChatMessageAsync(chatId, body, contentType = 'html') {
|
|
2118
|
-
const graphChatId = this.
|
|
2119
|
-
if (graphChatId == null)
|
|
2120
|
-
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
1781
|
+
const graphChatId = await this.resolveChatId(chatId);
|
|
2121
1782
|
const msg = await this.client.sendChatMessage(graphChatId, body, contentType);
|
|
2122
1783
|
const graphId = msg.id;
|
|
2123
|
-
|
|
2124
|
-
this.idCache.chatMessages.set(numericId, { chatId: graphChatId, messageId: graphId });
|
|
2125
|
-
return numericId;
|
|
1784
|
+
return this.mintAliasComposite('chatMessage', { chatId: graphChatId, messageId: graphId });
|
|
2126
1785
|
}
|
|
2127
1786
|
// ===========================================================================
|
|
2128
1787
|
// Message Reactions
|
|
@@ -2141,52 +1800,38 @@ export class GraphRepository {
|
|
|
2141
1800
|
});
|
|
2142
1801
|
};
|
|
2143
1802
|
if (messageType === 'channel') {
|
|
2144
|
-
const
|
|
2145
|
-
|
|
2146
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2147
|
-
const msg = await this.client.getChannelMessage(cached.teamId, cached.channelId, cached.messageId);
|
|
1803
|
+
const { teamId, channelId, messageId: graphMessageId } = this.toGraphParts(messageId, 'channelMessage', ['teamId', 'channelId', 'messageId']);
|
|
1804
|
+
const msg = await this.client.getChannelMessage(teamId, channelId, graphMessageId);
|
|
2148
1805
|
return mapReactions(msg);
|
|
2149
1806
|
}
|
|
2150
1807
|
else {
|
|
2151
|
-
const
|
|
2152
|
-
|
|
2153
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2154
|
-
const msg = await this.client.getChatMessage(cached.chatId, cached.messageId);
|
|
1808
|
+
const { chatId, messageId: graphMessageId } = this.toGraphParts(messageId, 'chatMessage', ['chatId', 'messageId']);
|
|
1809
|
+
const msg = await this.client.getChatMessage(chatId, graphMessageId);
|
|
2155
1810
|
return mapReactions(msg);
|
|
2156
1811
|
}
|
|
2157
1812
|
}
|
|
2158
1813
|
async addMessageReactionAsync(messageId, messageType, reactionType) {
|
|
2159
1814
|
if (messageType === 'channel') {
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2163
|
-
await this.client.setChannelMessageReaction(cached.teamId, cached.channelId, cached.messageId, reactionType);
|
|
1815
|
+
const { teamId, channelId, messageId: graphMessageId } = this.toGraphParts(messageId, 'channelMessage', ['teamId', 'channelId', 'messageId']);
|
|
1816
|
+
await this.client.setChannelMessageReaction(teamId, channelId, graphMessageId, reactionType);
|
|
2164
1817
|
}
|
|
2165
1818
|
else {
|
|
2166
|
-
const
|
|
2167
|
-
|
|
2168
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2169
|
-
await this.client.setChatMessageReaction(cached.chatId, cached.messageId, reactionType);
|
|
1819
|
+
const { chatId, messageId: graphMessageId } = this.toGraphParts(messageId, 'chatMessage', ['chatId', 'messageId']);
|
|
1820
|
+
await this.client.setChatMessageReaction(chatId, graphMessageId, reactionType);
|
|
2170
1821
|
}
|
|
2171
1822
|
}
|
|
2172
1823
|
async removeMessageReactionAsync(messageId, messageType, reactionType) {
|
|
2173
1824
|
if (messageType === 'channel') {
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2176
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2177
|
-
await this.client.unsetChannelMessageReaction(cached.teamId, cached.channelId, cached.messageId, reactionType);
|
|
1825
|
+
const { teamId, channelId, messageId: graphMessageId } = this.toGraphParts(messageId, 'channelMessage', ['teamId', 'channelId', 'messageId']);
|
|
1826
|
+
await this.client.unsetChannelMessageReaction(teamId, channelId, graphMessageId, reactionType);
|
|
2178
1827
|
}
|
|
2179
1828
|
else {
|
|
2180
|
-
const
|
|
2181
|
-
|
|
2182
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2183
|
-
await this.client.unsetChatMessageReaction(cached.chatId, cached.messageId, reactionType);
|
|
1829
|
+
const { chatId, messageId: graphMessageId } = this.toGraphParts(messageId, 'chatMessage', ['chatId', 'messageId']);
|
|
1830
|
+
await this.client.unsetChatMessageReaction(chatId, graphMessageId, reactionType);
|
|
2184
1831
|
}
|
|
2185
1832
|
}
|
|
2186
1833
|
async listChatMembersAsync(chatId) {
|
|
2187
|
-
const graphChatId = this.
|
|
2188
|
-
if (graphChatId == null)
|
|
2189
|
-
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
1834
|
+
const graphChatId = await this.resolveChatId(chatId);
|
|
2190
1835
|
const members = await this.client.listChatMembers(graphChatId);
|
|
2191
1836
|
return members.map((m) => ({
|
|
2192
1837
|
displayName: m.displayName ?? '',
|
|
@@ -2198,17 +1843,14 @@ export class GraphRepository {
|
|
|
2198
1843
|
// Planner Plans
|
|
2199
1844
|
// ===========================================================================
|
|
2200
1845
|
/**
|
|
2201
|
-
* Lists all plans the current user has.
|
|
1846
|
+
* Lists all plans the current user has, minting durable pl_ tokens.
|
|
2202
1847
|
*/
|
|
2203
1848
|
async listPlansAsync() {
|
|
2204
1849
|
const plans = await this.client.listPlans();
|
|
2205
1850
|
return plans.map((plan) => {
|
|
2206
1851
|
const graphId = plan.id;
|
|
2207
|
-
const numericId = hashStringToNumber(graphId);
|
|
2208
|
-
const etag = plan['@odata.etag'] ?? '';
|
|
2209
|
-
this.idCache.plans.set(numericId, { planId: graphId, etag });
|
|
2210
1852
|
return {
|
|
2211
|
-
id:
|
|
1853
|
+
id: this.mintAlias('plan', graphId),
|
|
2212
1854
|
title: plan.title ?? '',
|
|
2213
1855
|
owner: plan.owner ?? '',
|
|
2214
1856
|
createdDateTime: plan.createdDateTime ?? '',
|
|
@@ -2216,21 +1858,17 @@ export class GraphRepository {
|
|
|
2216
1858
|
});
|
|
2217
1859
|
}
|
|
2218
1860
|
/**
|
|
2219
|
-
* Gets a specific plan
|
|
1861
|
+
* Gets a specific plan.
|
|
2220
1862
|
*/
|
|
2221
1863
|
async getPlanAsync(planId) {
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2225
|
-
const plan = await this.client.getPlan(cached.planId);
|
|
2226
|
-
const etag = plan['@odata.etag'] ?? '';
|
|
2227
|
-
this.idCache.plans.set(planId, { planId: cached.planId, etag });
|
|
1864
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1865
|
+
const plan = await this.client.getPlan(graphPlanId);
|
|
2228
1866
|
return {
|
|
2229
|
-
id: planId,
|
|
1867
|
+
id: String(planId),
|
|
2230
1868
|
title: plan.title ?? '',
|
|
2231
1869
|
owner: plan.owner ?? '',
|
|
2232
1870
|
createdDateTime: plan.createdDateTime ?? '',
|
|
2233
|
-
etag,
|
|
1871
|
+
etag: this.extractEtag(plan),
|
|
2234
1872
|
};
|
|
2235
1873
|
}
|
|
2236
1874
|
/**
|
|
@@ -2238,46 +1876,34 @@ export class GraphRepository {
|
|
|
2238
1876
|
*/
|
|
2239
1877
|
async createPlanAsync(title, groupId) {
|
|
2240
1878
|
const plan = await this.client.createPlan(title, groupId);
|
|
2241
|
-
|
|
2242
|
-
const numericId = hashStringToNumber(graphId);
|
|
2243
|
-
const etag = plan['@odata.etag'] ?? '';
|
|
2244
|
-
this.idCache.plans.set(numericId, { planId: graphId, etag });
|
|
2245
|
-
return numericId;
|
|
1879
|
+
return this.mintAlias('plan', plan.id);
|
|
2246
1880
|
}
|
|
2247
1881
|
/**
|
|
2248
|
-
* Updates a plan (
|
|
1882
|
+
* Updates a plan (U5b-5: fetches a fresh etag immediately before the write —
|
|
1883
|
+
* etags are never cached across calls).
|
|
2249
1884
|
*/
|
|
2250
1885
|
async updatePlanAsync(planId, updates) {
|
|
2251
|
-
const
|
|
2252
|
-
if (cached == null)
|
|
2253
|
-
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
1886
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
2254
1887
|
const graphUpdates = {};
|
|
2255
1888
|
if (updates.title != null)
|
|
2256
1889
|
graphUpdates['title'] = updates.title;
|
|
2257
|
-
|
|
2258
|
-
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2259
|
-
this.idCache.plans.set(planId, { planId: cached.planId, etag: newEtag });
|
|
1890
|
+
await this.withFreshEtag(async () => this.extractEtag(await this.client.getPlan(graphPlanId)), (etag) => this.client.updatePlan(graphPlanId, graphUpdates, etag));
|
|
2260
1891
|
}
|
|
2261
1892
|
// ===========================================================================
|
|
2262
1893
|
// Planner Buckets
|
|
2263
1894
|
// ===========================================================================
|
|
2264
1895
|
/**
|
|
2265
|
-
* Lists all buckets in a plan.
|
|
1896
|
+
* Lists all buckets in a plan, minting durable pb_ tokens.
|
|
2266
1897
|
*/
|
|
2267
1898
|
async listBucketsAsync(planId) {
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2271
|
-
const buckets = await this.client.listBuckets(cached.planId);
|
|
1899
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1900
|
+
const buckets = await this.client.listBuckets(graphPlanId);
|
|
2272
1901
|
return buckets.map((bucket) => {
|
|
2273
1902
|
const graphId = bucket.id;
|
|
2274
|
-
const numericId = hashStringToNumber(graphId);
|
|
2275
|
-
const etag = bucket['@odata.etag'] ?? '';
|
|
2276
|
-
this.idCache.plannerBuckets.set(numericId, { planId: cached.planId, bucketId: graphId, etag });
|
|
2277
1903
|
return {
|
|
2278
|
-
id:
|
|
1904
|
+
id: this.mintAlias('plannerBucket', graphId),
|
|
2279
1905
|
name: bucket.name ?? '',
|
|
2280
|
-
planId,
|
|
1906
|
+
planId: String(planId),
|
|
2281
1907
|
orderHint: bucket.orderHint ?? '',
|
|
2282
1908
|
};
|
|
2283
1909
|
});
|
|
@@ -2286,65 +1912,42 @@ export class GraphRepository {
|
|
|
2286
1912
|
* Creates a new bucket in a plan.
|
|
2287
1913
|
*/
|
|
2288
1914
|
async createBucketAsync(planId, name) {
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
const bucket = await this.client.createBucket(cached.planId, name);
|
|
2293
|
-
const graphId = bucket.id;
|
|
2294
|
-
const numericId = hashStringToNumber(graphId);
|
|
2295
|
-
const etag = bucket['@odata.etag'] ?? '';
|
|
2296
|
-
this.idCache.plannerBuckets.set(numericId, { planId: cached.planId, bucketId: graphId, etag });
|
|
2297
|
-
return numericId;
|
|
1915
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1916
|
+
const bucket = await this.client.createBucket(graphPlanId, name);
|
|
1917
|
+
return this.mintAlias('plannerBucket', bucket.id);
|
|
2298
1918
|
}
|
|
2299
1919
|
/**
|
|
2300
|
-
* Updates a bucket (
|
|
1920
|
+
* Updates a bucket (U5b-5: fetches a fresh etag immediately before the write).
|
|
2301
1921
|
*/
|
|
2302
1922
|
async updateBucketAsync(bucketId, updates) {
|
|
2303
|
-
const
|
|
2304
|
-
if (cached == null)
|
|
2305
|
-
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
1923
|
+
const graphBucketId = this.toGraphId(bucketId, 'plannerBucket');
|
|
2306
1924
|
const graphUpdates = {};
|
|
2307
1925
|
if (updates.name != null)
|
|
2308
1926
|
graphUpdates['name'] = updates.name;
|
|
2309
|
-
|
|
2310
|
-
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2311
|
-
this.idCache.plannerBuckets.set(bucketId, { planId: cached.planId, bucketId: cached.bucketId, etag: newEtag });
|
|
1927
|
+
await this.withFreshEtag(async () => this.extractEtag(await this.client.getBucket(graphBucketId)), (etag) => this.client.updateBucket(graphBucketId, graphUpdates, etag));
|
|
2312
1928
|
}
|
|
2313
1929
|
/**
|
|
2314
|
-
* Deletes a bucket (
|
|
1930
|
+
* Deletes a bucket (U5b-5: fetches a fresh etag immediately before the write).
|
|
2315
1931
|
*/
|
|
2316
1932
|
async deleteBucketAsync(bucketId) {
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2319
|
-
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2320
|
-
await this.client.deleteBucket(cached.bucketId, cached.etag);
|
|
2321
|
-
this.idCache.plannerBuckets.delete(bucketId);
|
|
1933
|
+
const graphBucketId = this.toGraphId(bucketId, 'plannerBucket');
|
|
1934
|
+
await this.withFreshEtag(async () => this.extractEtag(await this.client.getBucket(graphBucketId)), (etag) => this.client.deleteBucket(graphBucketId, etag));
|
|
2322
1935
|
}
|
|
2323
1936
|
// ===========================================================================
|
|
2324
1937
|
// Planner Tasks
|
|
2325
1938
|
// ===========================================================================
|
|
2326
1939
|
/**
|
|
2327
|
-
* Lists all tasks in a plan.
|
|
1940
|
+
* Lists all tasks in a plan, minting durable pt_ tokens.
|
|
2328
1941
|
*/
|
|
2329
1942
|
async listPlannerTasksAsync(planId) {
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2333
|
-
const tasks = await this.client.listPlannerTasks(cached.planId);
|
|
1943
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1944
|
+
const tasks = await this.client.listPlannerTasks(graphPlanId);
|
|
2334
1945
|
return tasks.map((task) => {
|
|
2335
1946
|
const graphId = task.id;
|
|
2336
|
-
const numericId = hashStringToNumber(graphId);
|
|
2337
|
-
const etag = task['@odata.etag'] ?? '';
|
|
2338
|
-
this.idCache.plannerTasks.set(numericId, { taskId: graphId, etag });
|
|
2339
|
-
// Resolve bucket numeric ID if present
|
|
2340
|
-
let bucketNumericId = null;
|
|
2341
|
-
if (task.bucketId != null) {
|
|
2342
|
-
bucketNumericId = hashStringToNumber(task.bucketId);
|
|
2343
|
-
}
|
|
2344
1947
|
return {
|
|
2345
|
-
id:
|
|
1948
|
+
id: this.mintAlias('plannerTask', graphId),
|
|
2346
1949
|
title: task.title ?? '',
|
|
2347
|
-
bucketId:
|
|
1950
|
+
bucketId: task.bucketId != null ? this.mintAlias('plannerBucket', task.bucketId) : null,
|
|
2348
1951
|
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
2349
1952
|
percentComplete: task.percentComplete ?? 0,
|
|
2350
1953
|
priority: task.priority ?? 5,
|
|
@@ -2355,23 +1958,39 @@ export class GraphRepository {
|
|
|
2355
1958
|
});
|
|
2356
1959
|
}
|
|
2357
1960
|
/**
|
|
2358
|
-
*
|
|
1961
|
+
* Lists all Planner tasks assigned to the signed-in user, across every plan
|
|
1962
|
+
* (`GET /me/planner/tasks`). Unlike the plan-scoped list, each task carries its
|
|
1963
|
+
* own `planId`; the plan/bucket/task durable tokens are minted so follow-up
|
|
1964
|
+
* get/update calls resolve without a re-list.
|
|
1965
|
+
*/
|
|
1966
|
+
async listMyPlannerTasksAsync() {
|
|
1967
|
+
const tasks = await this.client.listMyPlannerTasks();
|
|
1968
|
+
return tasks.map((task) => {
|
|
1969
|
+
const graphId = task.id;
|
|
1970
|
+
return {
|
|
1971
|
+
id: this.mintAlias('plannerTask', graphId),
|
|
1972
|
+
title: task.title ?? '',
|
|
1973
|
+
planId: task.planId != null ? this.mintAlias('plan', task.planId) : '',
|
|
1974
|
+
bucketId: task.bucketId != null ? this.mintAlias('plannerBucket', task.bucketId) : null,
|
|
1975
|
+
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
1976
|
+
percentComplete: task.percentComplete ?? 0,
|
|
1977
|
+
priority: task.priority ?? 5,
|
|
1978
|
+
startDateTime: task.startDateTime ?? '',
|
|
1979
|
+
dueDateTime: task.dueDateTime ?? '',
|
|
1980
|
+
createdDateTime: task.createdDateTime ?? '',
|
|
1981
|
+
};
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Gets a specific planner task.
|
|
2359
1986
|
*/
|
|
2360
1987
|
async getPlannerTaskAsync(taskId) {
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2363
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2364
|
-
const task = await this.client.getPlannerTask(cached.taskId);
|
|
2365
|
-
const etag = task['@odata.etag'] ?? '';
|
|
2366
|
-
this.idCache.plannerTasks.set(taskId, { taskId: cached.taskId, etag });
|
|
2367
|
-
let bucketNumericId = null;
|
|
2368
|
-
if (task.bucketId != null) {
|
|
2369
|
-
bucketNumericId = hashStringToNumber(task.bucketId);
|
|
2370
|
-
}
|
|
1988
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
1989
|
+
const task = await this.client.getPlannerTask(gTaskId);
|
|
2371
1990
|
return {
|
|
2372
|
-
id: taskId,
|
|
1991
|
+
id: String(taskId),
|
|
2373
1992
|
title: task.title ?? '',
|
|
2374
|
-
bucketId:
|
|
1993
|
+
bucketId: task.bucketId != null ? this.mintAlias('plannerBucket', task.bucketId) : null,
|
|
2375
1994
|
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
2376
1995
|
percentComplete: task.percentComplete ?? 0,
|
|
2377
1996
|
priority: task.priority ?? 5,
|
|
@@ -2380,22 +1999,17 @@ export class GraphRepository {
|
|
|
2380
1999
|
createdDateTime: task.createdDateTime ?? '',
|
|
2381
2000
|
conversationThreadId: task.conversationThreadId ?? '',
|
|
2382
2001
|
orderHint: task.orderHint ?? '',
|
|
2383
|
-
etag,
|
|
2002
|
+
etag: this.extractEtag(task),
|
|
2384
2003
|
};
|
|
2385
2004
|
}
|
|
2386
2005
|
/**
|
|
2387
2006
|
* Creates a new planner task.
|
|
2388
2007
|
*/
|
|
2389
2008
|
async createPlannerTaskAsync(planId, title, bucketId, assignments, priority, startDate, dueDate) {
|
|
2390
|
-
const
|
|
2391
|
-
|
|
2392
|
-
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2393
|
-
const body = { planId: cachedPlan.planId, title };
|
|
2009
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
2010
|
+
const body = { planId: graphPlanId, title };
|
|
2394
2011
|
if (bucketId != null) {
|
|
2395
|
-
|
|
2396
|
-
if (cachedBucket == null)
|
|
2397
|
-
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2398
|
-
body.bucketId = cachedBucket.bucketId;
|
|
2012
|
+
body.bucketId = this.toGraphId(bucketId, 'plannerBucket');
|
|
2399
2013
|
}
|
|
2400
2014
|
if (assignments != null)
|
|
2401
2015
|
body.assignments = assignments;
|
|
@@ -2406,28 +2020,18 @@ export class GraphRepository {
|
|
|
2406
2020
|
if (dueDate != null)
|
|
2407
2021
|
body.dueDateTime = dueDate;
|
|
2408
2022
|
const task = await this.client.createPlannerTask(body);
|
|
2409
|
-
|
|
2410
|
-
const numericId = hashStringToNumber(graphId);
|
|
2411
|
-
const etag = task['@odata.etag'] ?? '';
|
|
2412
|
-
this.idCache.plannerTasks.set(numericId, { taskId: graphId, etag });
|
|
2413
|
-
return numericId;
|
|
2023
|
+
return this.mintAlias('plannerTask', task.id);
|
|
2414
2024
|
}
|
|
2415
2025
|
/**
|
|
2416
|
-
* Updates a planner task (
|
|
2026
|
+
* Updates a planner task (U5b-5: fetches a fresh etag immediately before the write).
|
|
2417
2027
|
*/
|
|
2418
2028
|
async updatePlannerTaskAsync(taskId, updates) {
|
|
2419
|
-
const
|
|
2420
|
-
if (cached == null)
|
|
2421
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2029
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
2422
2030
|
const graphUpdates = {};
|
|
2423
2031
|
if (updates.title != null)
|
|
2424
2032
|
graphUpdates['title'] = updates.title;
|
|
2425
|
-
if (updates.bucketId != null)
|
|
2426
|
-
|
|
2427
|
-
if (cachedBucket == null)
|
|
2428
|
-
throw new Error(`Bucket ID ${updates.bucketId} not found in cache. Try listing buckets first.`);
|
|
2429
|
-
graphUpdates['bucketId'] = cachedBucket.bucketId;
|
|
2430
|
-
}
|
|
2033
|
+
if (updates.bucketId != null)
|
|
2034
|
+
graphUpdates['bucketId'] = this.toGraphId(updates.bucketId, 'plannerBucket');
|
|
2431
2035
|
if (updates.percentComplete != null)
|
|
2432
2036
|
graphUpdates['percentComplete'] = updates.percentComplete;
|
|
2433
2037
|
if (updates.priority != null)
|
|
@@ -2438,39 +2042,31 @@ export class GraphRepository {
|
|
|
2438
2042
|
graphUpdates['dueDateTime'] = updates.dueDate;
|
|
2439
2043
|
if (updates.assignments != null)
|
|
2440
2044
|
graphUpdates['assignments'] = updates.assignments;
|
|
2441
|
-
|
|
2442
|
-
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2443
|
-
this.idCache.plannerTasks.set(taskId, { taskId: cached.taskId, etag: newEtag });
|
|
2045
|
+
await this.withFreshEtag(async () => this.extractEtag(await this.client.getPlannerTask(gTaskId)), (etag) => this.client.updatePlannerTask(gTaskId, graphUpdates, etag));
|
|
2444
2046
|
}
|
|
2445
2047
|
/**
|
|
2446
|
-
* Deletes a planner task (
|
|
2048
|
+
* Deletes a planner task (U5b-5: fetches a fresh etag immediately before the write).
|
|
2447
2049
|
*/
|
|
2448
2050
|
async deletePlannerTaskAsync(taskId) {
|
|
2449
|
-
const
|
|
2450
|
-
|
|
2451
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2452
|
-
await this.client.deletePlannerTask(cached.taskId, cached.etag);
|
|
2453
|
-
this.idCache.plannerTasks.delete(taskId);
|
|
2051
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
2052
|
+
await this.withFreshEtag(async () => this.extractEtag(await this.client.getPlannerTask(gTaskId)), (etag) => this.client.deletePlannerTask(gTaskId, etag));
|
|
2454
2053
|
}
|
|
2455
2054
|
// ===========================================================================
|
|
2456
2055
|
// Planner Task Details
|
|
2457
2056
|
// ===========================================================================
|
|
2458
2057
|
/**
|
|
2459
|
-
* Gets details for a planner task (description, checklist, references).
|
|
2058
|
+
* Gets details for a planner task (description, checklist, references). Task
|
|
2059
|
+
* details piggyback the pt_ task token — they have no id of their own.
|
|
2460
2060
|
*/
|
|
2461
2061
|
async getPlannerTaskDetailsAsync(taskId) {
|
|
2462
|
-
const
|
|
2463
|
-
|
|
2464
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2465
|
-
const details = await this.client.getPlannerTaskDetails(cached.taskId);
|
|
2466
|
-
const etag = details['@odata.etag'] ?? '';
|
|
2467
|
-
this.idCache.plannerTaskDetails.set(taskId, { taskId: cached.taskId, etag });
|
|
2062
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
2063
|
+
const details = await this.client.getPlannerTaskDetails(gTaskId);
|
|
2468
2064
|
return {
|
|
2469
|
-
id: taskId,
|
|
2065
|
+
id: String(taskId),
|
|
2470
2066
|
description: details.description ?? '',
|
|
2471
2067
|
checklist: details.checklist ?? {},
|
|
2472
2068
|
references: details.references ?? {},
|
|
2473
|
-
etag,
|
|
2069
|
+
etag: this.extractEtag(details),
|
|
2474
2070
|
};
|
|
2475
2071
|
}
|
|
2476
2072
|
/**
|
|
@@ -2483,17 +2079,15 @@ export class GraphRepository {
|
|
|
2483
2079
|
* have `details` set to `undefined`.
|
|
2484
2080
|
*/
|
|
2485
2081
|
async listPlannerTasksWithDetailsAsync(planId) {
|
|
2486
|
-
// Step 1: List all tasks (
|
|
2082
|
+
// Step 1: List all tasks (mints pt_ tokens, registered in the alias store)
|
|
2487
2083
|
const tasks = await this.listPlannerTasksAsync(planId);
|
|
2488
2084
|
if (tasks.length === 0)
|
|
2489
2085
|
return [];
|
|
2490
2086
|
// Step 2: Build batch requests for each task's details
|
|
2491
2087
|
const batchRequests = tasks.map((task) => {
|
|
2492
|
-
const
|
|
2493
|
-
// cached is guaranteed to exist since listPlannerTasksAsync just populated it
|
|
2494
|
-
const graphTaskId = cached.taskId;
|
|
2088
|
+
const graphTaskId = this.toGraphId(task.id, 'plannerTask');
|
|
2495
2089
|
return {
|
|
2496
|
-
id:
|
|
2090
|
+
id: task.id,
|
|
2497
2091
|
method: 'GET',
|
|
2498
2092
|
url: `/planner/tasks/${graphTaskId}/details`,
|
|
2499
2093
|
};
|
|
@@ -2502,15 +2096,10 @@ export class GraphRepository {
|
|
|
2502
2096
|
const batchResults = await this.client.batchRequests(batchRequests);
|
|
2503
2097
|
// Step 4: Merge details into tasks
|
|
2504
2098
|
return tasks.map((task) => {
|
|
2505
|
-
const result = batchResults.get(
|
|
2099
|
+
const result = batchResults.get(task.id);
|
|
2506
2100
|
if (result != null && result.status >= 200 && result.status < 300) {
|
|
2507
2101
|
const detailBody = result.body;
|
|
2508
2102
|
const etag = result.headers?.ETag ?? result.headers?.etag ?? '';
|
|
2509
|
-
// Cache the task detail ETag for later updates
|
|
2510
|
-
const cachedTask = this.idCache.plannerTasks.get(task.id);
|
|
2511
|
-
if (cachedTask != null) {
|
|
2512
|
-
this.idCache.plannerTaskDetails.set(task.id, { taskId: cachedTask.taskId, etag });
|
|
2513
|
-
}
|
|
2514
2103
|
return {
|
|
2515
2104
|
...task,
|
|
2516
2105
|
details: {
|
|
@@ -2526,15 +2115,12 @@ export class GraphRepository {
|
|
|
2526
2115
|
});
|
|
2527
2116
|
}
|
|
2528
2117
|
/**
|
|
2529
|
-
* Updates details for a planner task (
|
|
2118
|
+
* Updates details for a planner task (U5b-5: fetches a fresh etag immediately
|
|
2119
|
+
* before the write — the task details etag is independent of the task's own
|
|
2120
|
+
* etag).
|
|
2530
2121
|
*/
|
|
2531
2122
|
async updatePlannerTaskDetailsAsync(taskId, updates) {
|
|
2532
|
-
const
|
|
2533
|
-
if (cachedTask == null)
|
|
2534
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2535
|
-
const cachedDetails = this.idCache.plannerTaskDetails.get(taskId);
|
|
2536
|
-
if (cachedDetails == null)
|
|
2537
|
-
throw new Error(`Task details ETag for task ${taskId} not found in cache. Call get_planner_task_details first.`);
|
|
2123
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
2538
2124
|
const graphUpdates = {};
|
|
2539
2125
|
if (updates.description != null)
|
|
2540
2126
|
graphUpdates['description'] = updates.description;
|
|
@@ -2542,9 +2128,7 @@ export class GraphRepository {
|
|
|
2542
2128
|
graphUpdates['checklist'] = updates.checklist;
|
|
2543
2129
|
if (updates.references != null)
|
|
2544
2130
|
graphUpdates['references'] = updates.references;
|
|
2545
|
-
|
|
2546
|
-
const newEtag = result['@odata.etag'] ?? cachedDetails.etag;
|
|
2547
|
-
this.idCache.plannerTaskDetails.set(taskId, { taskId: cachedTask.taskId, etag: newEtag });
|
|
2131
|
+
await this.withFreshEtag(async () => this.extractEtag(await this.client.getPlannerTaskDetails(gTaskId)), (etag) => this.client.updatePlannerTaskDetails(gTaskId, graphUpdates, etag));
|
|
2548
2132
|
}
|
|
2549
2133
|
// ===========================================================================
|
|
2550
2134
|
// Planner Visualization Data
|
|
@@ -2569,7 +2153,7 @@ export class GraphRepository {
|
|
|
2569
2153
|
tasks: tasks.map(t => ({
|
|
2570
2154
|
id: t.id,
|
|
2571
2155
|
title: t.title,
|
|
2572
|
-
bucketId: t.bucketId ??
|
|
2156
|
+
bucketId: t.bucketId ?? '',
|
|
2573
2157
|
percentComplete: t.percentComplete,
|
|
2574
2158
|
priority: t.priority,
|
|
2575
2159
|
startDateTime: t.startDateTime || null,
|
|
@@ -2585,10 +2169,8 @@ export class GraphRepository {
|
|
|
2585
2169
|
const meetings = await this.client.listOnlineMeetings(limit ?? 20);
|
|
2586
2170
|
return meetings.map((meeting) => {
|
|
2587
2171
|
const graphId = meeting.id ?? '';
|
|
2588
|
-
const numericId = hashStringToNumber(graphId);
|
|
2589
|
-
this.idCache.onlineMeetings.set(numericId, graphId);
|
|
2590
2172
|
return {
|
|
2591
|
-
id:
|
|
2173
|
+
id: this.mintAlias('onlineMeeting', graphId),
|
|
2592
2174
|
subject: meeting.subject ?? '',
|
|
2593
2175
|
startDateTime: meeting.startDateTime ?? '',
|
|
2594
2176
|
endDateTime: meeting.endDateTime ?? '',
|
|
@@ -2598,103 +2180,96 @@ export class GraphRepository {
|
|
|
2598
2180
|
}
|
|
2599
2181
|
async getOnlineMeetingAsync(meetingId) {
|
|
2600
2182
|
const mapMeeting = (m) => ({
|
|
2601
|
-
id: meetingId,
|
|
2183
|
+
id: String(meetingId),
|
|
2602
2184
|
subject: m.subject ?? '',
|
|
2603
2185
|
startDateTime: m.startDateTime ?? '',
|
|
2604
2186
|
endDateTime: m.endDateTime ?? '',
|
|
2605
2187
|
joinUrl: m.joinWebUrl ?? '',
|
|
2606
2188
|
participants: m.participants ?? null,
|
|
2607
2189
|
});
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2190
|
+
// om_ tokens resolve from the alias store. On a cold miss (never listed this
|
|
2191
|
+
// session, or a lost store) re-list — listOnlineMeetingsAsync deterministically
|
|
2192
|
+
// re-mints and re-stores the same token — then retry the resolve, matching
|
|
2193
|
+
// the resolveTeamId self-heal pattern. A final miss means "not found" (the
|
|
2194
|
+
// documented contract), not an error.
|
|
2195
|
+
let graphId;
|
|
2196
|
+
try {
|
|
2197
|
+
graphId = this.toGraphId(meetingId, 'onlineMeeting');
|
|
2198
|
+
}
|
|
2199
|
+
catch (e) {
|
|
2200
|
+
if (e instanceof IdUnknownError) {
|
|
2201
|
+
await this.listOnlineMeetingsAsync();
|
|
2202
|
+
try {
|
|
2203
|
+
graphId = this.toGraphId(meetingId, 'onlineMeeting');
|
|
2204
|
+
}
|
|
2205
|
+
catch (e2) {
|
|
2206
|
+
if (e2 instanceof IdUnknownError)
|
|
2207
|
+
return undefined;
|
|
2208
|
+
throw e2;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
else {
|
|
2212
|
+
throw e;
|
|
2213
|
+
}
|
|
2617
2214
|
}
|
|
2618
2215
|
const meeting = await this.client.getOnlineMeeting(graphId);
|
|
2619
2216
|
return mapMeeting(meeting);
|
|
2620
2217
|
}
|
|
2621
2218
|
async listMeetingRecordingsAsync(meetingId) {
|
|
2622
|
-
const graphMeetingId = this.
|
|
2623
|
-
if (graphMeetingId == null)
|
|
2624
|
-
throw new Error(`Meeting ID ${meetingId} not found in cache. Try listing online meetings first.`);
|
|
2219
|
+
const graphMeetingId = this.toGraphId(meetingId, 'onlineMeeting');
|
|
2625
2220
|
const recordings = await this.client.listMeetingRecordings(graphMeetingId);
|
|
2626
2221
|
return recordings.map((recording) => {
|
|
2627
2222
|
const graphId = recording.id ?? '';
|
|
2628
|
-
const numericId = hashStringToNumber(graphId);
|
|
2629
|
-
this.idCache.recordings.set(numericId, { meetingId: graphMeetingId, recordingId: graphId });
|
|
2630
2223
|
return {
|
|
2631
|
-
id:
|
|
2224
|
+
id: this.mintAliasComposite('recording', { meetingId: graphMeetingId, recordingId: graphId }),
|
|
2632
2225
|
createdDateTime: recording.createdDateTime ?? '',
|
|
2633
2226
|
recordingContentUrl: recording.recordingContentUrl ?? '',
|
|
2634
2227
|
};
|
|
2635
2228
|
});
|
|
2636
2229
|
}
|
|
2637
2230
|
async downloadMeetingRecordingAsync(recordingId, outputPath) {
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
throw new Error(`Recording ID ${recordingId} not found in cache. Call list_meeting_recordings first.`);
|
|
2641
|
-
const content = await this.client.getMeetingRecordingContent(cached.meetingId, cached.recordingId);
|
|
2231
|
+
const { meetingId, recordingId: recId } = this.toGraphParts(recordingId, 'recording', ['meetingId', 'recordingId']);
|
|
2232
|
+
const content = await this.client.getMeetingRecordingContent(meetingId, recId);
|
|
2642
2233
|
fs.writeFileSync(outputPath, Buffer.from(content));
|
|
2643
2234
|
return outputPath;
|
|
2644
2235
|
}
|
|
2645
2236
|
async listMeetingTranscriptsAsync(meetingId) {
|
|
2646
|
-
const graphMeetingId = this.
|
|
2647
|
-
if (graphMeetingId == null)
|
|
2648
|
-
throw new Error(`Meeting ID ${meetingId} not found in cache. Try listing online meetings first.`);
|
|
2237
|
+
const graphMeetingId = this.toGraphId(meetingId, 'onlineMeeting');
|
|
2649
2238
|
const transcripts = await this.client.listMeetingTranscripts(graphMeetingId);
|
|
2650
2239
|
return transcripts.map((transcript) => {
|
|
2651
2240
|
const graphId = transcript.id ?? '';
|
|
2652
|
-
const numericId = hashStringToNumber(graphId);
|
|
2653
|
-
this.idCache.transcripts.set(numericId, { meetingId: graphMeetingId, transcriptId: graphId });
|
|
2654
2241
|
return {
|
|
2655
|
-
id:
|
|
2242
|
+
id: this.mintAliasComposite('transcript', { meetingId: graphMeetingId, transcriptId: graphId }),
|
|
2656
2243
|
createdDateTime: transcript.createdDateTime ?? '',
|
|
2657
2244
|
contentUrl: transcript.contentUrl ?? '',
|
|
2658
2245
|
};
|
|
2659
2246
|
});
|
|
2660
2247
|
}
|
|
2661
2248
|
async getMeetingTranscriptContentAsync(transcriptId, format) {
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
throw new Error(`Transcript ID ${transcriptId} not found in cache. Call list_meeting_transcripts first.`);
|
|
2665
|
-
return await this.client.getMeetingTranscriptContent(cached.meetingId, cached.transcriptId, format ?? 'text/vtt');
|
|
2249
|
+
const { meetingId, transcriptId: tId } = this.toGraphParts(transcriptId, 'transcript', ['meetingId', 'transcriptId']);
|
|
2250
|
+
return await this.client.getMeetingTranscriptContent(meetingId, tId, format ?? 'text/vtt');
|
|
2666
2251
|
}
|
|
2667
2252
|
// ===========================================================================
|
|
2668
2253
|
// Excel Online (Workbook)
|
|
2669
2254
|
// ===========================================================================
|
|
2670
2255
|
async listWorksheetsAsync(fileId) {
|
|
2671
|
-
const driveItemId = this.
|
|
2672
|
-
if (driveItemId == null)
|
|
2673
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2256
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2674
2257
|
return await this.client.listWorksheets(driveItemId);
|
|
2675
2258
|
}
|
|
2676
2259
|
async getWorksheetRangeAsync(fileId, worksheetName, range) {
|
|
2677
|
-
const driveItemId = this.
|
|
2678
|
-
if (driveItemId == null)
|
|
2679
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2260
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2680
2261
|
return await this.client.getWorksheetRange(driveItemId, worksheetName, range);
|
|
2681
2262
|
}
|
|
2682
2263
|
async getUsedRangeAsync(fileId, worksheetName) {
|
|
2683
|
-
const driveItemId = this.
|
|
2684
|
-
if (driveItemId == null)
|
|
2685
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2264
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2686
2265
|
return await this.client.getUsedRange(driveItemId, worksheetName);
|
|
2687
2266
|
}
|
|
2688
2267
|
async updateWorksheetRangeAsync(fileId, worksheetName, range, values) {
|
|
2689
|
-
const driveItemId = this.
|
|
2690
|
-
if (driveItemId == null)
|
|
2691
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2268
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2692
2269
|
return await this.client.updateWorksheetRange(driveItemId, worksheetName, range, values);
|
|
2693
2270
|
}
|
|
2694
2271
|
async getTableDataAsync(fileId, tableName) {
|
|
2695
|
-
const driveItemId = this.
|
|
2696
|
-
if (driveItemId == null)
|
|
2697
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2272
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2698
2273
|
return await this.client.getTableData(driveItemId, tableName);
|
|
2699
2274
|
}
|
|
2700
2275
|
// OneDrive
|
|
@@ -2703,19 +2278,15 @@ export class GraphRepository {
|
|
|
2703
2278
|
* Lists files/folders in a drive folder (or root).
|
|
2704
2279
|
*/
|
|
2705
2280
|
async listDriveItemsAsync(folderId) {
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
if (graphFolderId == null)
|
|
2710
|
-
throw new Error(`Drive item ID ${folderId} not found in cache. Try listing drive items first.`);
|
|
2711
|
-
}
|
|
2281
|
+
const graphFolderId = folderId != null
|
|
2282
|
+
? this.toGraphId(folderId, 'driveItem')
|
|
2283
|
+
: undefined;
|
|
2712
2284
|
const items = await this.client.listDriveItems(graphFolderId);
|
|
2285
|
+
// Rows carry self-encoding dr_ tokens (minted here) — no cache.
|
|
2713
2286
|
return items.map((item) => {
|
|
2714
2287
|
const itemId = item.id;
|
|
2715
|
-
const numericId = hashStringToNumber(itemId);
|
|
2716
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2717
2288
|
return {
|
|
2718
|
-
id:
|
|
2289
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2719
2290
|
name: item.name ?? '',
|
|
2720
2291
|
size: item.size ?? 0,
|
|
2721
2292
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2731,10 +2302,8 @@ export class GraphRepository {
|
|
|
2731
2302
|
const items = await this.client.searchDriveItems(query, limit);
|
|
2732
2303
|
return items.map((item) => {
|
|
2733
2304
|
const itemId = item.id;
|
|
2734
|
-
const numericId = hashStringToNumber(itemId);
|
|
2735
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2736
2305
|
return {
|
|
2737
|
-
id:
|
|
2306
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2738
2307
|
name: item.name ?? '',
|
|
2739
2308
|
size: item.size ?? 0,
|
|
2740
2309
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2747,15 +2316,13 @@ export class GraphRepository {
|
|
|
2747
2316
|
* Gets metadata for a specific drive item.
|
|
2748
2317
|
*/
|
|
2749
2318
|
async getDriveItemAsync(itemId) {
|
|
2750
|
-
const graphId = this.
|
|
2751
|
-
if (graphId == null)
|
|
2752
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2319
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2753
2320
|
const item = await this.client.getDriveItem(graphId);
|
|
2754
2321
|
const fileObj = item.file;
|
|
2755
2322
|
const createdByObj = item.createdBy;
|
|
2756
2323
|
const createdByUser = createdByObj?.user;
|
|
2757
2324
|
return {
|
|
2758
|
-
id:
|
|
2325
|
+
id: mintSelfEncoded('driveItem', graphId),
|
|
2759
2326
|
name: item.name ?? '',
|
|
2760
2327
|
size: item.size ?? 0,
|
|
2761
2328
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2769,9 +2336,7 @@ export class GraphRepository {
|
|
|
2769
2336
|
* Downloads a drive item to a local file.
|
|
2770
2337
|
*/
|
|
2771
2338
|
async downloadFileAsync(itemId, outputPath) {
|
|
2772
|
-
const graphId = this.
|
|
2773
|
-
if (graphId == null)
|
|
2774
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2339
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2775
2340
|
const content = await this.client.downloadDriveItem(graphId);
|
|
2776
2341
|
const buffer = Buffer.from(content);
|
|
2777
2342
|
fs.writeFileSync(outputPath, buffer);
|
|
@@ -2784,9 +2349,7 @@ export class GraphRepository {
|
|
|
2784
2349
|
const content = fs.readFileSync(localFilePath);
|
|
2785
2350
|
const result = await this.client.uploadDriveItem(parentPath, fileName, content);
|
|
2786
2351
|
const resultId = result.id;
|
|
2787
|
-
|
|
2788
|
-
this.idCache.driveItems.set(numericId, resultId);
|
|
2789
|
-
return numericId;
|
|
2352
|
+
return mintSelfEncoded('driveItem', resultId);
|
|
2790
2353
|
}
|
|
2791
2354
|
/**
|
|
2792
2355
|
* Lists recently accessed drive items.
|
|
@@ -2795,10 +2358,8 @@ export class GraphRepository {
|
|
|
2795
2358
|
const items = await this.client.listRecentDriveItems();
|
|
2796
2359
|
return items.map((item) => {
|
|
2797
2360
|
const itemId = item.id;
|
|
2798
|
-
const numericId = hashStringToNumber(itemId);
|
|
2799
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2800
2361
|
return {
|
|
2801
|
-
id:
|
|
2362
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2802
2363
|
name: item.name ?? '',
|
|
2803
2364
|
size: item.size ?? 0,
|
|
2804
2365
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2814,10 +2375,8 @@ export class GraphRepository {
|
|
|
2814
2375
|
const items = await this.client.listSharedWithMe();
|
|
2815
2376
|
return items.map((item) => {
|
|
2816
2377
|
const itemId = item.id;
|
|
2817
|
-
const numericId = hashStringToNumber(itemId);
|
|
2818
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2819
2378
|
return {
|
|
2820
|
-
id:
|
|
2379
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2821
2380
|
name: item.name ?? '',
|
|
2822
2381
|
size: item.size ?? 0,
|
|
2823
2382
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2830,9 +2389,7 @@ export class GraphRepository {
|
|
|
2830
2389
|
* Creates a sharing link for a drive item.
|
|
2831
2390
|
*/
|
|
2832
2391
|
async createSharingLinkAsync(itemId, type, scope) {
|
|
2833
|
-
const graphId = this.
|
|
2834
|
-
if (graphId == null)
|
|
2835
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2392
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2836
2393
|
const result = await this.client.createSharingLink(graphId, type, scope);
|
|
2837
2394
|
const link = result.link;
|
|
2838
2395
|
return {
|
|
@@ -2845,17 +2402,14 @@ export class GraphRepository {
|
|
|
2845
2402
|
* Deletes a drive item.
|
|
2846
2403
|
*/
|
|
2847
2404
|
async deleteDriveItemAsync(itemId) {
|
|
2848
|
-
const graphId = this.
|
|
2849
|
-
if (graphId == null)
|
|
2850
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2405
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2851
2406
|
await this.client.deleteDriveItem(graphId);
|
|
2852
|
-
this.idCache.driveItems.delete(itemId);
|
|
2853
2407
|
}
|
|
2854
2408
|
// ===========================================================================
|
|
2855
2409
|
// SharePoint Sites & Document Libraries
|
|
2856
2410
|
// ===========================================================================
|
|
2857
2411
|
/**
|
|
2858
|
-
* Lists followed SharePoint sites,
|
|
2412
|
+
* Lists followed SharePoint sites, minting durable si_ tokens.
|
|
2859
2413
|
*/
|
|
2860
2414
|
async listSitesAsync() {
|
|
2861
2415
|
const sites = await this.client.listFollowedSites();
|
|
@@ -2863,10 +2417,8 @@ export class GraphRepository {
|
|
|
2863
2417
|
for (const site of sites) {
|
|
2864
2418
|
const siteId = site.id;
|
|
2865
2419
|
if (siteId != null) {
|
|
2866
|
-
const numericId = hashStringToNumber(siteId);
|
|
2867
|
-
this.idCache.sites.set(numericId, siteId);
|
|
2868
2420
|
result.push({
|
|
2869
|
-
id:
|
|
2421
|
+
id: this.mintAlias('site', siteId),
|
|
2870
2422
|
name: site.name ?? '',
|
|
2871
2423
|
webUrl: site.webUrl ?? '',
|
|
2872
2424
|
displayName: site.displayName ?? '',
|
|
@@ -2876,7 +2428,7 @@ export class GraphRepository {
|
|
|
2876
2428
|
return result;
|
|
2877
2429
|
}
|
|
2878
2430
|
/**
|
|
2879
|
-
* Searches SharePoint sites by keyword,
|
|
2431
|
+
* Searches SharePoint sites by keyword, minting durable si_ tokens.
|
|
2880
2432
|
*/
|
|
2881
2433
|
async searchSitesAsync(query) {
|
|
2882
2434
|
const sites = await this.client.searchSites(query);
|
|
@@ -2884,10 +2436,8 @@ export class GraphRepository {
|
|
|
2884
2436
|
for (const site of sites) {
|
|
2885
2437
|
const siteId = site.id;
|
|
2886
2438
|
if (siteId != null) {
|
|
2887
|
-
const numericId = hashStringToNumber(siteId);
|
|
2888
|
-
this.idCache.sites.set(numericId, siteId);
|
|
2889
2439
|
result.push({
|
|
2890
|
-
id:
|
|
2440
|
+
id: this.mintAlias('site', siteId),
|
|
2891
2441
|
name: site.name ?? '',
|
|
2892
2442
|
webUrl: site.webUrl ?? '',
|
|
2893
2443
|
displayName: site.displayName ?? '',
|
|
@@ -2900,12 +2450,10 @@ export class GraphRepository {
|
|
|
2900
2450
|
* Gets details for a specific SharePoint site.
|
|
2901
2451
|
*/
|
|
2902
2452
|
async getSiteAsync(siteId) {
|
|
2903
|
-
const graphId = this.
|
|
2904
|
-
if (graphId == null)
|
|
2905
|
-
throw new Error(`Site ID ${siteId} not found in cache. Try listing or searching sites first.`);
|
|
2453
|
+
const graphId = this.toGraphId(siteId, 'site');
|
|
2906
2454
|
const site = await this.client.getSite(graphId);
|
|
2907
2455
|
return {
|
|
2908
|
-
id: siteId,
|
|
2456
|
+
id: String(siteId),
|
|
2909
2457
|
name: site.name ?? '',
|
|
2910
2458
|
webUrl: site.webUrl ?? '',
|
|
2911
2459
|
displayName: site.displayName ?? '',
|
|
@@ -2913,21 +2461,17 @@ export class GraphRepository {
|
|
|
2913
2461
|
};
|
|
2914
2462
|
}
|
|
2915
2463
|
/**
|
|
2916
|
-
* Lists document libraries for a SharePoint site,
|
|
2464
|
+
* Lists document libraries for a SharePoint site, minting durable dl_ tokens.
|
|
2917
2465
|
*/
|
|
2918
2466
|
async listDocumentLibrariesAsync(siteId) {
|
|
2919
|
-
const graphSiteId = this.
|
|
2920
|
-
if (graphSiteId == null)
|
|
2921
|
-
throw new Error(`Site ID ${siteId} not found in cache. Try listing or searching sites first.`);
|
|
2467
|
+
const graphSiteId = this.toGraphId(siteId, 'site');
|
|
2922
2468
|
const drives = await this.client.listDocumentLibraries(graphSiteId);
|
|
2923
2469
|
const result = [];
|
|
2924
2470
|
for (const drive of drives) {
|
|
2925
2471
|
const driveId = drive.id;
|
|
2926
2472
|
if (driveId != null) {
|
|
2927
|
-
const numericId = hashStringToNumber(driveId);
|
|
2928
|
-
this.idCache.documentLibraries.set(numericId, { siteId: graphSiteId, driveId });
|
|
2929
2473
|
result.push({
|
|
2930
|
-
id:
|
|
2474
|
+
id: this.mintAliasComposite('documentLibrary', { siteId: graphSiteId, driveId }),
|
|
2931
2475
|
name: drive.name ?? '',
|
|
2932
2476
|
webUrl: drive.webUrl ?? '',
|
|
2933
2477
|
driveType: drive.driveType ?? '',
|
|
@@ -2937,28 +2481,21 @@ export class GraphRepository {
|
|
|
2937
2481
|
return result;
|
|
2938
2482
|
}
|
|
2939
2483
|
/**
|
|
2940
|
-
* Lists items in a document library or folder,
|
|
2484
|
+
* Lists items in a document library or folder, minting durable li_ tokens.
|
|
2941
2485
|
*/
|
|
2942
2486
|
async listLibraryItemsAsync(libraryId, folderId) {
|
|
2943
|
-
const libCached = this.
|
|
2944
|
-
if (libCached == null)
|
|
2945
|
-
throw new Error(`Library ID ${libraryId} not found in cache. Try listing document libraries first.`);
|
|
2487
|
+
const libCached = this.toGraphParts(libraryId, 'documentLibrary', ['siteId', 'driveId']);
|
|
2946
2488
|
let folderItemId;
|
|
2947
2489
|
if (folderId != null) {
|
|
2948
|
-
|
|
2949
|
-
if (folderCached == null)
|
|
2950
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try listing library items first.`);
|
|
2951
|
-
folderItemId = folderCached.itemId;
|
|
2490
|
+
folderItemId = this.toGraphParts(folderId, 'libraryDriveItem', ['driveId', 'itemId']).itemId;
|
|
2952
2491
|
}
|
|
2953
2492
|
const items = await this.client.listLibraryItems(libCached.driveId, folderItemId);
|
|
2954
2493
|
const result = [];
|
|
2955
2494
|
for (const item of items) {
|
|
2956
2495
|
const itemGraphId = item.id;
|
|
2957
2496
|
if (itemGraphId != null) {
|
|
2958
|
-
const numericId = hashStringToNumber(itemGraphId);
|
|
2959
|
-
this.idCache.libraryDriveItems.set(numericId, { driveId: libCached.driveId, itemId: itemGraphId });
|
|
2960
2497
|
result.push({
|
|
2961
|
-
id:
|
|
2498
|
+
id: this.mintAliasComposite('libraryDriveItem', { driveId: libCached.driveId, itemId: itemGraphId }),
|
|
2962
2499
|
name: item.name ?? '',
|
|
2963
2500
|
size: item.size ?? 0,
|
|
2964
2501
|
webUrl: item.webUrl ?? '',
|
|
@@ -2973,9 +2510,7 @@ export class GraphRepository {
|
|
|
2973
2510
|
* Downloads a file from a document library to the specified path.
|
|
2974
2511
|
*/
|
|
2975
2512
|
async downloadLibraryFileAsync(itemId, outputPath) {
|
|
2976
|
-
const cached = this.
|
|
2977
|
-
if (cached == null)
|
|
2978
|
-
throw new Error(`Item ID ${itemId} not found in cache. Try listing library items first.`);
|
|
2513
|
+
const cached = this.toGraphParts(itemId, 'libraryDriveItem', ['driveId', 'itemId']);
|
|
2979
2514
|
const content = await this.client.downloadLibraryFile(cached.driveId, cached.itemId);
|
|
2980
2515
|
const resolvedPath = path.resolve(outputPath);
|
|
2981
2516
|
const dir = path.dirname(resolvedPath);
|
|
@@ -2984,16 +2519,171 @@ export class GraphRepository {
|
|
|
2984
2519
|
return resolvedPath;
|
|
2985
2520
|
}
|
|
2986
2521
|
/**
|
|
2987
|
-
*
|
|
2522
|
+
* Resolves a folder id (durable `fd_` token or raw Graph id) to its Graph id.
|
|
2988
2523
|
*/
|
|
2989
2524
|
getFolderGraphId(folderId) {
|
|
2990
|
-
return this.
|
|
2525
|
+
return this.toGraphId(folderId, 'folder');
|
|
2991
2526
|
}
|
|
2527
|
+
// ===========================================================================
|
|
2528
|
+
// SharePoint Lists
|
|
2529
|
+
// ===========================================================================
|
|
2530
|
+
/**
|
|
2531
|
+
* Lists the SharePoint lists in a site, minting durable sl_ tokens. Each list
|
|
2532
|
+
* token carries the {siteId, listId} tuple its Graph URL needs.
|
|
2533
|
+
*/
|
|
2534
|
+
async listSharePointListsAsync(siteId) {
|
|
2535
|
+
const graphSiteId = this.toGraphId(siteId, 'site');
|
|
2536
|
+
const lists = await this.client.listSharePointLists(graphSiteId);
|
|
2537
|
+
const result = [];
|
|
2538
|
+
for (const list of lists) {
|
|
2539
|
+
const listId = list.id;
|
|
2540
|
+
if (listId != null) {
|
|
2541
|
+
result.push({
|
|
2542
|
+
id: this.mintAliasComposite('sharePointList', { siteId: graphSiteId, listId }),
|
|
2543
|
+
name: list.name ?? '',
|
|
2544
|
+
displayName: list.displayName ?? '',
|
|
2545
|
+
description: list.description ?? '',
|
|
2546
|
+
webUrl: list.webUrl ?? '',
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return result;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Gets a specific SharePoint list, resolving the sl_ token to its tuple.
|
|
2554
|
+
*/
|
|
2555
|
+
async getSharePointListAsync(listId) {
|
|
2556
|
+
const { siteId, listId: graphListId } = this.toGraphParts(listId, 'sharePointList', ['siteId', 'listId']);
|
|
2557
|
+
const list = await this.client.getSharePointList(siteId, graphListId);
|
|
2558
|
+
return {
|
|
2559
|
+
id: String(listId),
|
|
2560
|
+
name: list.name ?? '',
|
|
2561
|
+
displayName: list.displayName ?? '',
|
|
2562
|
+
description: list.description ?? '',
|
|
2563
|
+
webUrl: list.webUrl ?? '',
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Creates a SharePoint list in a site, minting a durable sl_ token.
|
|
2568
|
+
*/
|
|
2569
|
+
async createSharePointListAsync(siteId, displayName, description) {
|
|
2570
|
+
const graphSiteId = this.toGraphId(siteId, 'site');
|
|
2571
|
+
const body = {
|
|
2572
|
+
displayName,
|
|
2573
|
+
list: { template: 'genericList' },
|
|
2574
|
+
};
|
|
2575
|
+
if (description != null) {
|
|
2576
|
+
body.description = description;
|
|
2577
|
+
}
|
|
2578
|
+
const created = await this.client.createSharePointList(graphSiteId, body);
|
|
2579
|
+
const newListId = created.id;
|
|
2580
|
+
if (newListId == null || newListId.length === 0) {
|
|
2581
|
+
// Mint guard (matches #46/#47): a composite token minted with an empty id
|
|
2582
|
+
// would digest to a resolvable-but-wrong token and be reported as 'created'.
|
|
2583
|
+
throw new Error('SharePoint list creation returned no id.');
|
|
2584
|
+
}
|
|
2585
|
+
return this.mintAliasComposite('sharePointList', { siteId: graphSiteId, listId: newListId });
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Lists the column definitions for a SharePoint list. Columns are addressed by
|
|
2589
|
+
* name in item field values, so they carry no durable token.
|
|
2590
|
+
*/
|
|
2591
|
+
async listSharePointListColumnsAsync(listId) {
|
|
2592
|
+
const { siteId, listId: graphListId } = this.toGraphParts(listId, 'sharePointList', ['siteId', 'listId']);
|
|
2593
|
+
const columns = await this.client.listSharePointListColumns(siteId, graphListId);
|
|
2594
|
+
return columns.map((col) => ({
|
|
2595
|
+
id: col.id ?? '',
|
|
2596
|
+
name: col.name ?? '',
|
|
2597
|
+
displayName: col.displayName ?? '',
|
|
2598
|
+
columnType: sharePointColumnType(col),
|
|
2599
|
+
required: col.required ?? false,
|
|
2600
|
+
readOnly: col.readOnly ?? false,
|
|
2601
|
+
}));
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Lists the items in a SharePoint list, minting durable sn_ tokens that carry
|
|
2605
|
+
* the {siteId, listId, itemId} tuple.
|
|
2606
|
+
*/
|
|
2607
|
+
async listSharePointListItemsAsync(listId, limit = 50) {
|
|
2608
|
+
const { siteId, listId: graphListId } = this.toGraphParts(listId, 'sharePointList', ['siteId', 'listId']);
|
|
2609
|
+
const items = await this.client.listSharePointListItems(siteId, graphListId, limit);
|
|
2610
|
+
const result = [];
|
|
2611
|
+
for (const item of items) {
|
|
2612
|
+
const itemGraphId = item.id;
|
|
2613
|
+
if (itemGraphId != null) {
|
|
2614
|
+
result.push({
|
|
2615
|
+
id: this.mintAliasComposite('sharePointListItem', { siteId, listId: graphListId, itemId: itemGraphId }),
|
|
2616
|
+
fields: item.fields ?? {},
|
|
2617
|
+
webUrl: item.webUrl ?? '',
|
|
2618
|
+
createdDateTime: item.createdDateTime ?? '',
|
|
2619
|
+
lastModifiedDateTime: item.lastModifiedDateTime ?? '',
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return result;
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Gets a specific SharePoint list item, resolving the sn_ token to its tuple.
|
|
2627
|
+
*/
|
|
2628
|
+
async getSharePointListItemAsync(itemId) {
|
|
2629
|
+
const { siteId, listId, itemId: graphItemId } = this.toGraphParts(itemId, 'sharePointListItem', ['siteId', 'listId', 'itemId']);
|
|
2630
|
+
const item = await this.client.getSharePointListItem(siteId, listId, graphItemId);
|
|
2631
|
+
return {
|
|
2632
|
+
id: String(itemId),
|
|
2633
|
+
fields: item.fields ?? {},
|
|
2634
|
+
webUrl: item.webUrl ?? '',
|
|
2635
|
+
createdDateTime: item.createdDateTime ?? '',
|
|
2636
|
+
lastModifiedDateTime: item.lastModifiedDateTime ?? '',
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* Creates an item in a SharePoint list, minting a durable sn_ token.
|
|
2641
|
+
*/
|
|
2642
|
+
async createSharePointListItemAsync(listId, fields) {
|
|
2643
|
+
const { siteId, listId: graphListId } = this.toGraphParts(listId, 'sharePointList', ['siteId', 'listId']);
|
|
2644
|
+
const created = await this.client.createSharePointListItem(siteId, graphListId, fields);
|
|
2645
|
+
const newItemId = created.id;
|
|
2646
|
+
if (newItemId == null || newItemId.length === 0) {
|
|
2647
|
+
// Mint guard (matches #46/#47): see createSharePointListAsync.
|
|
2648
|
+
throw new Error('SharePoint list item creation returned no id.');
|
|
2649
|
+
}
|
|
2650
|
+
return this.mintAliasComposite('sharePointListItem', { siteId, listId: graphListId, itemId: newItemId });
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Updates the field values of a SharePoint list item.
|
|
2654
|
+
*/
|
|
2655
|
+
async updateSharePointListItemAsync(itemId, fields) {
|
|
2656
|
+
const { siteId, listId, itemId: graphItemId } = this.toGraphParts(itemId, 'sharePointListItem', ['siteId', 'listId', 'itemId']);
|
|
2657
|
+
await this.client.updateSharePointListItem(siteId, listId, graphItemId, fields);
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Deletes an item from a SharePoint list.
|
|
2661
|
+
*/
|
|
2662
|
+
async deleteSharePointListItemAsync(itemId) {
|
|
2663
|
+
const { siteId, listId, itemId: graphItemId } = this.toGraphParts(itemId, 'sharePointListItem', ['siteId', 'listId', 'itemId']);
|
|
2664
|
+
await this.client.deleteSharePointListItem(siteId, listId, graphItemId);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Derives a SharePoint column's type from its definition facets. Graph encodes
|
|
2669
|
+
* the type as the presence of a facet (`text`, `number`, `boolean`, …) rather
|
|
2670
|
+
* than a single field, so return the first facet present or 'unknown'.
|
|
2671
|
+
*/
|
|
2672
|
+
function sharePointColumnType(col) {
|
|
2673
|
+
const facets = [
|
|
2674
|
+
'text', 'number', 'boolean', 'dateTime', 'choice', 'currency',
|
|
2675
|
+
'personOrGroup', 'lookup', 'hyperlinkOrPicture', 'calculated',
|
|
2676
|
+
];
|
|
2677
|
+
for (const facet of facets) {
|
|
2678
|
+
if (col[facet] != null)
|
|
2679
|
+
return facet;
|
|
2680
|
+
}
|
|
2681
|
+
return 'unknown';
|
|
2992
2682
|
}
|
|
2993
2683
|
/**
|
|
2994
2684
|
* Creates a Microsoft Graph API repository.
|
|
2995
2685
|
*/
|
|
2996
|
-
export function createGraphRepository(deviceCodeCallback) {
|
|
2997
|
-
return new GraphRepository(deviceCodeCallback);
|
|
2686
|
+
export function createGraphRepository(deviceCodeCallback, store, accountId) {
|
|
2687
|
+
return new GraphRepository(deviceCodeCallback, store, accountId);
|
|
2998
2688
|
}
|
|
2999
2689
|
//# sourceMappingURL=repository.js.map
|