@jbctechsolutions/mcp-office365 3.0.0 → 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 +42 -65
- package/dist/approval/hash.d.ts +8 -8
- package/dist/approval/token-manager.d.ts +12 -6
- package/dist/approval/token-manager.d.ts.map +1 -1
- package/dist/approval/token-manager.js +11 -3
- 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 +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -3
- 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 +8 -0
- package/dist/graph/auth/config.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/client/graph-client.d.ts +105 -0
- package/dist/graph/client/graph-client.d.ts.map +1 -1
- package/dist/graph/client/graph-client.js +414 -18
- 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 +400 -270
- package/dist/graph/repository.d.ts.map +1 -1
- package/dist/graph/repository.js +739 -1046
- package/dist/graph/repository.js.map +1 -1
- 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 +2 -1
- package/dist/ids/resolver.d.ts.map +1 -1
- package/dist/ids/resolver.js +19 -4
- package/dist/ids/resolver.js.map +1 -1
- 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 +7 -1
- package/dist/ids/token.d.ts.map +1 -1
- package/dist/ids/token.js +46 -7
- package/dist/ids/token.js.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -100
- 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.map +1 -1
- package/dist/registry/all-tools.js +8 -4
- package/dist/registry/all-tools.js.map +1 -1
- package/dist/registry/context.d.ts +1 -7
- package/dist/registry/context.d.ts.map +1 -1
- package/dist/registry/context.js +0 -11
- package/dist/registry/context.js.map +1 -1
- 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 +2 -2
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/registry.d.ts +11 -0
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +87 -0
- package/dist/registry/registry.js.map +1 -1
- package/dist/registry/types.d.ts +61 -13
- package/dist/registry/types.d.ts.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/schema.d.ts +2 -3
- package/dist/state/schema.d.ts.map +1 -1
- package/dist/state/schema.js +26 -3
- package/dist/state/schema.js.map +1 -1
- package/dist/state/store.d.ts +3 -0
- package/dist/state/store.d.ts.map +1 -1
- package/dist/state/store.js +4 -0
- package/dist/state/store.js.map +1 -1
- package/dist/tools/calendar-graph.d.ts.map +1 -1
- package/dist/tools/calendar-graph.js +10 -9
- package/dist/tools/calendar-graph.js.map +1 -1
- package/dist/tools/calendar-permissions.d.ts +7 -7
- package/dist/tools/calendar-permissions.d.ts.map +1 -1
- package/dist/tools/calendar-permissions.js +7 -4
- package/dist/tools/calendar-permissions.js.map +1 -1
- package/dist/tools/calendar.d.ts +14 -114
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +41 -202
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/categories.d.ts +4 -4
- package/dist/tools/categories.d.ts.map +1 -1
- package/dist/tools/categories.js +5 -2
- package/dist/tools/categories.js.map +1 -1
- package/dist/tools/checklist-items.d.ts +9 -9
- package/dist/tools/checklist-items.d.ts.map +1 -1
- package/dist/tools/checklist-items.js +8 -5
- package/dist/tools/checklist-items.js.map +1 -1
- package/dist/tools/contact-folders.d.ts +2 -2
- package/dist/tools/contact-folders.d.ts.map +1 -1
- package/dist/tools/contact-folders.js +5 -2
- package/dist/tools/contact-folders.js.map +1 -1
- package/dist/tools/contacts-graph.d.ts +1 -1
- package/dist/tools/contacts-graph.d.ts.map +1 -1
- package/dist/tools/contacts-graph.js +8 -6
- package/dist/tools/contacts-graph.js.map +1 -1
- package/dist/tools/contacts.d.ts +9 -44
- package/dist/tools/contacts.d.ts.map +1 -1
- package/dist/tools/contacts.js +23 -131
- package/dist/tools/contacts.js.map +1 -1
- package/dist/tools/excel.d.ts +10 -10
- package/dist/tools/excel.d.ts.map +1 -1
- package/dist/tools/excel.js +9 -6
- package/dist/tools/excel.js.map +1 -1
- package/dist/tools/focused-overrides.d.ts +4 -4
- package/dist/tools/focused-overrides.d.ts.map +1 -1
- package/dist/tools/focused-overrides.js +5 -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 +7 -7
- package/dist/tools/linked-resources.d.ts.map +1 -1
- package/dist/tools/linked-resources.js +7 -4
- package/dist/tools/linked-resources.js.map +1 -1
- package/dist/tools/mail-graph.d.ts.map +1 -1
- package/dist/tools/mail-graph.js +21 -12
- package/dist/tools/mail-graph.js.map +1 -1
- package/dist/tools/mail-rules.d.ts +8 -8
- package/dist/tools/mail-rules.d.ts.map +1 -1
- package/dist/tools/mail-rules.js +7 -7
- package/dist/tools/mail-rules.js.map +1 -1
- package/dist/tools/mail-send.d.ts +27 -27
- package/dist/tools/mail-send.d.ts.map +1 -1
- package/dist/tools/mail-send.js +30 -19
- package/dist/tools/mail-send.js.map +1 -1
- package/dist/tools/mail.d.ts +22 -79
- package/dist/tools/mail.d.ts.map +1 -1
- package/dist/tools/mail.js +45 -293
- package/dist/tools/mail.js.map +1 -1
- package/dist/tools/mailbox-organization.d.ts +34 -37
- package/dist/tools/mailbox-organization.d.ts.map +1 -1
- package/dist/tools/mailbox-organization.js +72 -64
- package/dist/tools/mailbox-organization.js.map +1 -1
- package/dist/tools/meetings.d.ts +14 -14
- package/dist/tools/meetings.d.ts.map +1 -1
- package/dist/tools/meetings.js +8 -6
- package/dist/tools/meetings.js.map +1 -1
- package/dist/tools/onedrive.d.ts +16 -16
- package/dist/tools/onedrive.d.ts.map +1 -1
- package/dist/tools/onedrive.js +16 -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/planner-visualization.d.ts +5 -5
- package/dist/tools/planner-visualization.d.ts.map +1 -1
- package/dist/tools/planner-visualization.js +5 -4
- package/dist/tools/planner-visualization.js.map +1 -1
- package/dist/tools/planner.d.ts +59 -39
- package/dist/tools/planner.d.ts.map +1 -1
- package/dist/tools/planner.js +48 -23
- package/dist/tools/planner.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 +14 -14
- package/dist/tools/sharepoint.d.ts.map +1 -1
- package/dist/tools/sharepoint.js +11 -9
- package/dist/tools/sharepoint.js.map +1 -1
- package/dist/tools/task-attachments.d.ts +7 -7
- package/dist/tools/task-attachments.d.ts.map +1 -1
- package/dist/tools/task-attachments.js +7 -4
- package/dist/tools/task-attachments.js.map +1 -1
- package/dist/tools/task-lists.d.ts +3 -3
- package/dist/tools/task-lists.d.ts.map +1 -1
- package/dist/tools/task-lists.js +10 -6
- package/dist/tools/task-lists.js.map +1 -1
- package/dist/tools/tasks-graph.d.ts +2 -2
- package/dist/tools/tasks-graph.d.ts.map +1 -1
- package/dist/tools/tasks-graph.js +6 -4
- package/dist/tools/tasks-graph.js.map +1 -1
- package/dist/tools/tasks.d.ts +8 -43
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +19 -122
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/teams.d.ts +46 -46
- package/dist/tools/teams.d.ts.map +1 -1
- package/dist/tools/teams.js +34 -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 +15 -34
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +13 -49
- 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/accounts.d.ts +0 -37
- package/dist/tools/accounts.d.ts.map +0 -1
- package/dist/tools/accounts.js +0 -68
- package/dist/tools/accounts.js.map +0 -1
- package/dist/tools/calendar-apple.d.ts +0 -39
- package/dist/tools/calendar-apple.d.ts.map +0 -1
- package/dist/tools/calendar-apple.js +0 -159
- package/dist/tools/calendar-apple.js.map +0 -1
- package/dist/tools/mail-apple.d.ts +0 -40
- package/dist/tools/mail-apple.d.ts.map +0 -1
- package/dist/tools/mail-apple.js +0 -146
- package/dist/tools/mail-apple.js.map +0 -1
- package/dist/tools/notes.d.ts +0 -86
- package/dist/tools/notes.d.ts.map +0 -1
- package/dist/tools/notes.js +0 -202
- 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,78 +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;
|
|
56
106
|
}
|
|
57
107
|
// ===========================================================================
|
|
58
108
|
// Cache Resolvers (auto-fetch parent on cache miss)
|
|
59
109
|
// ===========================================================================
|
|
60
110
|
async resolveTeamId(teamId) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
|
69
124
|
}
|
|
70
125
|
async resolvePlanId(planId) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
79
184
|
}
|
|
80
185
|
async resolveChatId(chatId) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
196
|
}
|
|
90
197
|
// ===========================================================================
|
|
91
198
|
// Folders
|
|
@@ -97,31 +204,15 @@ export class GraphRepository {
|
|
|
97
204
|
throw new Error('Use listFoldersAsync() for Graph repository');
|
|
98
205
|
}
|
|
99
206
|
async listFoldersAsync() {
|
|
207
|
+
// Rows carry self-encoding fd_ tokens (the mapper mints them) — no cache.
|
|
100
208
|
const folders = await this.client.listMailFolders();
|
|
101
|
-
// Update ID cache
|
|
102
|
-
for (const folder of folders) {
|
|
103
|
-
if (folder.id != null) {
|
|
104
|
-
const numericId = hashStringToNumber(folder.id);
|
|
105
|
-
this.idCache.folders.set(numericId, folder.id);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
209
|
return folders.map(mapMailFolderToRow);
|
|
109
210
|
}
|
|
110
211
|
getFolder(_id) {
|
|
111
212
|
throw new Error('Use getFolderAsync() for Graph repository');
|
|
112
213
|
}
|
|
113
214
|
async getFolderAsync(id) {
|
|
114
|
-
const graphId = this.
|
|
115
|
-
if (graphId == null) {
|
|
116
|
-
// Try to find it by listing all folders
|
|
117
|
-
await this.listFoldersAsync();
|
|
118
|
-
const refreshedGraphId = this.idCache.folders.get(id);
|
|
119
|
-
if (refreshedGraphId == null) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
const folder = await this.client.getMailFolder(refreshedGraphId);
|
|
123
|
-
return folder != null ? mapMailFolderToRow(folder) : undefined;
|
|
124
|
-
}
|
|
215
|
+
const graphId = this.toGraphId(id, 'folder');
|
|
125
216
|
const folder = await this.client.getMailFolder(graphId);
|
|
126
217
|
return folder != null ? mapMailFolderToRow(folder) : undefined;
|
|
127
218
|
}
|
|
@@ -132,58 +223,23 @@ export class GraphRepository {
|
|
|
132
223
|
throw new Error('Use listEmailsAsync() for Graph repository');
|
|
133
224
|
}
|
|
134
225
|
async listEmailsAsync(folderId, limit, offset) {
|
|
135
|
-
const graphFolderId = this.
|
|
136
|
-
if (graphFolderId == null) {
|
|
137
|
-
// Refresh folder cache
|
|
138
|
-
await this.listFoldersAsync();
|
|
139
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
140
|
-
if (refreshedId == null) {
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
return this.listEmailsWithGraphId(refreshedId, limit, offset);
|
|
144
|
-
}
|
|
226
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
145
227
|
return this.listEmailsWithGraphId(graphFolderId, limit, offset);
|
|
146
228
|
}
|
|
147
229
|
async listEmailsWithGraphId(folderId, limit, offset) {
|
|
230
|
+
// Rows carry self-encoding em_ tokens (the mapper mints them) — no cache.
|
|
148
231
|
const messages = await this.client.listMessages(folderId, limit, offset);
|
|
149
|
-
// Update ID cache
|
|
150
|
-
for (const message of messages) {
|
|
151
|
-
if (message.id != null) {
|
|
152
|
-
const numericId = hashStringToNumber(message.id);
|
|
153
|
-
this.idCache.messages.set(numericId, message.id);
|
|
154
|
-
}
|
|
155
|
-
if (message.conversationId != null) {
|
|
156
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
232
|
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
160
233
|
}
|
|
161
234
|
listUnreadEmails(_folderId, _limit, _offset) {
|
|
162
235
|
throw new Error('Use listUnreadEmailsAsync() for Graph repository');
|
|
163
236
|
}
|
|
164
237
|
async listUnreadEmailsAsync(folderId, limit, offset) {
|
|
165
|
-
const graphFolderId = this.
|
|
166
|
-
if (graphFolderId == null) {
|
|
167
|
-
await this.listFoldersAsync();
|
|
168
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
169
|
-
if (refreshedId == null) {
|
|
170
|
-
return [];
|
|
171
|
-
}
|
|
172
|
-
return this.listUnreadEmailsWithGraphId(refreshedId, limit, offset);
|
|
173
|
-
}
|
|
238
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
174
239
|
return this.listUnreadEmailsWithGraphId(graphFolderId, limit, offset);
|
|
175
240
|
}
|
|
176
241
|
async listUnreadEmailsWithGraphId(folderId, limit, offset) {
|
|
177
242
|
const messages = await this.client.listUnreadMessages(folderId, limit, offset);
|
|
178
|
-
for (const message of messages) {
|
|
179
|
-
if (message.id != null) {
|
|
180
|
-
const numericId = hashStringToNumber(message.id);
|
|
181
|
-
this.idCache.messages.set(numericId, message.id);
|
|
182
|
-
}
|
|
183
|
-
if (message.conversationId != null) {
|
|
184
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
243
|
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
188
244
|
}
|
|
189
245
|
searchEmails(_query, _limit) {
|
|
@@ -191,43 +247,17 @@ export class GraphRepository {
|
|
|
191
247
|
}
|
|
192
248
|
async searchEmailsAsync(query, limit) {
|
|
193
249
|
const messages = await this.client.searchMessages(query, limit);
|
|
194
|
-
for (const message of messages) {
|
|
195
|
-
if (message.id != null) {
|
|
196
|
-
const numericId = hashStringToNumber(message.id);
|
|
197
|
-
this.idCache.messages.set(numericId, message.id);
|
|
198
|
-
}
|
|
199
|
-
if (message.conversationId != null) {
|
|
200
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
250
|
return messages.map((m) => mapMessageToEmailRow(m));
|
|
204
251
|
}
|
|
205
252
|
searchEmailsInFolder(_folderId, _query, _limit) {
|
|
206
253
|
throw new Error('Use searchEmailsInFolderAsync() for Graph repository');
|
|
207
254
|
}
|
|
208
255
|
async searchEmailsInFolderAsync(folderId, query, limit) {
|
|
209
|
-
const graphFolderId = this.
|
|
210
|
-
if (graphFolderId == null) {
|
|
211
|
-
await this.listFoldersAsync();
|
|
212
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
213
|
-
if (refreshedId == null) {
|
|
214
|
-
return [];
|
|
215
|
-
}
|
|
216
|
-
return this.searchEmailsInFolderWithGraphId(refreshedId, query, limit);
|
|
217
|
-
}
|
|
256
|
+
const graphFolderId = this.toGraphId(folderId, 'folder');
|
|
218
257
|
return this.searchEmailsInFolderWithGraphId(graphFolderId, query, limit);
|
|
219
258
|
}
|
|
220
259
|
async searchEmailsInFolderWithGraphId(folderId, query, limit) {
|
|
221
260
|
const messages = await this.client.searchMessagesInFolder(folderId, query, limit);
|
|
222
|
-
for (const message of messages) {
|
|
223
|
-
if (message.id != null) {
|
|
224
|
-
const numericId = hashStringToNumber(message.id);
|
|
225
|
-
this.idCache.messages.set(numericId, message.id);
|
|
226
|
-
}
|
|
227
|
-
if (message.conversationId != null) {
|
|
228
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
261
|
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
232
262
|
}
|
|
233
263
|
/**
|
|
@@ -237,14 +267,6 @@ export class GraphRepository {
|
|
|
237
267
|
*/
|
|
238
268
|
async searchEmailsStructuredAsync(compiled, limit) {
|
|
239
269
|
const messages = await this.runStructuredSearch(compiled, limit);
|
|
240
|
-
for (const msg of messages) {
|
|
241
|
-
if (msg.id != null) {
|
|
242
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
243
|
-
}
|
|
244
|
-
if (msg.conversationId != null) {
|
|
245
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
270
|
return messages.map((m) => mapMessageToEmailRow(m));
|
|
249
271
|
}
|
|
250
272
|
runStructuredSearch(compiled, limit) {
|
|
@@ -258,22 +280,16 @@ export class GraphRepository {
|
|
|
258
280
|
}
|
|
259
281
|
}
|
|
260
282
|
async checkNewEmailsAsync(folderId) {
|
|
261
|
-
const graphFolderId = this.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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);
|
|
265
289
|
const isInitialSync = existingDeltaLink == null;
|
|
266
290
|
const { messages, deltaLink } = await this.client.getMessagesDelta(graphFolderId, existingDeltaLink);
|
|
267
291
|
if (deltaLink) {
|
|
268
|
-
this.deltaLinks.set(
|
|
269
|
-
}
|
|
270
|
-
for (const msg of messages) {
|
|
271
|
-
if (msg.id != null) {
|
|
272
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
273
|
-
}
|
|
274
|
-
if (msg.conversationId != null) {
|
|
275
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
276
|
-
}
|
|
292
|
+
this.deltaLinks.set(graphFolderId, deltaLink);
|
|
277
293
|
}
|
|
278
294
|
const activeMessages = messages.filter((m) => m['@removed'] == null);
|
|
279
295
|
return {
|
|
@@ -284,61 +300,8 @@ export class GraphRepository {
|
|
|
284
300
|
getEmail(_id) {
|
|
285
301
|
throw new Error('Use getEmailAsync() for Graph repository');
|
|
286
302
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Populates the message ID cache by listing messages from mail folders.
|
|
289
|
-
* Used as a fallback when getEmailAsync is called with an ID not yet in cache
|
|
290
|
-
* (e.g. after server restart or when list_emails/search_emails wasn't called first).
|
|
291
|
-
*/
|
|
292
|
-
async refreshMessageCacheForGetEmail(targetId) {
|
|
293
|
-
let folders;
|
|
294
|
-
try {
|
|
295
|
-
folders = await this.listFoldersAsync();
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
if (folders.length === 0)
|
|
301
|
-
return false;
|
|
302
|
-
const MESSAGE_LIMIT_PER_FOLDER = 100;
|
|
303
|
-
const MAX_FOLDERS_TO_SCAN = 15;
|
|
304
|
-
for (let i = 0; i < Math.min(folders.length, MAX_FOLDERS_TO_SCAN); i++) {
|
|
305
|
-
const folder = folders[i];
|
|
306
|
-
const graphFolderId = this.idCache.folders.get(folder.id);
|
|
307
|
-
if (graphFolderId == null)
|
|
308
|
-
continue;
|
|
309
|
-
let messages;
|
|
310
|
-
try {
|
|
311
|
-
messages = await this.client.listMessages(graphFolderId, MESSAGE_LIMIT_PER_FOLDER, 0);
|
|
312
|
-
}
|
|
313
|
-
catch {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
if (!Array.isArray(messages))
|
|
317
|
-
continue;
|
|
318
|
-
for (const message of messages) {
|
|
319
|
-
if (message.id != null) {
|
|
320
|
-
const numericId = hashStringToNumber(message.id);
|
|
321
|
-
this.idCache.messages.set(numericId, message.id);
|
|
322
|
-
if (numericId === targetId)
|
|
323
|
-
return true;
|
|
324
|
-
}
|
|
325
|
-
if (message.conversationId != null) {
|
|
326
|
-
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return this.idCache.messages.has(targetId);
|
|
331
|
-
}
|
|
332
303
|
async getEmailAsync(id) {
|
|
333
|
-
|
|
334
|
-
if (graphId == null) {
|
|
335
|
-
const found = await this.refreshMessageCacheForGetEmail(id);
|
|
336
|
-
if (found)
|
|
337
|
-
graphId = this.idCache.messages.get(id) ?? undefined;
|
|
338
|
-
}
|
|
339
|
-
if (graphId == null) {
|
|
340
|
-
return undefined;
|
|
341
|
-
}
|
|
304
|
+
const graphId = this.toGraphId(id, 'message');
|
|
342
305
|
const message = await this.client.getMessage(graphId);
|
|
343
306
|
return message != null ? mapMessageToEmailRow(message) : undefined;
|
|
344
307
|
}
|
|
@@ -353,16 +316,7 @@ export class GraphRepository {
|
|
|
353
316
|
throw new Error('Use getUnreadCountByFolderAsync() for Graph repository');
|
|
354
317
|
}
|
|
355
318
|
async getUnreadCountByFolderAsync(folderId) {
|
|
356
|
-
const graphId = this.
|
|
357
|
-
if (graphId == null) {
|
|
358
|
-
await this.listFoldersAsync();
|
|
359
|
-
const refreshedId = this.idCache.folders.get(folderId);
|
|
360
|
-
if (refreshedId == null) {
|
|
361
|
-
return 0;
|
|
362
|
-
}
|
|
363
|
-
const folder = await this.client.getMailFolder(refreshedId);
|
|
364
|
-
return folder?.unreadItemCount ?? 0;
|
|
365
|
-
}
|
|
319
|
+
const graphId = this.toGraphId(folderId, 'folder');
|
|
366
320
|
const folder = await this.client.getMailFolder(graphId);
|
|
367
321
|
return folder?.unreadItemCount ?? 0;
|
|
368
322
|
}
|
|
@@ -372,27 +326,19 @@ export class GraphRepository {
|
|
|
372
326
|
/**
|
|
373
327
|
* Lists all messages in a conversation thread.
|
|
374
328
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
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.
|
|
377
332
|
*/
|
|
378
333
|
async listConversationAsync(messageId, limit) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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)
|
|
383
340
|
throw new Error('Message has no conversation ID');
|
|
384
|
-
const
|
|
385
|
-
if (graphConversationId == null)
|
|
386
|
-
throw new Error('Conversation ID not found in cache. Try fetching the email first to populate the cache.');
|
|
387
|
-
const messages = await this.client.listConversationMessages(graphConversationId, limit);
|
|
388
|
-
for (const msg of messages) {
|
|
389
|
-
if (msg.id != null) {
|
|
390
|
-
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
391
|
-
}
|
|
392
|
-
if (msg.conversationId != null) {
|
|
393
|
-
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
341
|
+
const messages = await this.client.listConversationMessages(convId, limit);
|
|
396
342
|
return messages.map((m) => mapMessageToEmailRow(m));
|
|
397
343
|
}
|
|
398
344
|
// ===========================================================================
|
|
@@ -402,72 +348,39 @@ export class GraphRepository {
|
|
|
402
348
|
throw new Error('Use listCalendarsAsync() for Graph repository');
|
|
403
349
|
}
|
|
404
350
|
async listCalendarsAsync() {
|
|
351
|
+
// Rows carry self-encoding fd_ tokens (the mapper mints them) — no cache.
|
|
405
352
|
const calendars = await this.client.listCalendars();
|
|
406
|
-
for (const calendar of calendars) {
|
|
407
|
-
if (calendar.id != null) {
|
|
408
|
-
const numericId = hashStringToNumber(calendar.id);
|
|
409
|
-
this.idCache.folders.set(numericId, calendar.id);
|
|
410
|
-
this.idCache.calendars.set(numericId, calendar.id);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
353
|
return calendars.map(mapCalendarToFolderRow);
|
|
414
354
|
}
|
|
415
355
|
listEvents(_limit) {
|
|
416
356
|
throw new Error('Use listEventsAsync() for Graph repository');
|
|
417
357
|
}
|
|
418
358
|
async listEventsAsync(limit) {
|
|
359
|
+
// Rows carry self-encoding ev_ tokens (the mapper mints them) — no cache.
|
|
419
360
|
const events = await this.client.listEvents(limit);
|
|
420
|
-
for (const event of events) {
|
|
421
|
-
if (event.id != null) {
|
|
422
|
-
const numericId = hashStringToNumber(event.id);
|
|
423
|
-
this.idCache.events.set(numericId, event.id);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
361
|
return events.map((e) => mapEventToEventRow(e));
|
|
427
362
|
}
|
|
428
363
|
listEventsByFolder(_folderId, _limit) {
|
|
429
364
|
throw new Error('Use listEventsByFolderAsync() for Graph repository');
|
|
430
365
|
}
|
|
431
366
|
async listEventsByFolderAsync(folderId, limit) {
|
|
432
|
-
const graphCalendarId = this.
|
|
433
|
-
if (graphCalendarId == null) {
|
|
434
|
-
return this.listEventsAsync(limit);
|
|
435
|
-
}
|
|
367
|
+
const graphCalendarId = this.toGraphId(folderId, 'folder');
|
|
436
368
|
const events = await this.client.listEvents(limit, graphCalendarId);
|
|
437
|
-
for (const event of events) {
|
|
438
|
-
if (event.id != null) {
|
|
439
|
-
const numericId = hashStringToNumber(event.id);
|
|
440
|
-
this.idCache.events.set(numericId, event.id);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
369
|
return events.map((e) => mapEventToEventRow(e, graphCalendarId));
|
|
444
370
|
}
|
|
445
371
|
searchEvents(_query, _startDate, _endDate, _limit) {
|
|
446
372
|
throw new Error('Use searchEventsAsync() for Graph repository');
|
|
447
373
|
}
|
|
448
374
|
async searchEventsAsync(query, startDate, endDate, limit) {
|
|
449
|
-
// 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.
|
|
450
377
|
const start = startDate != null ? new Date(startDate) : undefined;
|
|
451
378
|
const end = endDate != null ? new Date(endDate) : undefined;
|
|
452
379
|
const events = await this.client.listEvents(1000, undefined, start, end);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
let rows = events.map((e) => mapEventToEventRow(e));
|
|
460
|
-
// Filter by title client-side if query provided
|
|
461
|
-
if (query != null) {
|
|
462
|
-
const queryLower = query.toLowerCase();
|
|
463
|
-
rows = rows.filter((row) => {
|
|
464
|
-
// EventRow doesn't have title, so we need to check the original event
|
|
465
|
-
const originalEvent = events.find((e) => e.id != null && hashStringToNumber(e.id) === row.id);
|
|
466
|
-
const subject = originalEvent?.subject?.toLowerCase() ?? '';
|
|
467
|
-
return subject.includes(queryLower);
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
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));
|
|
471
384
|
}
|
|
472
385
|
listEventsByDateRange(_startDate, _endDate, _limit) {
|
|
473
386
|
throw new Error('Use listEventsByDateRangeAsync() for Graph repository');
|
|
@@ -476,36 +389,23 @@ export class GraphRepository {
|
|
|
476
389
|
const start = new Date(startDate * 1000);
|
|
477
390
|
const end = new Date(endDate * 1000);
|
|
478
391
|
const events = await this.client.listEvents(limit, undefined, start, end);
|
|
479
|
-
for (const event of events) {
|
|
480
|
-
if (event.id != null) {
|
|
481
|
-
const numericId = hashStringToNumber(event.id);
|
|
482
|
-
this.idCache.events.set(numericId, event.id);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
392
|
return events.map((e) => mapEventToEventRow(e));
|
|
486
393
|
}
|
|
487
394
|
getEvent(_id) {
|
|
488
395
|
throw new Error('Use getEventAsync() for Graph repository');
|
|
489
396
|
}
|
|
490
397
|
async getEventAsync(id) {
|
|
491
|
-
const graphId = this.
|
|
492
|
-
if (graphId == null) {
|
|
493
|
-
return undefined;
|
|
494
|
-
}
|
|
398
|
+
const graphId = this.toGraphId(id, 'event');
|
|
495
399
|
const event = await this.client.getEvent(graphId);
|
|
496
400
|
return event != null ? mapEventToEventRow(event) : undefined;
|
|
497
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
|
+
}
|
|
498
406
|
async listEventInstancesAsync(eventId, startDate, endDate) {
|
|
499
|
-
const graphId = this.
|
|
500
|
-
if (graphId == null) {
|
|
501
|
-
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
502
|
-
}
|
|
407
|
+
const graphId = this.toGraphId(eventId, 'event');
|
|
503
408
|
const instances = await this.client.listEventInstances(graphId, startDate, endDate);
|
|
504
|
-
for (const inst of instances) {
|
|
505
|
-
if (inst.id != null) {
|
|
506
|
-
this.idCache.events.set(hashStringToNumber(inst.id), inst.id);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
409
|
return instances.map((e) => mapEventToEventRow(e));
|
|
510
410
|
}
|
|
511
411
|
// ===========================================================================
|
|
@@ -515,13 +415,9 @@ export class GraphRepository {
|
|
|
515
415
|
throw new Error('Use listContactsAsync() for Graph repository');
|
|
516
416
|
}
|
|
517
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.
|
|
518
420
|
const contacts = await this.client.listContacts(limit, offset);
|
|
519
|
-
for (const contact of contacts) {
|
|
520
|
-
if (contact.id != null) {
|
|
521
|
-
const numericId = hashStringToNumber(contact.id);
|
|
522
|
-
this.idCache.contacts.set(numericId, contact.id);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
421
|
return contacts.map(mapContactToContactRow);
|
|
526
422
|
}
|
|
527
423
|
searchContacts(_query, _limit) {
|
|
@@ -529,25 +425,20 @@ export class GraphRepository {
|
|
|
529
425
|
}
|
|
530
426
|
async searchContactsAsync(query, limit) {
|
|
531
427
|
const contacts = await this.client.searchContacts(query, limit);
|
|
532
|
-
for (const contact of contacts) {
|
|
533
|
-
if (contact.id != null) {
|
|
534
|
-
const numericId = hashStringToNumber(contact.id);
|
|
535
|
-
this.idCache.contacts.set(numericId, contact.id);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
428
|
return contacts.map(mapContactToContactRow);
|
|
539
429
|
}
|
|
540
430
|
getContact(_id) {
|
|
541
431
|
throw new Error('Use getContactAsync() for Graph repository');
|
|
542
432
|
}
|
|
543
433
|
async getContactAsync(id) {
|
|
544
|
-
const graphId = this.
|
|
545
|
-
if (graphId == null) {
|
|
546
|
-
return undefined;
|
|
547
|
-
}
|
|
434
|
+
const graphId = this.toGraphId(id, 'contact');
|
|
548
435
|
const contact = await this.client.getContact(graphId);
|
|
549
436
|
return contact != null ? mapContactToContactRow(contact) : undefined;
|
|
550
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
|
+
}
|
|
551
442
|
// ===========================================================================
|
|
552
443
|
// Contact Folders
|
|
553
444
|
// ===========================================================================
|
|
@@ -555,10 +446,8 @@ export class GraphRepository {
|
|
|
555
446
|
const folders = await this.client.listContactFolders();
|
|
556
447
|
return folders.map((folder) => {
|
|
557
448
|
const graphId = folder.id;
|
|
558
|
-
const numericId = hashStringToNumber(graphId);
|
|
559
|
-
this.idCache.contactFolders.set(numericId, graphId);
|
|
560
449
|
return {
|
|
561
|
-
id:
|
|
450
|
+
id: this.mintAlias('contactFolder', graphId),
|
|
562
451
|
name: folder.displayName ?? '',
|
|
563
452
|
parentFolderId: folder.parentFolderId ?? null,
|
|
564
453
|
};
|
|
@@ -566,47 +455,30 @@ export class GraphRepository {
|
|
|
566
455
|
}
|
|
567
456
|
async createContactFolderAsync(name) {
|
|
568
457
|
const created = await this.client.createContactFolder(name);
|
|
569
|
-
|
|
570
|
-
const numericId = hashStringToNumber(graphId);
|
|
571
|
-
this.idCache.contactFolders.set(numericId, graphId);
|
|
572
|
-
return numericId;
|
|
458
|
+
return this.mintAlias('contactFolder', created.id);
|
|
573
459
|
}
|
|
574
460
|
async deleteContactFolderAsync(folderId) {
|
|
575
|
-
const graphId = this.
|
|
576
|
-
if (graphId == null)
|
|
577
|
-
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');
|
|
578
462
|
await this.client.deleteContactFolder(graphId);
|
|
579
|
-
this.idCache.contactFolders.delete(folderId);
|
|
580
463
|
}
|
|
581
464
|
async listContactsInFolderAsync(folderId, limit = 100) {
|
|
582
|
-
const graphId = this.
|
|
583
|
-
if (graphId == null)
|
|
584
|
-
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');
|
|
585
466
|
const contacts = await this.client.listContactsInFolder(graphId, limit);
|
|
586
|
-
return contacts.map(
|
|
587
|
-
if (c.id != null) {
|
|
588
|
-
this.idCache.contacts.set(hashStringToNumber(c.id), c.id);
|
|
589
|
-
}
|
|
590
|
-
return mapContactToContactRow(c);
|
|
591
|
-
});
|
|
467
|
+
return contacts.map(mapContactToContactRow);
|
|
592
468
|
}
|
|
593
469
|
// ===========================================================================
|
|
594
470
|
// Contact Photos
|
|
595
471
|
// ===========================================================================
|
|
596
472
|
async getContactPhotoAsync(contactId) {
|
|
597
|
-
const graphId = this.
|
|
598
|
-
if (graphId == null)
|
|
599
|
-
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');
|
|
600
474
|
const photoData = await this.client.getContactPhoto(graphId);
|
|
601
475
|
const downloadDir = getDownloadDir();
|
|
602
|
-
const filePath = path.join(downloadDir, `contact-${
|
|
476
|
+
const filePath = path.join(downloadDir, `contact-${createHash('sha1').update(graphId).digest('hex').slice(0, 16)}-photo.jpg`);
|
|
603
477
|
fs.writeFileSync(filePath, Buffer.from(photoData));
|
|
604
478
|
return { filePath, contentType: 'image/jpeg' };
|
|
605
479
|
}
|
|
606
480
|
async setContactPhotoAsync(contactId, filePath) {
|
|
607
|
-
const graphId = this.
|
|
608
|
-
if (graphId == null)
|
|
609
|
-
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');
|
|
610
482
|
const photoData = fs.readFileSync(filePath);
|
|
611
483
|
const ext = path.extname(filePath).toLowerCase();
|
|
612
484
|
const contentType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
@@ -620,66 +492,65 @@ export class GraphRepository {
|
|
|
620
492
|
}
|
|
621
493
|
async listTasksAsync(limit, offset) {
|
|
622
494
|
const tasks = await this.client.listAllTasks(limit, offset, true);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
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
|
+
});
|
|
632
502
|
}
|
|
633
503
|
listIncompleteTasks(_limit, _offset) {
|
|
634
504
|
throw new Error('Use listIncompleteTasksAsync() for Graph repository');
|
|
635
505
|
}
|
|
636
506
|
async listIncompleteTasksAsync(limit, offset) {
|
|
637
507
|
const tasks = await this.client.listAllTasks(limit, offset, false);
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
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
|
+
});
|
|
647
515
|
}
|
|
648
516
|
searchTasks(_query, _limit) {
|
|
649
517
|
throw new Error('Use searchTasksAsync() for Graph repository');
|
|
650
518
|
}
|
|
651
519
|
async searchTasksAsync(query, limit) {
|
|
652
520
|
const tasks = await this.client.searchTasks(query, limit);
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
+
});
|
|
660
530
|
}
|
|
661
531
|
getTask(_id) {
|
|
662
532
|
throw new Error('Use getTaskAsync() for Graph repository');
|
|
663
533
|
}
|
|
664
534
|
async getTaskAsync(id) {
|
|
665
|
-
|
|
666
|
-
|
|
535
|
+
let taskInfo;
|
|
536
|
+
try {
|
|
537
|
+
taskInfo = this.toGraphParts(id, 'task', ['taskListId', 'taskId']);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
667
540
|
return undefined;
|
|
668
541
|
}
|
|
669
542
|
const task = await this.client.getTask(taskInfo.taskListId, taskInfo.taskId);
|
|
670
543
|
if (task == null) {
|
|
671
544
|
return undefined;
|
|
672
545
|
}
|
|
673
|
-
return mapTaskToTaskRow({ ...task, taskListId: taskInfo.taskListId });
|
|
546
|
+
return mapTaskToTaskRow({ ...task, taskListId: taskInfo.taskListId }, String(id), this.mintAlias('taskList', taskInfo.taskListId));
|
|
674
547
|
}
|
|
675
548
|
async listTaskListsAsync() {
|
|
676
549
|
const lists = await this.client.listTaskLists();
|
|
677
550
|
return lists.map((list) => {
|
|
678
551
|
const graphId = list.id;
|
|
679
|
-
const numericId = hashStringToNumber(graphId);
|
|
680
|
-
this.idCache.taskLists.set(numericId, graphId);
|
|
681
552
|
return {
|
|
682
|
-
id:
|
|
553
|
+
id: this.mintAlias('taskList', graphId),
|
|
683
554
|
name: list.displayName ?? '',
|
|
684
555
|
isDefault: list.wellknownListName === 'defaultList',
|
|
685
556
|
};
|
|
@@ -720,37 +591,33 @@ export class GraphRepository {
|
|
|
720
591
|
return this.client;
|
|
721
592
|
}
|
|
722
593
|
/**
|
|
723
|
-
*
|
|
594
|
+
* Resolves a draft id (durable `em_` token or raw Graph id) to its Graph id
|
|
595
|
+
* (satisfies IMailSendRepository). Drafts are messages.
|
|
724
596
|
*/
|
|
725
597
|
getGraphIdForDraft(draftId) {
|
|
726
|
-
return this.
|
|
598
|
+
return this.toGraphId(draftId, 'message');
|
|
727
599
|
}
|
|
728
600
|
/**
|
|
729
|
-
* Gets
|
|
601
|
+
* Gets task info (Graph taskListId/taskId) from a durable `td_` token.
|
|
730
602
|
*/
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
case 'event':
|
|
738
|
-
return this.idCache.events.get(numericId);
|
|
739
|
-
case 'contact':
|
|
740
|
-
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;
|
|
741
609
|
}
|
|
742
610
|
}
|
|
743
611
|
/**
|
|
744
|
-
* Gets task
|
|
745
|
-
*/
|
|
746
|
-
getTaskInfo(numericId) {
|
|
747
|
-
return this.idCache.tasks.get(numericId);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* 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.
|
|
751
613
|
*/
|
|
752
|
-
getTaskListGraphId(
|
|
753
|
-
|
|
614
|
+
getTaskListGraphId(id) {
|
|
615
|
+
try {
|
|
616
|
+
return this.toGraphId(id, 'taskList');
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
754
621
|
}
|
|
755
622
|
// ===========================================================================
|
|
756
623
|
// Write Operations (Async)
|
|
@@ -797,42 +664,28 @@ export class GraphRepository {
|
|
|
797
664
|
}
|
|
798
665
|
// Async implementations
|
|
799
666
|
async moveEmailAsync(emailId, destinationFolderId) {
|
|
800
|
-
const graphMessageId = this.
|
|
801
|
-
const graphFolderId = this.
|
|
802
|
-
if (graphMessageId == null)
|
|
803
|
-
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
804
|
-
if (graphFolderId == null)
|
|
805
|
-
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');
|
|
806
669
|
await this.client.moveMessage(graphMessageId, graphFolderId);
|
|
807
670
|
}
|
|
808
671
|
async deleteEmailAsync(emailId) {
|
|
809
|
-
const graphId = this.
|
|
810
|
-
if (graphId == null)
|
|
811
|
-
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');
|
|
812
673
|
await this.client.deleteMessage(graphId);
|
|
813
674
|
}
|
|
814
675
|
async archiveEmailAsync(emailId) {
|
|
815
|
-
const graphId = this.
|
|
816
|
-
if (graphId == null)
|
|
817
|
-
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');
|
|
818
677
|
await this.client.archiveMessage(graphId);
|
|
819
678
|
}
|
|
820
679
|
async junkEmailAsync(emailId) {
|
|
821
|
-
const graphId = this.
|
|
822
|
-
if (graphId == null)
|
|
823
|
-
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');
|
|
824
681
|
await this.client.junkMessage(graphId);
|
|
825
682
|
}
|
|
826
683
|
async markEmailReadAsync(emailId, isRead) {
|
|
827
|
-
const graphId = this.
|
|
828
|
-
if (graphId == null)
|
|
829
|
-
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');
|
|
830
685
|
await this.client.updateMessage(graphId, { isRead });
|
|
831
686
|
}
|
|
832
687
|
async setEmailFlagAsync(emailId, flagStatus) {
|
|
833
|
-
const graphId = this.
|
|
834
|
-
if (graphId == null)
|
|
835
|
-
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');
|
|
836
689
|
const flagStatusMap = {
|
|
837
690
|
0: 'notFlagged',
|
|
838
691
|
1: 'flagged',
|
|
@@ -843,55 +696,35 @@ export class GraphRepository {
|
|
|
843
696
|
});
|
|
844
697
|
}
|
|
845
698
|
async setEmailCategoriesAsync(emailId, categories) {
|
|
846
|
-
const graphId = this.
|
|
847
|
-
if (graphId == null)
|
|
848
|
-
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');
|
|
849
700
|
await this.client.updateMessage(graphId, { categories });
|
|
850
701
|
}
|
|
851
702
|
async setEmailImportanceAsync(emailId, importance) {
|
|
852
|
-
const graphId = this.
|
|
853
|
-
if (graphId == null)
|
|
854
|
-
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');
|
|
855
704
|
await this.client.updateMessage(graphId, { importance });
|
|
856
705
|
}
|
|
857
706
|
async createFolderAsync(name, parentFolderId) {
|
|
858
707
|
const graphParentId = parentFolderId != null
|
|
859
|
-
? this.
|
|
708
|
+
? this.toGraphId(parentFolderId, 'folder')
|
|
860
709
|
: undefined;
|
|
861
|
-
const folder = await this.client.createMailFolder(name, graphParentId
|
|
862
|
-
// Update cache with new folder
|
|
863
|
-
if (folder.id != null) {
|
|
864
|
-
const numericId = hashStringToNumber(folder.id);
|
|
865
|
-
this.idCache.folders.set(numericId, folder.id);
|
|
866
|
-
}
|
|
710
|
+
const folder = await this.client.createMailFolder(name, graphParentId);
|
|
867
711
|
return mapMailFolderToRow(folder);
|
|
868
712
|
}
|
|
869
713
|
async deleteFolderAsync(folderId) {
|
|
870
|
-
const graphId = this.
|
|
871
|
-
if (graphId == null)
|
|
872
|
-
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');
|
|
873
715
|
await this.client.deleteMailFolder(graphId);
|
|
874
|
-
this.idCache.folders.delete(folderId);
|
|
875
716
|
}
|
|
876
717
|
async renameFolderAsync(folderId, newName) {
|
|
877
|
-
const graphId = this.
|
|
878
|
-
if (graphId == null)
|
|
879
|
-
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');
|
|
880
719
|
await this.client.renameMailFolder(graphId, newName);
|
|
881
720
|
}
|
|
882
721
|
async moveFolderAsync(folderId, destinationParentId) {
|
|
883
|
-
const graphFolderId = this.
|
|
884
|
-
const graphParentId = this.
|
|
885
|
-
if (graphFolderId == null)
|
|
886
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
887
|
-
if (graphParentId == null)
|
|
888
|
-
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');
|
|
889
724
|
await this.client.moveMailFolder(graphFolderId, graphParentId);
|
|
890
725
|
}
|
|
891
726
|
async emptyFolderAsync(folderId) {
|
|
892
|
-
const graphId = this.
|
|
893
|
-
if (graphId == null)
|
|
894
|
-
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');
|
|
895
728
|
await this.client.emptyMailFolder(graphId);
|
|
896
729
|
}
|
|
897
730
|
// ===========================================================================
|
|
@@ -901,7 +734,7 @@ export class GraphRepository {
|
|
|
901
734
|
* Creates a new draft message.
|
|
902
735
|
*
|
|
903
736
|
* Converts email address strings to Recipient objects, calls the Graph client,
|
|
904
|
-
*
|
|
737
|
+
* and returns a durable `em_` token plus the raw Graph id.
|
|
905
738
|
*/
|
|
906
739
|
async createDraftAsync(params) {
|
|
907
740
|
const toRecipients = (params.to ?? []).map(addr => ({
|
|
@@ -921,19 +754,15 @@ export class GraphRepository {
|
|
|
921
754
|
bccRecipients,
|
|
922
755
|
});
|
|
923
756
|
const graphId = draft.id;
|
|
924
|
-
|
|
925
|
-
this.idCache.messages.set(numericId, graphId);
|
|
926
|
-
return { numericId, graphId };
|
|
757
|
+
return { token: mintSelfEncoded('message', graphId), graphId };
|
|
927
758
|
}
|
|
928
759
|
/**
|
|
929
760
|
* Updates an existing draft message.
|
|
930
761
|
*
|
|
931
|
-
*
|
|
762
|
+
* Resolves the draft id to its Graph id, then calls the client.
|
|
932
763
|
*/
|
|
933
764
|
async updateDraftAsync(draftId, updates) {
|
|
934
|
-
const graphId = this.
|
|
935
|
-
if (graphId == null)
|
|
936
|
-
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');
|
|
937
766
|
await this.client.updateDraft(graphId, updates);
|
|
938
767
|
}
|
|
939
768
|
/**
|
|
@@ -947,12 +776,10 @@ export class GraphRepository {
|
|
|
947
776
|
/**
|
|
948
777
|
* Sends an existing draft message.
|
|
949
778
|
*
|
|
950
|
-
*
|
|
779
|
+
* Resolves the id to its Graph id, then calls the client.
|
|
951
780
|
*/
|
|
952
781
|
async sendDraftAsync(draftId) {
|
|
953
|
-
const graphId = this.
|
|
954
|
-
if (graphId == null)
|
|
955
|
-
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');
|
|
956
783
|
await this.client.sendDraft(graphId);
|
|
957
784
|
}
|
|
958
785
|
/**
|
|
@@ -981,24 +808,20 @@ export class GraphRepository {
|
|
|
981
808
|
/**
|
|
982
809
|
* Replies to a message (or replies all).
|
|
983
810
|
*
|
|
984
|
-
*
|
|
811
|
+
* Resolves the id to its Graph id, then calls the client.
|
|
985
812
|
*/
|
|
986
813
|
async replyMessageAsync(messageId, comment, replyAll) {
|
|
987
|
-
const graphId = this.
|
|
988
|
-
if (graphId == null)
|
|
989
|
-
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');
|
|
990
815
|
await this.client.replyMessage(graphId, comment, replyAll);
|
|
991
816
|
}
|
|
992
817
|
/**
|
|
993
818
|
* Forwards a message to specified recipients.
|
|
994
819
|
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
820
|
+
* Resolves the id to its Graph id, converts recipient email strings to
|
|
821
|
+
* Recipient objects, then calls the client.
|
|
997
822
|
*/
|
|
998
823
|
async forwardMessageAsync(messageId, toRecipients, comment) {
|
|
999
|
-
const graphId = this.
|
|
1000
|
-
if (graphId == null)
|
|
1001
|
-
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');
|
|
1002
825
|
const recipients = toRecipients.map(addr => ({
|
|
1003
826
|
emailAddress: { address: addr },
|
|
1004
827
|
}));
|
|
@@ -1010,42 +833,33 @@ export class GraphRepository {
|
|
|
1010
833
|
/**
|
|
1011
834
|
* Creates a reply (or reply-all) draft for a message.
|
|
1012
835
|
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
836
|
+
* Resolves the source message id to its Graph id, creates the draft via the
|
|
837
|
+
* client, and optionally updates the body.
|
|
1015
838
|
*
|
|
1016
|
-
* @returns
|
|
839
|
+
* @returns A durable `em_` token and the raw Graph id of the new draft.
|
|
1017
840
|
*/
|
|
1018
841
|
async replyAsDraftAsync(messageId, replyAll = false, 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.`);
|
|
842
|
+
const graphMessageId = this.toGraphId(messageId, 'message');
|
|
1022
843
|
// Pass comment/body through createReply so the quoted thread is preserved
|
|
1023
844
|
const body = comment != null ? { contentType: bodyType, content: comment } : undefined;
|
|
1024
845
|
const draft = replyAll
|
|
1025
846
|
? await this.client.createReplyAllDraft(graphMessageId, undefined, body)
|
|
1026
847
|
: await this.client.createReplyDraft(graphMessageId, undefined, body);
|
|
1027
848
|
const graphId = draft.id;
|
|
1028
|
-
|
|
1029
|
-
this.idCache.messages.set(numericId, graphId);
|
|
1030
|
-
return { numericId, graphId };
|
|
849
|
+
return { token: mintSelfEncoded('message', graphId), graphId };
|
|
1031
850
|
}
|
|
1032
851
|
/**
|
|
1033
852
|
* Creates a forward draft for a message.
|
|
1034
853
|
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
* 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.
|
|
1038
856
|
*
|
|
1039
|
-
* @returns
|
|
857
|
+
* @returns A durable `em_` token and the raw Graph id of the new draft.
|
|
1040
858
|
*/
|
|
1041
859
|
async forwardAsDraftAsync(messageId, toRecipients, comment, bodyType = 'text') {
|
|
1042
|
-
const graphMessageId = this.
|
|
1043
|
-
if (graphMessageId == null)
|
|
1044
|
-
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');
|
|
1045
861
|
const draft = await this.client.createForwardDraft(graphMessageId);
|
|
1046
862
|
const graphId = draft.id;
|
|
1047
|
-
const numericId = hashStringToNumber(graphId);
|
|
1048
|
-
this.idCache.messages.set(numericId, graphId);
|
|
1049
863
|
const updates = {};
|
|
1050
864
|
if (toRecipients != null && toRecipients.length > 0) {
|
|
1051
865
|
updates.toRecipients = toRecipients.map(addr => ({
|
|
@@ -1058,7 +872,7 @@ export class GraphRepository {
|
|
|
1058
872
|
if (Object.keys(updates).length > 0) {
|
|
1059
873
|
await this.client.updateDraft(graphId, updates);
|
|
1060
874
|
}
|
|
1061
|
-
return {
|
|
875
|
+
return { token: mintSelfEncoded('message', graphId), graphId };
|
|
1062
876
|
}
|
|
1063
877
|
// ---------------------------------------------------------------------------
|
|
1064
878
|
// Calendar Scheduling
|
|
@@ -1100,26 +914,18 @@ export class GraphRepository {
|
|
|
1100
914
|
/**
|
|
1101
915
|
* Lists attachments for a given email.
|
|
1102
916
|
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
* 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.
|
|
1106
919
|
*
|
|
1107
920
|
* @returns Array of attachment metadata objects.
|
|
1108
921
|
*/
|
|
1109
922
|
async listAttachmentsAsync(emailId) {
|
|
1110
|
-
const graphMessageId = this.
|
|
1111
|
-
if (graphMessageId == null)
|
|
1112
|
-
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');
|
|
1113
924
|
const attachments = await this.client.listAttachments(graphMessageId);
|
|
1114
925
|
return attachments.map((att) => {
|
|
1115
926
|
const attId = att.id ?? '';
|
|
1116
|
-
const numericId = hashStringToNumber(attId);
|
|
1117
|
-
this.idCache.attachments.set(numericId, {
|
|
1118
|
-
messageId: graphMessageId,
|
|
1119
|
-
attachmentId: attId,
|
|
1120
|
-
});
|
|
1121
927
|
return {
|
|
1122
|
-
id:
|
|
928
|
+
id: this.mintAliasComposite('attachment', { messageId: graphMessageId, attachmentId: attId }),
|
|
1123
929
|
name: att.name ?? '',
|
|
1124
930
|
size: att.size ?? 0,
|
|
1125
931
|
contentType: att.contentType ?? 'application/octet-stream',
|
|
@@ -1130,16 +936,14 @@ export class GraphRepository {
|
|
|
1130
936
|
/**
|
|
1131
937
|
* Downloads an attachment for a given email.
|
|
1132
938
|
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
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.
|
|
1136
942
|
*
|
|
1137
943
|
* @returns Metadata about the downloaded file including its local path.
|
|
1138
944
|
*/
|
|
1139
945
|
async downloadAttachmentAsync(attachmentId) {
|
|
1140
|
-
const cached = this.
|
|
1141
|
-
if (cached == null)
|
|
1142
|
-
throw new Error(`Attachment ID ${attachmentId} not found in cache. Call list_attachments first.`);
|
|
946
|
+
const cached = this.toGraphParts(attachmentId, 'attachment', ['messageId', 'attachmentId']);
|
|
1143
947
|
return downloadAttachment(this.client, cached.messageId, cached.attachmentId);
|
|
1144
948
|
}
|
|
1145
949
|
// ===========================================================================
|
|
@@ -1185,13 +989,11 @@ export class GraphRepository {
|
|
|
1185
989
|
graphEvent.onlineMeetingProvider = params.online_meeting_provider ?? 'teamsForBusiness';
|
|
1186
990
|
}
|
|
1187
991
|
const graphCalendarId = params.calendarId != null
|
|
1188
|
-
? this.
|
|
992
|
+
? this.toGraphId(params.calendarId, 'folder')
|
|
1189
993
|
: undefined;
|
|
1190
994
|
const created = await this.client.createEvent(graphEvent, graphCalendarId);
|
|
1191
995
|
const graphId = created.id;
|
|
1192
|
-
|
|
1193
|
-
this.idCache.events.set(numericId, graphId);
|
|
1194
|
-
return numericId;
|
|
996
|
+
return mintSelfEncoded('event', graphId);
|
|
1195
997
|
}
|
|
1196
998
|
/**
|
|
1197
999
|
* Updates an existing calendar event.
|
|
@@ -1200,9 +1002,7 @@ export class GraphRepository {
|
|
|
1200
1002
|
* client.updateEvent(). Throws if the event is not cached.
|
|
1201
1003
|
*/
|
|
1202
1004
|
async updateEventAsync(eventId, updates) {
|
|
1203
|
-
const graphId = this.
|
|
1204
|
-
if (graphId == null)
|
|
1205
|
-
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');
|
|
1206
1006
|
await this.client.updateEvent(graphId, updates);
|
|
1207
1007
|
}
|
|
1208
1008
|
/**
|
|
@@ -1213,11 +1013,8 @@ export class GraphRepository {
|
|
|
1213
1013
|
* Throws if the event is not cached.
|
|
1214
1014
|
*/
|
|
1215
1015
|
async deleteEventAsync(eventId) {
|
|
1216
|
-
const graphId = this.
|
|
1217
|
-
if (graphId == null)
|
|
1218
|
-
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');
|
|
1219
1017
|
await this.client.deleteEvent(graphId);
|
|
1220
|
-
this.idCache.events.delete(eventId);
|
|
1221
1018
|
}
|
|
1222
1019
|
/**
|
|
1223
1020
|
* Responds to a calendar event invitation.
|
|
@@ -1226,9 +1023,7 @@ export class GraphRepository {
|
|
|
1226
1023
|
* client.respondToEvent(). Throws if the event is not cached.
|
|
1227
1024
|
*/
|
|
1228
1025
|
async respondToEventAsync(eventId, response, sendResponse, comment) {
|
|
1229
|
-
const graphId = this.
|
|
1230
|
-
if (graphId == null)
|
|
1231
|
-
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');
|
|
1232
1027
|
await this.client.respondToEvent(graphId, response, sendResponse, comment);
|
|
1233
1028
|
}
|
|
1234
1029
|
// ===========================================================================
|
|
@@ -1273,9 +1068,7 @@ export class GraphRepository {
|
|
|
1273
1068
|
}
|
|
1274
1069
|
const created = await this.client.createContact(graphContact);
|
|
1275
1070
|
const graphId = created.id;
|
|
1276
|
-
|
|
1277
|
-
this.idCache.contacts.set(numericId, graphId);
|
|
1278
|
-
return numericId;
|
|
1071
|
+
return mintSelfEncoded('contact', graphId);
|
|
1279
1072
|
}
|
|
1280
1073
|
/**
|
|
1281
1074
|
* Updates an existing contact.
|
|
@@ -1284,9 +1077,7 @@ export class GraphRepository {
|
|
|
1284
1077
|
* client.updateContact(). Throws if the contact is not cached.
|
|
1285
1078
|
*/
|
|
1286
1079
|
async updateContactAsync(contactId, updates) {
|
|
1287
|
-
const graphId = this.
|
|
1288
|
-
if (graphId == null)
|
|
1289
|
-
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');
|
|
1290
1081
|
await this.client.updateContact(graphId, updates);
|
|
1291
1082
|
}
|
|
1292
1083
|
/**
|
|
@@ -1297,11 +1088,8 @@ export class GraphRepository {
|
|
|
1297
1088
|
* Throws if the contact is not cached.
|
|
1298
1089
|
*/
|
|
1299
1090
|
async deleteContactAsync(contactId) {
|
|
1300
|
-
const graphId = this.
|
|
1301
|
-
if (graphId == null)
|
|
1302
|
-
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');
|
|
1303
1092
|
await this.client.deleteContact(graphId);
|
|
1304
|
-
this.idCache.contacts.delete(contactId);
|
|
1305
1093
|
}
|
|
1306
1094
|
// ===========================================================================
|
|
1307
1095
|
// Task Write Operations (Async)
|
|
@@ -1309,14 +1097,12 @@ export class GraphRepository {
|
|
|
1309
1097
|
/**
|
|
1310
1098
|
* Creates a new task in a task list.
|
|
1311
1099
|
*
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
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.
|
|
1315
1103
|
*/
|
|
1316
1104
|
async createTaskAsync(params) {
|
|
1317
|
-
const graphListId = this.
|
|
1318
|
-
if (graphListId == null)
|
|
1319
|
-
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');
|
|
1320
1106
|
const graphTask = {
|
|
1321
1107
|
title: params.title,
|
|
1322
1108
|
};
|
|
@@ -1362,22 +1148,14 @@ export class GraphRepository {
|
|
|
1362
1148
|
};
|
|
1363
1149
|
}
|
|
1364
1150
|
const created = await this.client.createTask(graphListId, graphTask);
|
|
1365
|
-
|
|
1366
|
-
const numericId = hashStringToNumber(graphId);
|
|
1367
|
-
this.idCache.tasks.set(numericId, { taskListId: graphListId, taskId: graphId });
|
|
1368
|
-
return numericId;
|
|
1151
|
+
return this.mintAliasComposite('task', { taskListId: graphListId, taskId: created.id });
|
|
1369
1152
|
}
|
|
1370
1153
|
/**
|
|
1371
1154
|
* Updates an existing task.
|
|
1372
|
-
*
|
|
1373
|
-
* Looks up the Graph task info from idCache.tasks, then calls
|
|
1374
|
-
* client.updateTask(). Throws if the task is not cached.
|
|
1375
1155
|
*/
|
|
1376
1156
|
async updateTaskAsync(taskId, updates) {
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1380
|
-
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);
|
|
1381
1159
|
}
|
|
1382
1160
|
/**
|
|
1383
1161
|
* Marks a task as completed.
|
|
@@ -1396,64 +1174,42 @@ export class GraphRepository {
|
|
|
1396
1174
|
}
|
|
1397
1175
|
/**
|
|
1398
1176
|
* Deletes a task.
|
|
1399
|
-
*
|
|
1400
|
-
* Looks up the Graph task info from idCache.tasks, calls
|
|
1401
|
-
* client.deleteTask(), and removes the entry from idCache.
|
|
1402
|
-
* Throws if the task is not cached.
|
|
1403
1177
|
*/
|
|
1404
1178
|
async deleteTaskAsync(taskId) {
|
|
1405
|
-
const
|
|
1406
|
-
|
|
1407
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1408
|
-
await this.client.deleteTask(taskInfo.taskListId, taskInfo.taskId);
|
|
1409
|
-
this.idCache.tasks.delete(taskId);
|
|
1179
|
+
const { taskListId, taskId: gTaskId } = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1180
|
+
await this.client.deleteTask(taskListId, gTaskId);
|
|
1410
1181
|
}
|
|
1411
1182
|
/**
|
|
1412
1183
|
* Creates a new task list.
|
|
1413
|
-
*
|
|
1414
|
-
* Calls client.createTaskList(), caches the resulting ID in
|
|
1415
|
-
* idCache.taskLists, and returns a numeric ID.
|
|
1416
1184
|
*/
|
|
1417
1185
|
async createTaskListAsync(displayName) {
|
|
1418
1186
|
const created = await this.client.createTaskList(displayName);
|
|
1419
|
-
|
|
1420
|
-
const numericId = hashStringToNumber(graphId);
|
|
1421
|
-
this.idCache.taskLists.set(numericId, graphId);
|
|
1422
|
-
return numericId;
|
|
1187
|
+
return this.mintAlias('taskList', created.id);
|
|
1423
1188
|
}
|
|
1424
1189
|
/**
|
|
1425
1190
|
* Renames a task list.
|
|
1426
1191
|
*/
|
|
1427
1192
|
async renameTaskListAsync(listId, name) {
|
|
1428
|
-
const graphId = this.
|
|
1429
|
-
if (graphId == null)
|
|
1430
|
-
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');
|
|
1431
1194
|
await this.client.updateTaskList(graphId, { displayName: name });
|
|
1432
1195
|
}
|
|
1433
1196
|
/**
|
|
1434
1197
|
* Deletes a task list.
|
|
1435
1198
|
*/
|
|
1436
1199
|
async deleteTaskListAsync(listId) {
|
|
1437
|
-
const graphId = this.
|
|
1438
|
-
if (graphId == null)
|
|
1439
|
-
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');
|
|
1440
1201
|
await this.client.deleteTaskList(graphId);
|
|
1441
|
-
this.idCache.taskLists.delete(listId);
|
|
1442
1202
|
}
|
|
1443
1203
|
// ===========================================================================
|
|
1444
1204
|
// Checklist Items
|
|
1445
1205
|
// ===========================================================================
|
|
1446
1206
|
async listChecklistItemsAsync(taskId) {
|
|
1447
|
-
const taskInfo = this.
|
|
1448
|
-
if (taskInfo == null)
|
|
1449
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1207
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1450
1208
|
const items = await this.client.listChecklistItems(taskInfo.taskListId, taskInfo.taskId);
|
|
1451
1209
|
return items.map((item) => {
|
|
1452
1210
|
const graphId = item.id;
|
|
1453
|
-
const numericId = hashStringToNumber(graphId);
|
|
1454
|
-
this.idCache.checklistItems.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId });
|
|
1455
1211
|
return {
|
|
1456
|
-
id:
|
|
1212
|
+
id: this.mintAliasComposite('checklistItem', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId }),
|
|
1457
1213
|
displayName: item.displayName ?? '',
|
|
1458
1214
|
isChecked: item.isChecked ?? false,
|
|
1459
1215
|
createdDateTime: item.createdDateTime ?? '',
|
|
@@ -1461,19 +1217,12 @@ export class GraphRepository {
|
|
|
1461
1217
|
});
|
|
1462
1218
|
}
|
|
1463
1219
|
async createChecklistItemAsync(taskId, displayName, isChecked = false) {
|
|
1464
|
-
const taskInfo = this.
|
|
1465
|
-
if (taskInfo == null)
|
|
1466
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1220
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1467
1221
|
const item = await this.client.createChecklistItem(taskInfo.taskListId, taskInfo.taskId, displayName, isChecked);
|
|
1468
|
-
|
|
1469
|
-
const numericId = hashStringToNumber(graphId);
|
|
1470
|
-
this.idCache.checklistItems.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId });
|
|
1471
|
-
return numericId;
|
|
1222
|
+
return this.mintAliasComposite('checklistItem', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: item.id });
|
|
1472
1223
|
}
|
|
1473
1224
|
async updateChecklistItemAsync(checklistItemId, updates) {
|
|
1474
|
-
const cached = this.
|
|
1475
|
-
if (cached == null)
|
|
1476
|
-
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']);
|
|
1477
1226
|
const graphUpdates = {};
|
|
1478
1227
|
if (updates.displayName != null)
|
|
1479
1228
|
graphUpdates['displayName'] = updates.displayName;
|
|
@@ -1482,26 +1231,19 @@ export class GraphRepository {
|
|
|
1482
1231
|
await this.client.updateChecklistItem(cached.taskListId, cached.taskId, cached.checklistItemId, graphUpdates);
|
|
1483
1232
|
}
|
|
1484
1233
|
async deleteChecklistItemAsync(checklistItemId) {
|
|
1485
|
-
const cached = this.
|
|
1486
|
-
if (cached == null)
|
|
1487
|
-
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']);
|
|
1488
1235
|
await this.client.deleteChecklistItem(cached.taskListId, cached.taskId, cached.checklistItemId);
|
|
1489
|
-
this.idCache.checklistItems.delete(checklistItemId);
|
|
1490
1236
|
}
|
|
1491
1237
|
// ===========================================================================
|
|
1492
1238
|
// Linked Resources
|
|
1493
1239
|
// ===========================================================================
|
|
1494
1240
|
async listLinkedResourcesAsync(taskId) {
|
|
1495
|
-
const taskInfo = this.
|
|
1496
|
-
if (taskInfo == null)
|
|
1497
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1241
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1498
1242
|
const items = await this.client.listLinkedResources(taskInfo.taskListId, taskInfo.taskId);
|
|
1499
1243
|
return items.map((item) => {
|
|
1500
1244
|
const graphId = item.id;
|
|
1501
|
-
const numericId = hashStringToNumber(graphId);
|
|
1502
|
-
this.idCache.linkedResources.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId });
|
|
1503
1245
|
return {
|
|
1504
|
-
id:
|
|
1246
|
+
id: this.mintAliasComposite('linkedResource', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId }),
|
|
1505
1247
|
webUrl: item.webUrl ?? '',
|
|
1506
1248
|
applicationName: item.applicationName ?? '',
|
|
1507
1249
|
displayName: item.displayName ?? '',
|
|
@@ -1509,36 +1251,24 @@ export class GraphRepository {
|
|
|
1509
1251
|
});
|
|
1510
1252
|
}
|
|
1511
1253
|
async createLinkedResourceAsync(taskId, webUrl, applicationName, displayName) {
|
|
1512
|
-
const taskInfo = this.
|
|
1513
|
-
if (taskInfo == null)
|
|
1514
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1254
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1515
1255
|
const item = await this.client.createLinkedResource(taskInfo.taskListId, taskInfo.taskId, webUrl, applicationName, displayName);
|
|
1516
|
-
|
|
1517
|
-
const numericId = hashStringToNumber(graphId);
|
|
1518
|
-
this.idCache.linkedResources.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId });
|
|
1519
|
-
return numericId;
|
|
1256
|
+
return this.mintAliasComposite('linkedResource', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: item.id });
|
|
1520
1257
|
}
|
|
1521
1258
|
async deleteLinkedResourceAsync(linkedResourceId) {
|
|
1522
|
-
const cached = this.
|
|
1523
|
-
if (cached == null)
|
|
1524
|
-
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']);
|
|
1525
1260
|
await this.client.deleteLinkedResource(cached.taskListId, cached.taskId, cached.linkedResourceId);
|
|
1526
|
-
this.idCache.linkedResources.delete(linkedResourceId);
|
|
1527
1261
|
}
|
|
1528
1262
|
// ===========================================================================
|
|
1529
1263
|
// Task Attachments
|
|
1530
1264
|
// ===========================================================================
|
|
1531
1265
|
async listTaskAttachmentsAsync(taskId) {
|
|
1532
|
-
const taskInfo = this.
|
|
1533
|
-
if (taskInfo == null)
|
|
1534
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1266
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1535
1267
|
const items = await this.client.listTaskAttachments(taskInfo.taskListId, taskInfo.taskId);
|
|
1536
1268
|
return items.map((item) => {
|
|
1537
1269
|
const graphId = item.id;
|
|
1538
|
-
const numericId = hashStringToNumber(graphId);
|
|
1539
|
-
this.idCache.taskAttachments.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId });
|
|
1540
1270
|
return {
|
|
1541
|
-
id:
|
|
1271
|
+
id: this.mintAliasComposite('taskAttachment', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId }),
|
|
1542
1272
|
name: item['name'] ?? '',
|
|
1543
1273
|
size: item.size ?? 0,
|
|
1544
1274
|
contentType: item.contentType ?? '',
|
|
@@ -1546,21 +1276,13 @@ export class GraphRepository {
|
|
|
1546
1276
|
});
|
|
1547
1277
|
}
|
|
1548
1278
|
async createTaskAttachmentAsync(taskId, name, contentBytes, contentType) {
|
|
1549
|
-
const taskInfo = this.
|
|
1550
|
-
if (taskInfo == null)
|
|
1551
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1279
|
+
const taskInfo = this.toGraphParts(taskId, 'task', ['taskListId', 'taskId']);
|
|
1552
1280
|
const item = await this.client.createTaskAttachment(taskInfo.taskListId, taskInfo.taskId, name, contentBytes, contentType);
|
|
1553
|
-
|
|
1554
|
-
const numericId = hashStringToNumber(graphId);
|
|
1555
|
-
this.idCache.taskAttachments.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId });
|
|
1556
|
-
return numericId;
|
|
1281
|
+
return this.mintAliasComposite('taskAttachment', { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: item.id });
|
|
1557
1282
|
}
|
|
1558
1283
|
async deleteTaskAttachmentAsync(taskAttachmentId) {
|
|
1559
|
-
const cached = this.
|
|
1560
|
-
if (cached == null)
|
|
1561
|
-
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']);
|
|
1562
1285
|
await this.client.deleteTaskAttachment(cached.taskListId, cached.taskId, cached.attachmentId);
|
|
1563
|
-
this.idCache.taskAttachments.delete(taskAttachmentId);
|
|
1564
1286
|
}
|
|
1565
1287
|
// ===========================================================================
|
|
1566
1288
|
// Mail Rules (Async)
|
|
@@ -1572,10 +1294,8 @@ export class GraphRepository {
|
|
|
1572
1294
|
const rules = await this.client.listMailRules();
|
|
1573
1295
|
return rules.map((rule) => {
|
|
1574
1296
|
const graphId = rule.id;
|
|
1575
|
-
const numericId = hashStringToNumber(graphId);
|
|
1576
|
-
this.idCache.rules.set(numericId, graphId);
|
|
1577
1297
|
return {
|
|
1578
|
-
id:
|
|
1298
|
+
id: this.mintAlias('mailRule', graphId),
|
|
1579
1299
|
displayName: rule.displayName ?? '',
|
|
1580
1300
|
sequence: rule.sequence ?? 0,
|
|
1581
1301
|
isEnabled: rule.isEnabled ?? true,
|
|
@@ -1589,20 +1309,14 @@ export class GraphRepository {
|
|
|
1589
1309
|
*/
|
|
1590
1310
|
async createMailRuleAsync(rule) {
|
|
1591
1311
|
const created = await this.client.createMailRule(rule);
|
|
1592
|
-
|
|
1593
|
-
const numericId = hashStringToNumber(graphId);
|
|
1594
|
-
this.idCache.rules.set(numericId, graphId);
|
|
1595
|
-
return numericId;
|
|
1312
|
+
return this.mintAlias('mailRule', created.id);
|
|
1596
1313
|
}
|
|
1597
1314
|
/**
|
|
1598
1315
|
* Deletes an inbox mail rule.
|
|
1599
1316
|
*/
|
|
1600
1317
|
async deleteMailRuleAsync(ruleId) {
|
|
1601
|
-
const graphId = this.
|
|
1602
|
-
if (graphId == null)
|
|
1603
|
-
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');
|
|
1604
1319
|
await this.client.deleteMailRule(graphId);
|
|
1605
|
-
this.idCache.rules.delete(ruleId);
|
|
1606
1320
|
}
|
|
1607
1321
|
// ===========================================================================
|
|
1608
1322
|
// Automatic Replies (Out of Office)
|
|
@@ -1682,10 +1396,8 @@ export class GraphRepository {
|
|
|
1682
1396
|
const categories = await this.client.listMasterCategories();
|
|
1683
1397
|
return categories.map((cat) => {
|
|
1684
1398
|
const graphId = cat.id;
|
|
1685
|
-
const numericId = hashStringToNumber(graphId);
|
|
1686
|
-
this.idCache.categories.set(numericId, graphId);
|
|
1687
1399
|
return {
|
|
1688
|
-
id:
|
|
1400
|
+
id: this.mintAlias('category', graphId),
|
|
1689
1401
|
name: cat.displayName ?? '',
|
|
1690
1402
|
color: cat.color ?? 'none',
|
|
1691
1403
|
};
|
|
@@ -1696,20 +1408,14 @@ export class GraphRepository {
|
|
|
1696
1408
|
*/
|
|
1697
1409
|
async createCategoryAsync(name, color) {
|
|
1698
1410
|
const created = await this.client.createMasterCategory(name, color);
|
|
1699
|
-
|
|
1700
|
-
const numericId = hashStringToNumber(graphId);
|
|
1701
|
-
this.idCache.categories.set(numericId, graphId);
|
|
1702
|
-
return numericId;
|
|
1411
|
+
return this.mintAlias('category', created.id);
|
|
1703
1412
|
}
|
|
1704
1413
|
/**
|
|
1705
1414
|
* Deletes a master category.
|
|
1706
1415
|
*/
|
|
1707
1416
|
async deleteCategoryAsync(categoryId) {
|
|
1708
|
-
const graphId = this.
|
|
1709
|
-
if (graphId == null)
|
|
1710
|
-
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');
|
|
1711
1418
|
await this.client.deleteMasterCategory(graphId);
|
|
1712
|
-
this.idCache.categories.delete(categoryId);
|
|
1713
1419
|
}
|
|
1714
1420
|
// ===========================================================================
|
|
1715
1421
|
// Focused Inbox Overrides
|
|
@@ -1721,10 +1427,8 @@ export class GraphRepository {
|
|
|
1721
1427
|
const overrides = await this.client.listFocusedOverrides();
|
|
1722
1428
|
return overrides.map((o) => {
|
|
1723
1429
|
const graphId = o.id;
|
|
1724
|
-
const numericId = hashStringToNumber(graphId);
|
|
1725
|
-
this.idCache.focusedOverrides.set(numericId, graphId);
|
|
1726
1430
|
return {
|
|
1727
|
-
id:
|
|
1431
|
+
id: this.mintAlias('focusedOverride', graphId),
|
|
1728
1432
|
senderAddress: o.senderEmailAddress?.address ?? '',
|
|
1729
1433
|
classifyAs: o.classifyAs ?? '',
|
|
1730
1434
|
};
|
|
@@ -1735,20 +1439,14 @@ export class GraphRepository {
|
|
|
1735
1439
|
*/
|
|
1736
1440
|
async createFocusedOverrideAsync(senderAddress, classifyAs) {
|
|
1737
1441
|
const created = await this.client.createFocusedOverride(senderAddress, classifyAs);
|
|
1738
|
-
|
|
1739
|
-
const numericId = hashStringToNumber(graphId);
|
|
1740
|
-
this.idCache.focusedOverrides.set(numericId, graphId);
|
|
1741
|
-
return numericId;
|
|
1442
|
+
return this.mintAlias('focusedOverride', created.id);
|
|
1742
1443
|
}
|
|
1743
1444
|
/**
|
|
1744
1445
|
* Deletes a focused inbox override.
|
|
1745
1446
|
*/
|
|
1746
1447
|
async deleteFocusedOverrideAsync(overrideId) {
|
|
1747
|
-
const graphId = this.
|
|
1748
|
-
if (graphId == null)
|
|
1749
|
-
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');
|
|
1750
1449
|
await this.client.deleteFocusedOverride(graphId);
|
|
1751
|
-
this.idCache.focusedOverrides.delete(overrideId);
|
|
1752
1450
|
}
|
|
1753
1451
|
// ===========================================================================
|
|
1754
1452
|
// Message Headers & MIME
|
|
@@ -1757,21 +1455,17 @@ export class GraphRepository {
|
|
|
1757
1455
|
* Gets internet message headers for an email.
|
|
1758
1456
|
*/
|
|
1759
1457
|
async getMessageHeadersAsync(emailId) {
|
|
1760
|
-
const graphId = this.
|
|
1761
|
-
if (graphId == null)
|
|
1762
|
-
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');
|
|
1763
1459
|
return await this.client.getMessageHeaders(graphId);
|
|
1764
1460
|
}
|
|
1765
1461
|
/**
|
|
1766
1462
|
* Gets the MIME content of a message and saves it as an .eml file.
|
|
1767
1463
|
*/
|
|
1768
1464
|
async getMessageMimeAsync(emailId) {
|
|
1769
|
-
const graphId = this.
|
|
1770
|
-
if (graphId == null)
|
|
1771
|
-
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');
|
|
1772
1466
|
const mime = await this.client.getMessageMime(graphId);
|
|
1773
1467
|
const downloadDir = getDownloadDir();
|
|
1774
|
-
const filePath = path.join(downloadDir, `email-${
|
|
1468
|
+
const filePath = path.join(downloadDir, `email-${createHash('sha1').update(graphId).digest('hex').slice(0, 16)}.eml`);
|
|
1775
1469
|
fs.writeFileSync(filePath, mime, 'utf-8');
|
|
1776
1470
|
return { filePath };
|
|
1777
1471
|
}
|
|
@@ -1802,15 +1496,16 @@ export class GraphRepository {
|
|
|
1802
1496
|
// ===========================================================================
|
|
1803
1497
|
/**
|
|
1804
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.
|
|
1805
1502
|
*/
|
|
1806
1503
|
async listCalendarGroupsAsync() {
|
|
1807
1504
|
const groups = await this.client.listCalendarGroups();
|
|
1808
1505
|
return groups.map((group) => {
|
|
1809
1506
|
const graphId = group.id;
|
|
1810
|
-
const numericId = hashStringToNumber(graphId);
|
|
1811
|
-
this.idCache.calendarGroups.set(numericId, graphId);
|
|
1812
1507
|
return {
|
|
1813
|
-
id:
|
|
1508
|
+
id: graphId,
|
|
1814
1509
|
name: group.name ?? '',
|
|
1815
1510
|
classId: group.classId?.toString() ?? '',
|
|
1816
1511
|
};
|
|
@@ -1821,10 +1516,7 @@ export class GraphRepository {
|
|
|
1821
1516
|
*/
|
|
1822
1517
|
async createCalendarGroupAsync(name) {
|
|
1823
1518
|
const created = await this.client.createCalendarGroup(name);
|
|
1824
|
-
|
|
1825
|
-
const numericId = hashStringToNumber(graphId);
|
|
1826
|
-
this.idCache.calendarGroups.set(numericId, graphId);
|
|
1827
|
-
return numericId;
|
|
1519
|
+
return created.id;
|
|
1828
1520
|
}
|
|
1829
1521
|
// ===========================================================================
|
|
1830
1522
|
// Calendar Permissions
|
|
@@ -1833,17 +1525,12 @@ export class GraphRepository {
|
|
|
1833
1525
|
* Lists all permissions for a calendar.
|
|
1834
1526
|
*/
|
|
1835
1527
|
async listCalendarPermissionsAsync(calendarId) {
|
|
1836
|
-
const graphCalendarId = this.
|
|
1837
|
-
if (graphCalendarId == null) {
|
|
1838
|
-
throw new Error(`Calendar ID ${calendarId} not found in cache. Please call list_calendars first.`);
|
|
1839
|
-
}
|
|
1528
|
+
const graphCalendarId = this.toGraphId(calendarId, 'folder');
|
|
1840
1529
|
const permissions = await this.client.listCalendarPermissions(graphCalendarId);
|
|
1841
1530
|
return permissions.map((perm) => {
|
|
1842
1531
|
const graphPermId = perm.id;
|
|
1843
|
-
const numericId = hashStringToNumber(graphPermId);
|
|
1844
|
-
this.idCache.calendarPermissions.set(numericId, { calendarId: graphCalendarId, permissionId: graphPermId });
|
|
1845
1532
|
return {
|
|
1846
|
-
id:
|
|
1533
|
+
id: this.mintAliasComposite('calendarPermission', { calendarId: graphCalendarId, permissionId: graphPermId }),
|
|
1847
1534
|
emailAddress: perm.emailAddress?.address ?? '',
|
|
1848
1535
|
role: perm.role ?? 'none',
|
|
1849
1536
|
isRemovable: perm.isRemovable ?? false,
|
|
@@ -1855,10 +1542,7 @@ export class GraphRepository {
|
|
|
1855
1542
|
* Creates a calendar permission (shares a calendar with someone).
|
|
1856
1543
|
*/
|
|
1857
1544
|
async createCalendarPermissionAsync(calendarId, email, role) {
|
|
1858
|
-
const graphCalendarId = this.
|
|
1859
|
-
if (graphCalendarId == null) {
|
|
1860
|
-
throw new Error(`Calendar ID ${calendarId} not found in cache. Please call list_calendars first.`);
|
|
1861
|
-
}
|
|
1545
|
+
const graphCalendarId = this.toGraphId(calendarId, 'folder');
|
|
1862
1546
|
const permission = await this.client.createCalendarPermission(graphCalendarId, {
|
|
1863
1547
|
emailAddress: {
|
|
1864
1548
|
address: email,
|
|
@@ -1866,21 +1550,14 @@ export class GraphRepository {
|
|
|
1866
1550
|
},
|
|
1867
1551
|
role,
|
|
1868
1552
|
});
|
|
1869
|
-
|
|
1870
|
-
const numericId = hashStringToNumber(graphPermId);
|
|
1871
|
-
this.idCache.calendarPermissions.set(numericId, { calendarId: graphCalendarId, permissionId: graphPermId });
|
|
1872
|
-
return numericId;
|
|
1553
|
+
return this.mintAliasComposite('calendarPermission', { calendarId: graphCalendarId, permissionId: permission.id });
|
|
1873
1554
|
}
|
|
1874
1555
|
/**
|
|
1875
1556
|
* Deletes a calendar permission.
|
|
1876
1557
|
*/
|
|
1877
1558
|
async deleteCalendarPermissionAsync(permissionId) {
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
throw new Error(`Calendar permission ID ${permissionId} not found in cache. Please call list_calendar_permissions first.`);
|
|
1881
|
-
}
|
|
1882
|
-
await this.client.deleteCalendarPermission(cached.calendarId, cached.permissionId);
|
|
1883
|
-
this.idCache.calendarPermissions.delete(permissionId);
|
|
1559
|
+
const { calendarId, permissionId: permId } = this.toGraphParts(permissionId, 'calendarPermission', ['calendarId', 'permissionId']);
|
|
1560
|
+
await this.client.deleteCalendarPermission(calendarId, permId);
|
|
1884
1561
|
}
|
|
1885
1562
|
// ===========================================================================
|
|
1886
1563
|
// Room Lists & Rooms
|
|
@@ -1915,9 +1592,7 @@ export class GraphRepository {
|
|
|
1915
1592
|
const teams = await this.client.listJoinedTeams();
|
|
1916
1593
|
return teams.map((team) => {
|
|
1917
1594
|
const graphId = team.id;
|
|
1918
|
-
|
|
1919
|
-
this.idCache.teams.set(numericId, graphId);
|
|
1920
|
-
return { id: numericId, name: team.displayName ?? '', description: team.description ?? '' };
|
|
1595
|
+
return { id: this.mintAlias('team', graphId), name: team.displayName ?? '', description: team.description ?? '' };
|
|
1921
1596
|
});
|
|
1922
1597
|
}
|
|
1923
1598
|
/**
|
|
@@ -1928,20 +1603,17 @@ export class GraphRepository {
|
|
|
1928
1603
|
const channels = await this.client.listChannels(graphTeamId);
|
|
1929
1604
|
return channels.map((ch) => {
|
|
1930
1605
|
const graphId = ch.id;
|
|
1931
|
-
const
|
|
1932
|
-
|
|
1933
|
-
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' };
|
|
1934
1608
|
});
|
|
1935
1609
|
}
|
|
1936
1610
|
/**
|
|
1937
|
-
* Gets a specific channel by
|
|
1611
|
+
* Gets a specific channel by durable cn_ token.
|
|
1938
1612
|
*/
|
|
1939
1613
|
async getChannelAsync(channelId) {
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
const ch = await this.client.getChannel(cached.teamId, cached.channelId);
|
|
1944
|
-
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 ?? '' };
|
|
1945
1617
|
}
|
|
1946
1618
|
/**
|
|
1947
1619
|
* Creates a new channel in a team.
|
|
@@ -1949,34 +1621,26 @@ export class GraphRepository {
|
|
|
1949
1621
|
async createChannelAsync(teamId, name, description) {
|
|
1950
1622
|
const graphTeamId = await this.resolveTeamId(teamId);
|
|
1951
1623
|
const ch = await this.client.createChannel(graphTeamId, name, description);
|
|
1952
|
-
|
|
1953
|
-
const numericId = hashStringToNumber(graphId);
|
|
1954
|
-
this.idCache.channels.set(numericId, { teamId: graphTeamId, channelId: graphId });
|
|
1955
|
-
return numericId;
|
|
1624
|
+
return this.mintAliasComposite('channel', { teamId: graphTeamId, channelId: ch.id });
|
|
1956
1625
|
}
|
|
1957
1626
|
/**
|
|
1958
1627
|
* Updates a channel's properties.
|
|
1959
1628
|
*/
|
|
1960
1629
|
async updateChannelAsync(channelId, updates) {
|
|
1961
|
-
const
|
|
1962
|
-
if (cached == null)
|
|
1963
|
-
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']);
|
|
1964
1631
|
const graphUpdates = {};
|
|
1965
1632
|
if (updates.name != null)
|
|
1966
1633
|
graphUpdates['displayName'] = updates.name;
|
|
1967
1634
|
if (updates.description != null)
|
|
1968
1635
|
graphUpdates['description'] = updates.description;
|
|
1969
|
-
await this.client.updateChannel(
|
|
1636
|
+
await this.client.updateChannel(teamId, graphChannelId, graphUpdates);
|
|
1970
1637
|
}
|
|
1971
1638
|
/**
|
|
1972
1639
|
* Deletes a channel.
|
|
1973
1640
|
*/
|
|
1974
1641
|
async deleteChannelAsync(channelId) {
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1977
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1978
|
-
await this.client.deleteChannel(cached.teamId, cached.channelId);
|
|
1979
|
-
this.idCache.channels.delete(channelId);
|
|
1642
|
+
const { teamId, channelId: graphChannelId } = this.toGraphParts(channelId, 'channel', ['teamId', 'channelId']);
|
|
1643
|
+
await this.client.deleteChannel(teamId, graphChannelId);
|
|
1980
1644
|
}
|
|
1981
1645
|
/**
|
|
1982
1646
|
* Lists members of a team.
|
|
@@ -1998,16 +1662,12 @@ export class GraphRepository {
|
|
|
1998
1662
|
* Lists recent messages in a channel.
|
|
1999
1663
|
*/
|
|
2000
1664
|
async listChannelMessagesAsync(channelId, limit = 25) {
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
2004
|
-
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);
|
|
2005
1667
|
return messages.map((msg) => {
|
|
2006
1668
|
const graphId = msg.id;
|
|
2007
|
-
const numericId = hashStringToNumber(graphId);
|
|
2008
|
-
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2009
1669
|
return {
|
|
2010
|
-
id:
|
|
1670
|
+
id: this.mintAliasComposite('channelMessage', { teamId, channelId: graphChannelId, messageId: graphId }),
|
|
2011
1671
|
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2012
1672
|
senderEmail: msg.from?.user?.email ?? '',
|
|
2013
1673
|
bodyPreview: msg.body?.content?.substring(0, 200) ?? '',
|
|
@@ -2021,18 +1681,21 @@ export class GraphRepository {
|
|
|
2021
1681
|
* Gets a specific channel message with its replies.
|
|
2022
1682
|
*/
|
|
2023
1683
|
async getChannelMessageAsync(messageId) {
|
|
2024
|
-
const
|
|
2025
|
-
if (cached == null)
|
|
2026
|
-
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']);
|
|
2027
1685
|
const [msg, repliesRaw] = await Promise.all([
|
|
2028
|
-
this.client.getChannelMessage(
|
|
2029
|
-
this.client.listChannelMessageReplies(
|
|
1686
|
+
this.client.getChannelMessage(teamId, channelId, graphMessageId),
|
|
1687
|
+
this.client.listChannelMessageReplies(teamId, channelId, graphMessageId),
|
|
2030
1688
|
]);
|
|
2031
1689
|
const replies = repliesRaw.map((r) => {
|
|
2032
1690
|
const rGraphId = r.id;
|
|
2033
|
-
|
|
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.
|
|
2034
1697
|
return {
|
|
2035
|
-
id:
|
|
1698
|
+
id: this.mintAliasComposite('channelMessage', { teamId, channelId, messageId: rGraphId }),
|
|
2036
1699
|
senderName: r.from?.user?.displayName ?? r.from?.application?.displayName ?? '',
|
|
2037
1700
|
senderEmail: r.from?.user?.email ?? '',
|
|
2038
1701
|
bodyContent: r.body?.content ?? '',
|
|
@@ -2041,7 +1704,7 @@ export class GraphRepository {
|
|
|
2041
1704
|
};
|
|
2042
1705
|
});
|
|
2043
1706
|
return {
|
|
2044
|
-
id: messageId,
|
|
1707
|
+
id: String(messageId),
|
|
2045
1708
|
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2046
1709
|
senderEmail: msg.from?.user?.email ?? '',
|
|
2047
1710
|
bodyContent: msg.body?.content ?? '',
|
|
@@ -2054,27 +1717,18 @@ export class GraphRepository {
|
|
|
2054
1717
|
* Sends a new message to a channel.
|
|
2055
1718
|
*/
|
|
2056
1719
|
async sendChannelMessageAsync(channelId, body, contentType = 'html') {
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
2060
|
-
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);
|
|
2061
1722
|
const graphId = msg.id;
|
|
2062
|
-
|
|
2063
|
-
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2064
|
-
return numericId;
|
|
1723
|
+
return this.mintAliasComposite('channelMessage', { teamId, channelId: graphChannelId, messageId: graphId });
|
|
2065
1724
|
}
|
|
2066
1725
|
/**
|
|
2067
1726
|
* Replies to a channel message.
|
|
2068
1727
|
*/
|
|
2069
1728
|
async replyToChannelMessageAsync(messageId, body, contentType = 'html') {
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
const reply = await this.client.replyToChannelMessage(cached.teamId, cached.channelId, cached.messageId, body, contentType);
|
|
2074
|
-
const graphId = reply.id;
|
|
2075
|
-
const numericId = hashStringToNumber(graphId);
|
|
2076
|
-
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2077
|
-
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 });
|
|
2078
1732
|
}
|
|
2079
1733
|
// ===========================================================================
|
|
2080
1734
|
// Chats
|
|
@@ -2083,14 +1737,12 @@ export class GraphRepository {
|
|
|
2083
1737
|
const chats = await this.client.listChats(limit);
|
|
2084
1738
|
return chats.map((chat) => {
|
|
2085
1739
|
const graphId = chat.id;
|
|
2086
|
-
const numericId = hashStringToNumber(graphId);
|
|
2087
|
-
this.idCache.chats.set(numericId, graphId);
|
|
2088
1740
|
const chatRecord = chat;
|
|
2089
1741
|
const preview = chatRecord.lastMessagePreview;
|
|
2090
1742
|
const previewBody = preview?.body;
|
|
2091
1743
|
const previewContent = previewBody?.content?.substring(0, 200) ?? '';
|
|
2092
1744
|
return {
|
|
2093
|
-
id:
|
|
1745
|
+
id: this.mintAlias('chat', graphId),
|
|
2094
1746
|
topic: chat.topic ?? '',
|
|
2095
1747
|
chatType: chat.chatType ?? 'oneOnOne',
|
|
2096
1748
|
lastMessagePreview: previewContent,
|
|
@@ -2102,7 +1754,7 @@ export class GraphRepository {
|
|
|
2102
1754
|
const graphId = await this.resolveChatId(chatId);
|
|
2103
1755
|
const chat = await this.client.getChat(graphId);
|
|
2104
1756
|
return {
|
|
2105
|
-
id: chatId,
|
|
1757
|
+
id: String(chatId),
|
|
2106
1758
|
topic: chat.topic ?? '',
|
|
2107
1759
|
chatType: chat.chatType ?? 'oneOnOne',
|
|
2108
1760
|
createdDateTime: chat.createdDateTime ?? '',
|
|
@@ -2114,10 +1766,8 @@ export class GraphRepository {
|
|
|
2114
1766
|
const messages = await this.client.listChatMessages(graphChatId, limit);
|
|
2115
1767
|
return messages.map((msg) => {
|
|
2116
1768
|
const graphId = msg.id;
|
|
2117
|
-
const numericId = hashStringToNumber(graphId);
|
|
2118
|
-
this.idCache.chatMessages.set(numericId, { chatId: graphChatId, messageId: graphId });
|
|
2119
1769
|
return {
|
|
2120
|
-
id:
|
|
1770
|
+
id: this.mintAliasComposite('chatMessage', { chatId: graphChatId, messageId: graphId }),
|
|
2121
1771
|
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2122
1772
|
senderEmail: msg.from?.user?.email ?? '',
|
|
2123
1773
|
bodyPreview: msg.body?.content?.substring(0, 200) ?? '',
|
|
@@ -2131,9 +1781,7 @@ export class GraphRepository {
|
|
|
2131
1781
|
const graphChatId = await this.resolveChatId(chatId);
|
|
2132
1782
|
const msg = await this.client.sendChatMessage(graphChatId, body, contentType);
|
|
2133
1783
|
const graphId = msg.id;
|
|
2134
|
-
|
|
2135
|
-
this.idCache.chatMessages.set(numericId, { chatId: graphChatId, messageId: graphId });
|
|
2136
|
-
return numericId;
|
|
1784
|
+
return this.mintAliasComposite('chatMessage', { chatId: graphChatId, messageId: graphId });
|
|
2137
1785
|
}
|
|
2138
1786
|
// ===========================================================================
|
|
2139
1787
|
// Message Reactions
|
|
@@ -2152,46 +1800,34 @@ export class GraphRepository {
|
|
|
2152
1800
|
});
|
|
2153
1801
|
};
|
|
2154
1802
|
if (messageType === 'channel') {
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2158
|
-
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);
|
|
2159
1805
|
return mapReactions(msg);
|
|
2160
1806
|
}
|
|
2161
1807
|
else {
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2165
|
-
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);
|
|
2166
1810
|
return mapReactions(msg);
|
|
2167
1811
|
}
|
|
2168
1812
|
}
|
|
2169
1813
|
async addMessageReactionAsync(messageId, messageType, reactionType) {
|
|
2170
1814
|
if (messageType === 'channel') {
|
|
2171
|
-
const
|
|
2172
|
-
|
|
2173
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2174
|
-
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);
|
|
2175
1817
|
}
|
|
2176
1818
|
else {
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2180
|
-
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);
|
|
2181
1821
|
}
|
|
2182
1822
|
}
|
|
2183
1823
|
async removeMessageReactionAsync(messageId, messageType, reactionType) {
|
|
2184
1824
|
if (messageType === 'channel') {
|
|
2185
|
-
const
|
|
2186
|
-
|
|
2187
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2188
|
-
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);
|
|
2189
1827
|
}
|
|
2190
1828
|
else {
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2194
|
-
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);
|
|
2195
1831
|
}
|
|
2196
1832
|
}
|
|
2197
1833
|
async listChatMembersAsync(chatId) {
|
|
@@ -2207,17 +1843,14 @@ export class GraphRepository {
|
|
|
2207
1843
|
// Planner Plans
|
|
2208
1844
|
// ===========================================================================
|
|
2209
1845
|
/**
|
|
2210
|
-
* Lists all plans the current user has.
|
|
1846
|
+
* Lists all plans the current user has, minting durable pl_ tokens.
|
|
2211
1847
|
*/
|
|
2212
1848
|
async listPlansAsync() {
|
|
2213
1849
|
const plans = await this.client.listPlans();
|
|
2214
1850
|
return plans.map((plan) => {
|
|
2215
1851
|
const graphId = plan.id;
|
|
2216
|
-
const numericId = hashStringToNumber(graphId);
|
|
2217
|
-
const etag = plan['@odata.etag'] ?? '';
|
|
2218
|
-
this.idCache.plans.set(numericId, { planId: graphId, etag });
|
|
2219
1852
|
return {
|
|
2220
|
-
id:
|
|
1853
|
+
id: this.mintAlias('plan', graphId),
|
|
2221
1854
|
title: plan.title ?? '',
|
|
2222
1855
|
owner: plan.owner ?? '',
|
|
2223
1856
|
createdDateTime: plan.createdDateTime ?? '',
|
|
@@ -2225,19 +1858,17 @@ export class GraphRepository {
|
|
|
2225
1858
|
});
|
|
2226
1859
|
}
|
|
2227
1860
|
/**
|
|
2228
|
-
* Gets a specific plan
|
|
1861
|
+
* Gets a specific plan.
|
|
2229
1862
|
*/
|
|
2230
1863
|
async getPlanAsync(planId) {
|
|
2231
|
-
const
|
|
2232
|
-
const plan = await this.client.getPlan(
|
|
2233
|
-
const etag = plan['@odata.etag'] ?? '';
|
|
2234
|
-
this.idCache.plans.set(planId, { planId: cached.planId, etag });
|
|
1864
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1865
|
+
const plan = await this.client.getPlan(graphPlanId);
|
|
2235
1866
|
return {
|
|
2236
|
-
id: planId,
|
|
1867
|
+
id: String(planId),
|
|
2237
1868
|
title: plan.title ?? '',
|
|
2238
1869
|
owner: plan.owner ?? '',
|
|
2239
1870
|
createdDateTime: plan.createdDateTime ?? '',
|
|
2240
|
-
etag,
|
|
1871
|
+
etag: this.extractEtag(plan),
|
|
2241
1872
|
};
|
|
2242
1873
|
}
|
|
2243
1874
|
/**
|
|
@@ -2245,42 +1876,34 @@ export class GraphRepository {
|
|
|
2245
1876
|
*/
|
|
2246
1877
|
async createPlanAsync(title, groupId) {
|
|
2247
1878
|
const plan = await this.client.createPlan(title, groupId);
|
|
2248
|
-
|
|
2249
|
-
const numericId = hashStringToNumber(graphId);
|
|
2250
|
-
const etag = plan['@odata.etag'] ?? '';
|
|
2251
|
-
this.idCache.plans.set(numericId, { planId: graphId, etag });
|
|
2252
|
-
return numericId;
|
|
1879
|
+
return this.mintAlias('plan', plan.id);
|
|
2253
1880
|
}
|
|
2254
1881
|
/**
|
|
2255
|
-
* Updates a plan (
|
|
1882
|
+
* Updates a plan (U5b-5: fetches a fresh etag immediately before the write —
|
|
1883
|
+
* etags are never cached across calls).
|
|
2256
1884
|
*/
|
|
2257
1885
|
async updatePlanAsync(planId, updates) {
|
|
2258
|
-
const
|
|
1886
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
2259
1887
|
const graphUpdates = {};
|
|
2260
1888
|
if (updates.title != null)
|
|
2261
1889
|
graphUpdates['title'] = updates.title;
|
|
2262
|
-
|
|
2263
|
-
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2264
|
-
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));
|
|
2265
1891
|
}
|
|
2266
1892
|
// ===========================================================================
|
|
2267
1893
|
// Planner Buckets
|
|
2268
1894
|
// ===========================================================================
|
|
2269
1895
|
/**
|
|
2270
|
-
* Lists all buckets in a plan.
|
|
1896
|
+
* Lists all buckets in a plan, minting durable pb_ tokens.
|
|
2271
1897
|
*/
|
|
2272
1898
|
async listBucketsAsync(planId) {
|
|
2273
|
-
const
|
|
2274
|
-
const buckets = await this.client.listBuckets(
|
|
1899
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1900
|
+
const buckets = await this.client.listBuckets(graphPlanId);
|
|
2275
1901
|
return buckets.map((bucket) => {
|
|
2276
1902
|
const graphId = bucket.id;
|
|
2277
|
-
const numericId = hashStringToNumber(graphId);
|
|
2278
|
-
const etag = bucket['@odata.etag'] ?? '';
|
|
2279
|
-
this.idCache.plannerBuckets.set(numericId, { planId: cached.planId, bucketId: graphId, etag });
|
|
2280
1903
|
return {
|
|
2281
|
-
id:
|
|
1904
|
+
id: this.mintAlias('plannerBucket', graphId),
|
|
2282
1905
|
name: bucket.name ?? '',
|
|
2283
|
-
planId,
|
|
1906
|
+
planId: String(planId),
|
|
2284
1907
|
orderHint: bucket.orderHint ?? '',
|
|
2285
1908
|
};
|
|
2286
1909
|
});
|
|
@@ -2289,61 +1912,42 @@ export class GraphRepository {
|
|
|
2289
1912
|
* Creates a new bucket in a plan.
|
|
2290
1913
|
*/
|
|
2291
1914
|
async createBucketAsync(planId, name) {
|
|
2292
|
-
const
|
|
2293
|
-
const bucket = await this.client.createBucket(
|
|
2294
|
-
|
|
2295
|
-
const numericId = hashStringToNumber(graphId);
|
|
2296
|
-
const etag = bucket['@odata.etag'] ?? '';
|
|
2297
|
-
this.idCache.plannerBuckets.set(numericId, { planId: cached.planId, bucketId: graphId, etag });
|
|
2298
|
-
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);
|
|
2299
1918
|
}
|
|
2300
1919
|
/**
|
|
2301
|
-
* Updates a bucket (
|
|
1920
|
+
* Updates a bucket (U5b-5: fetches a fresh etag immediately before the write).
|
|
2302
1921
|
*/
|
|
2303
1922
|
async updateBucketAsync(bucketId, updates) {
|
|
2304
|
-
const
|
|
2305
|
-
if (cached == null)
|
|
2306
|
-
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
1923
|
+
const graphBucketId = this.toGraphId(bucketId, 'plannerBucket');
|
|
2307
1924
|
const graphUpdates = {};
|
|
2308
1925
|
if (updates.name != null)
|
|
2309
1926
|
graphUpdates['name'] = updates.name;
|
|
2310
|
-
|
|
2311
|
-
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2312
|
-
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));
|
|
2313
1928
|
}
|
|
2314
1929
|
/**
|
|
2315
|
-
* Deletes a bucket (
|
|
1930
|
+
* Deletes a bucket (U5b-5: fetches a fresh etag immediately before the write).
|
|
2316
1931
|
*/
|
|
2317
1932
|
async deleteBucketAsync(bucketId) {
|
|
2318
|
-
const
|
|
2319
|
-
|
|
2320
|
-
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2321
|
-
await this.client.deleteBucket(cached.bucketId, cached.etag);
|
|
2322
|
-
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));
|
|
2323
1935
|
}
|
|
2324
1936
|
// ===========================================================================
|
|
2325
1937
|
// Planner Tasks
|
|
2326
1938
|
// ===========================================================================
|
|
2327
1939
|
/**
|
|
2328
|
-
* Lists all tasks in a plan.
|
|
1940
|
+
* Lists all tasks in a plan, minting durable pt_ tokens.
|
|
2329
1941
|
*/
|
|
2330
1942
|
async listPlannerTasksAsync(planId) {
|
|
2331
|
-
const
|
|
2332
|
-
const tasks = await this.client.listPlannerTasks(
|
|
1943
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
1944
|
+
const tasks = await this.client.listPlannerTasks(graphPlanId);
|
|
2333
1945
|
return tasks.map((task) => {
|
|
2334
1946
|
const graphId = task.id;
|
|
2335
|
-
const numericId = hashStringToNumber(graphId);
|
|
2336
|
-
const etag = task['@odata.etag'] ?? '';
|
|
2337
|
-
this.idCache.plannerTasks.set(numericId, { taskId: graphId, etag });
|
|
2338
|
-
// Resolve bucket numeric ID if present
|
|
2339
|
-
let bucketNumericId = null;
|
|
2340
|
-
if (task.bucketId != null) {
|
|
2341
|
-
bucketNumericId = hashStringToNumber(task.bucketId);
|
|
2342
|
-
}
|
|
2343
1947
|
return {
|
|
2344
|
-
id:
|
|
1948
|
+
id: this.mintAlias('plannerTask', graphId),
|
|
2345
1949
|
title: task.title ?? '',
|
|
2346
|
-
bucketId:
|
|
1950
|
+
bucketId: task.bucketId != null ? this.mintAlias('plannerBucket', task.bucketId) : null,
|
|
2347
1951
|
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
2348
1952
|
percentComplete: task.percentComplete ?? 0,
|
|
2349
1953
|
priority: task.priority ?? 5,
|
|
@@ -2354,23 +1958,39 @@ export class GraphRepository {
|
|
|
2354
1958
|
});
|
|
2355
1959
|
}
|
|
2356
1960
|
/**
|
|
2357
|
-
*
|
|
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.
|
|
2358
1986
|
*/
|
|
2359
1987
|
async getPlannerTaskAsync(taskId) {
|
|
2360
|
-
const
|
|
2361
|
-
|
|
2362
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2363
|
-
const task = await this.client.getPlannerTask(cached.taskId);
|
|
2364
|
-
const etag = task['@odata.etag'] ?? '';
|
|
2365
|
-
this.idCache.plannerTasks.set(taskId, { taskId: cached.taskId, etag });
|
|
2366
|
-
let bucketNumericId = null;
|
|
2367
|
-
if (task.bucketId != null) {
|
|
2368
|
-
bucketNumericId = hashStringToNumber(task.bucketId);
|
|
2369
|
-
}
|
|
1988
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
1989
|
+
const task = await this.client.getPlannerTask(gTaskId);
|
|
2370
1990
|
return {
|
|
2371
|
-
id: taskId,
|
|
1991
|
+
id: String(taskId),
|
|
2372
1992
|
title: task.title ?? '',
|
|
2373
|
-
bucketId:
|
|
1993
|
+
bucketId: task.bucketId != null ? this.mintAlias('plannerBucket', task.bucketId) : null,
|
|
2374
1994
|
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
2375
1995
|
percentComplete: task.percentComplete ?? 0,
|
|
2376
1996
|
priority: task.priority ?? 5,
|
|
@@ -2379,20 +1999,17 @@ export class GraphRepository {
|
|
|
2379
1999
|
createdDateTime: task.createdDateTime ?? '',
|
|
2380
2000
|
conversationThreadId: task.conversationThreadId ?? '',
|
|
2381
2001
|
orderHint: task.orderHint ?? '',
|
|
2382
|
-
etag,
|
|
2002
|
+
etag: this.extractEtag(task),
|
|
2383
2003
|
};
|
|
2384
2004
|
}
|
|
2385
2005
|
/**
|
|
2386
2006
|
* Creates a new planner task.
|
|
2387
2007
|
*/
|
|
2388
2008
|
async createPlannerTaskAsync(planId, title, bucketId, assignments, priority, startDate, dueDate) {
|
|
2389
|
-
const
|
|
2390
|
-
const body = { planId:
|
|
2009
|
+
const graphPlanId = await this.resolvePlanId(planId);
|
|
2010
|
+
const body = { planId: graphPlanId, title };
|
|
2391
2011
|
if (bucketId != null) {
|
|
2392
|
-
|
|
2393
|
-
if (cachedBucket == null)
|
|
2394
|
-
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2395
|
-
body.bucketId = cachedBucket.bucketId;
|
|
2012
|
+
body.bucketId = this.toGraphId(bucketId, 'plannerBucket');
|
|
2396
2013
|
}
|
|
2397
2014
|
if (assignments != null)
|
|
2398
2015
|
body.assignments = assignments;
|
|
@@ -2403,28 +2020,18 @@ export class GraphRepository {
|
|
|
2403
2020
|
if (dueDate != null)
|
|
2404
2021
|
body.dueDateTime = dueDate;
|
|
2405
2022
|
const task = await this.client.createPlannerTask(body);
|
|
2406
|
-
|
|
2407
|
-
const numericId = hashStringToNumber(graphId);
|
|
2408
|
-
const etag = task['@odata.etag'] ?? '';
|
|
2409
|
-
this.idCache.plannerTasks.set(numericId, { taskId: graphId, etag });
|
|
2410
|
-
return numericId;
|
|
2023
|
+
return this.mintAlias('plannerTask', task.id);
|
|
2411
2024
|
}
|
|
2412
2025
|
/**
|
|
2413
|
-
* Updates a planner task (
|
|
2026
|
+
* Updates a planner task (U5b-5: fetches a fresh etag immediately before the write).
|
|
2414
2027
|
*/
|
|
2415
2028
|
async updatePlannerTaskAsync(taskId, updates) {
|
|
2416
|
-
const
|
|
2417
|
-
if (cached == null)
|
|
2418
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2029
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
2419
2030
|
const graphUpdates = {};
|
|
2420
2031
|
if (updates.title != null)
|
|
2421
2032
|
graphUpdates['title'] = updates.title;
|
|
2422
|
-
if (updates.bucketId != null)
|
|
2423
|
-
|
|
2424
|
-
if (cachedBucket == null)
|
|
2425
|
-
throw new Error(`Bucket ID ${updates.bucketId} not found in cache. Try listing buckets first.`);
|
|
2426
|
-
graphUpdates['bucketId'] = cachedBucket.bucketId;
|
|
2427
|
-
}
|
|
2033
|
+
if (updates.bucketId != null)
|
|
2034
|
+
graphUpdates['bucketId'] = this.toGraphId(updates.bucketId, 'plannerBucket');
|
|
2428
2035
|
if (updates.percentComplete != null)
|
|
2429
2036
|
graphUpdates['percentComplete'] = updates.percentComplete;
|
|
2430
2037
|
if (updates.priority != null)
|
|
@@ -2435,39 +2042,31 @@ export class GraphRepository {
|
|
|
2435
2042
|
graphUpdates['dueDateTime'] = updates.dueDate;
|
|
2436
2043
|
if (updates.assignments != null)
|
|
2437
2044
|
graphUpdates['assignments'] = updates.assignments;
|
|
2438
|
-
|
|
2439
|
-
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2440
|
-
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));
|
|
2441
2046
|
}
|
|
2442
2047
|
/**
|
|
2443
|
-
* Deletes a planner task (
|
|
2048
|
+
* Deletes a planner task (U5b-5: fetches a fresh etag immediately before the write).
|
|
2444
2049
|
*/
|
|
2445
2050
|
async deletePlannerTaskAsync(taskId) {
|
|
2446
|
-
const
|
|
2447
|
-
|
|
2448
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2449
|
-
await this.client.deletePlannerTask(cached.taskId, cached.etag);
|
|
2450
|
-
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));
|
|
2451
2053
|
}
|
|
2452
2054
|
// ===========================================================================
|
|
2453
2055
|
// Planner Task Details
|
|
2454
2056
|
// ===========================================================================
|
|
2455
2057
|
/**
|
|
2456
|
-
* 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.
|
|
2457
2060
|
*/
|
|
2458
2061
|
async getPlannerTaskDetailsAsync(taskId) {
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2462
|
-
const details = await this.client.getPlannerTaskDetails(cached.taskId);
|
|
2463
|
-
const etag = details['@odata.etag'] ?? '';
|
|
2464
|
-
this.idCache.plannerTaskDetails.set(taskId, { taskId: cached.taskId, etag });
|
|
2062
|
+
const gTaskId = this.toGraphId(taskId, 'plannerTask');
|
|
2063
|
+
const details = await this.client.getPlannerTaskDetails(gTaskId);
|
|
2465
2064
|
return {
|
|
2466
|
-
id: taskId,
|
|
2065
|
+
id: String(taskId),
|
|
2467
2066
|
description: details.description ?? '',
|
|
2468
2067
|
checklist: details.checklist ?? {},
|
|
2469
2068
|
references: details.references ?? {},
|
|
2470
|
-
etag,
|
|
2069
|
+
etag: this.extractEtag(details),
|
|
2471
2070
|
};
|
|
2472
2071
|
}
|
|
2473
2072
|
/**
|
|
@@ -2480,17 +2079,15 @@ export class GraphRepository {
|
|
|
2480
2079
|
* have `details` set to `undefined`.
|
|
2481
2080
|
*/
|
|
2482
2081
|
async listPlannerTasksWithDetailsAsync(planId) {
|
|
2483
|
-
// Step 1: List all tasks (
|
|
2082
|
+
// Step 1: List all tasks (mints pt_ tokens, registered in the alias store)
|
|
2484
2083
|
const tasks = await this.listPlannerTasksAsync(planId);
|
|
2485
2084
|
if (tasks.length === 0)
|
|
2486
2085
|
return [];
|
|
2487
2086
|
// Step 2: Build batch requests for each task's details
|
|
2488
2087
|
const batchRequests = tasks.map((task) => {
|
|
2489
|
-
const
|
|
2490
|
-
// cached is guaranteed to exist since listPlannerTasksAsync just populated it
|
|
2491
|
-
const graphTaskId = cached.taskId;
|
|
2088
|
+
const graphTaskId = this.toGraphId(task.id, 'plannerTask');
|
|
2492
2089
|
return {
|
|
2493
|
-
id:
|
|
2090
|
+
id: task.id,
|
|
2494
2091
|
method: 'GET',
|
|
2495
2092
|
url: `/planner/tasks/${graphTaskId}/details`,
|
|
2496
2093
|
};
|
|
@@ -2499,15 +2096,10 @@ export class GraphRepository {
|
|
|
2499
2096
|
const batchResults = await this.client.batchRequests(batchRequests);
|
|
2500
2097
|
// Step 4: Merge details into tasks
|
|
2501
2098
|
return tasks.map((task) => {
|
|
2502
|
-
const result = batchResults.get(
|
|
2099
|
+
const result = batchResults.get(task.id);
|
|
2503
2100
|
if (result != null && result.status >= 200 && result.status < 300) {
|
|
2504
2101
|
const detailBody = result.body;
|
|
2505
2102
|
const etag = result.headers?.ETag ?? result.headers?.etag ?? '';
|
|
2506
|
-
// Cache the task detail ETag for later updates
|
|
2507
|
-
const cachedTask = this.idCache.plannerTasks.get(task.id);
|
|
2508
|
-
if (cachedTask != null) {
|
|
2509
|
-
this.idCache.plannerTaskDetails.set(task.id, { taskId: cachedTask.taskId, etag });
|
|
2510
|
-
}
|
|
2511
2103
|
return {
|
|
2512
2104
|
...task,
|
|
2513
2105
|
details: {
|
|
@@ -2523,15 +2115,12 @@ export class GraphRepository {
|
|
|
2523
2115
|
});
|
|
2524
2116
|
}
|
|
2525
2117
|
/**
|
|
2526
|
-
* 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).
|
|
2527
2121
|
*/
|
|
2528
2122
|
async updatePlannerTaskDetailsAsync(taskId, updates) {
|
|
2529
|
-
const
|
|
2530
|
-
if (cachedTask == null)
|
|
2531
|
-
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2532
|
-
const cachedDetails = this.idCache.plannerTaskDetails.get(taskId);
|
|
2533
|
-
if (cachedDetails == null)
|
|
2534
|
-
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');
|
|
2535
2124
|
const graphUpdates = {};
|
|
2536
2125
|
if (updates.description != null)
|
|
2537
2126
|
graphUpdates['description'] = updates.description;
|
|
@@ -2539,9 +2128,7 @@ export class GraphRepository {
|
|
|
2539
2128
|
graphUpdates['checklist'] = updates.checklist;
|
|
2540
2129
|
if (updates.references != null)
|
|
2541
2130
|
graphUpdates['references'] = updates.references;
|
|
2542
|
-
|
|
2543
|
-
const newEtag = result['@odata.etag'] ?? cachedDetails.etag;
|
|
2544
|
-
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));
|
|
2545
2132
|
}
|
|
2546
2133
|
// ===========================================================================
|
|
2547
2134
|
// Planner Visualization Data
|
|
@@ -2566,7 +2153,7 @@ export class GraphRepository {
|
|
|
2566
2153
|
tasks: tasks.map(t => ({
|
|
2567
2154
|
id: t.id,
|
|
2568
2155
|
title: t.title,
|
|
2569
|
-
bucketId: t.bucketId ??
|
|
2156
|
+
bucketId: t.bucketId ?? '',
|
|
2570
2157
|
percentComplete: t.percentComplete,
|
|
2571
2158
|
priority: t.priority,
|
|
2572
2159
|
startDateTime: t.startDateTime || null,
|
|
@@ -2582,10 +2169,8 @@ export class GraphRepository {
|
|
|
2582
2169
|
const meetings = await this.client.listOnlineMeetings(limit ?? 20);
|
|
2583
2170
|
return meetings.map((meeting) => {
|
|
2584
2171
|
const graphId = meeting.id ?? '';
|
|
2585
|
-
const numericId = hashStringToNumber(graphId);
|
|
2586
|
-
this.idCache.onlineMeetings.set(numericId, graphId);
|
|
2587
2172
|
return {
|
|
2588
|
-
id:
|
|
2173
|
+
id: this.mintAlias('onlineMeeting', graphId),
|
|
2589
2174
|
subject: meeting.subject ?? '',
|
|
2590
2175
|
startDateTime: meeting.startDateTime ?? '',
|
|
2591
2176
|
endDateTime: meeting.endDateTime ?? '',
|
|
@@ -2595,103 +2180,96 @@ export class GraphRepository {
|
|
|
2595
2180
|
}
|
|
2596
2181
|
async getOnlineMeetingAsync(meetingId) {
|
|
2597
2182
|
const mapMeeting = (m) => ({
|
|
2598
|
-
id: meetingId,
|
|
2183
|
+
id: String(meetingId),
|
|
2599
2184
|
subject: m.subject ?? '',
|
|
2600
2185
|
startDateTime: m.startDateTime ?? '',
|
|
2601
2186
|
endDateTime: m.endDateTime ?? '',
|
|
2602
2187
|
joinUrl: m.joinWebUrl ?? '',
|
|
2603
2188
|
participants: m.participants ?? null,
|
|
2604
2189
|
});
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
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
|
+
}
|
|
2614
2214
|
}
|
|
2615
2215
|
const meeting = await this.client.getOnlineMeeting(graphId);
|
|
2616
2216
|
return mapMeeting(meeting);
|
|
2617
2217
|
}
|
|
2618
2218
|
async listMeetingRecordingsAsync(meetingId) {
|
|
2619
|
-
const graphMeetingId = this.
|
|
2620
|
-
if (graphMeetingId == null)
|
|
2621
|
-
throw new Error(`Meeting ID ${meetingId} not found in cache. Try listing online meetings first.`);
|
|
2219
|
+
const graphMeetingId = this.toGraphId(meetingId, 'onlineMeeting');
|
|
2622
2220
|
const recordings = await this.client.listMeetingRecordings(graphMeetingId);
|
|
2623
2221
|
return recordings.map((recording) => {
|
|
2624
2222
|
const graphId = recording.id ?? '';
|
|
2625
|
-
const numericId = hashStringToNumber(graphId);
|
|
2626
|
-
this.idCache.recordings.set(numericId, { meetingId: graphMeetingId, recordingId: graphId });
|
|
2627
2223
|
return {
|
|
2628
|
-
id:
|
|
2224
|
+
id: this.mintAliasComposite('recording', { meetingId: graphMeetingId, recordingId: graphId }),
|
|
2629
2225
|
createdDateTime: recording.createdDateTime ?? '',
|
|
2630
2226
|
recordingContentUrl: recording.recordingContentUrl ?? '',
|
|
2631
2227
|
};
|
|
2632
2228
|
});
|
|
2633
2229
|
}
|
|
2634
2230
|
async downloadMeetingRecordingAsync(recordingId, outputPath) {
|
|
2635
|
-
const
|
|
2636
|
-
|
|
2637
|
-
throw new Error(`Recording ID ${recordingId} not found in cache. Call list_meeting_recordings first.`);
|
|
2638
|
-
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);
|
|
2639
2233
|
fs.writeFileSync(outputPath, Buffer.from(content));
|
|
2640
2234
|
return outputPath;
|
|
2641
2235
|
}
|
|
2642
2236
|
async listMeetingTranscriptsAsync(meetingId) {
|
|
2643
|
-
const graphMeetingId = this.
|
|
2644
|
-
if (graphMeetingId == null)
|
|
2645
|
-
throw new Error(`Meeting ID ${meetingId} not found in cache. Try listing online meetings first.`);
|
|
2237
|
+
const graphMeetingId = this.toGraphId(meetingId, 'onlineMeeting');
|
|
2646
2238
|
const transcripts = await this.client.listMeetingTranscripts(graphMeetingId);
|
|
2647
2239
|
return transcripts.map((transcript) => {
|
|
2648
2240
|
const graphId = transcript.id ?? '';
|
|
2649
|
-
const numericId = hashStringToNumber(graphId);
|
|
2650
|
-
this.idCache.transcripts.set(numericId, { meetingId: graphMeetingId, transcriptId: graphId });
|
|
2651
2241
|
return {
|
|
2652
|
-
id:
|
|
2242
|
+
id: this.mintAliasComposite('transcript', { meetingId: graphMeetingId, transcriptId: graphId }),
|
|
2653
2243
|
createdDateTime: transcript.createdDateTime ?? '',
|
|
2654
2244
|
contentUrl: transcript.contentUrl ?? '',
|
|
2655
2245
|
};
|
|
2656
2246
|
});
|
|
2657
2247
|
}
|
|
2658
2248
|
async getMeetingTranscriptContentAsync(transcriptId, format) {
|
|
2659
|
-
const
|
|
2660
|
-
|
|
2661
|
-
throw new Error(`Transcript ID ${transcriptId} not found in cache. Call list_meeting_transcripts first.`);
|
|
2662
|
-
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');
|
|
2663
2251
|
}
|
|
2664
2252
|
// ===========================================================================
|
|
2665
2253
|
// Excel Online (Workbook)
|
|
2666
2254
|
// ===========================================================================
|
|
2667
2255
|
async listWorksheetsAsync(fileId) {
|
|
2668
|
-
const driveItemId = this.
|
|
2669
|
-
if (driveItemId == null)
|
|
2670
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2256
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2671
2257
|
return await this.client.listWorksheets(driveItemId);
|
|
2672
2258
|
}
|
|
2673
2259
|
async getWorksheetRangeAsync(fileId, worksheetName, range) {
|
|
2674
|
-
const driveItemId = this.
|
|
2675
|
-
if (driveItemId == null)
|
|
2676
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2260
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2677
2261
|
return await this.client.getWorksheetRange(driveItemId, worksheetName, range);
|
|
2678
2262
|
}
|
|
2679
2263
|
async getUsedRangeAsync(fileId, worksheetName) {
|
|
2680
|
-
const driveItemId = this.
|
|
2681
|
-
if (driveItemId == null)
|
|
2682
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2264
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2683
2265
|
return await this.client.getUsedRange(driveItemId, worksheetName);
|
|
2684
2266
|
}
|
|
2685
2267
|
async updateWorksheetRangeAsync(fileId, worksheetName, range, values) {
|
|
2686
|
-
const driveItemId = this.
|
|
2687
|
-
if (driveItemId == null)
|
|
2688
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2268
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2689
2269
|
return await this.client.updateWorksheetRange(driveItemId, worksheetName, range, values);
|
|
2690
2270
|
}
|
|
2691
2271
|
async getTableDataAsync(fileId, tableName) {
|
|
2692
|
-
const driveItemId = this.
|
|
2693
|
-
if (driveItemId == null)
|
|
2694
|
-
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2272
|
+
const driveItemId = this.toGraphId(fileId, 'driveItem');
|
|
2695
2273
|
return await this.client.getTableData(driveItemId, tableName);
|
|
2696
2274
|
}
|
|
2697
2275
|
// OneDrive
|
|
@@ -2700,19 +2278,15 @@ export class GraphRepository {
|
|
|
2700
2278
|
* Lists files/folders in a drive folder (or root).
|
|
2701
2279
|
*/
|
|
2702
2280
|
async listDriveItemsAsync(folderId) {
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
if (graphFolderId == null)
|
|
2707
|
-
throw new Error(`Drive item ID ${folderId} not found in cache. Try listing drive items first.`);
|
|
2708
|
-
}
|
|
2281
|
+
const graphFolderId = folderId != null
|
|
2282
|
+
? this.toGraphId(folderId, 'driveItem')
|
|
2283
|
+
: undefined;
|
|
2709
2284
|
const items = await this.client.listDriveItems(graphFolderId);
|
|
2285
|
+
// Rows carry self-encoding dr_ tokens (minted here) — no cache.
|
|
2710
2286
|
return items.map((item) => {
|
|
2711
2287
|
const itemId = item.id;
|
|
2712
|
-
const numericId = hashStringToNumber(itemId);
|
|
2713
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2714
2288
|
return {
|
|
2715
|
-
id:
|
|
2289
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2716
2290
|
name: item.name ?? '',
|
|
2717
2291
|
size: item.size ?? 0,
|
|
2718
2292
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2728,10 +2302,8 @@ export class GraphRepository {
|
|
|
2728
2302
|
const items = await this.client.searchDriveItems(query, limit);
|
|
2729
2303
|
return items.map((item) => {
|
|
2730
2304
|
const itemId = item.id;
|
|
2731
|
-
const numericId = hashStringToNumber(itemId);
|
|
2732
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2733
2305
|
return {
|
|
2734
|
-
id:
|
|
2306
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2735
2307
|
name: item.name ?? '',
|
|
2736
2308
|
size: item.size ?? 0,
|
|
2737
2309
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2744,15 +2316,13 @@ export class GraphRepository {
|
|
|
2744
2316
|
* Gets metadata for a specific drive item.
|
|
2745
2317
|
*/
|
|
2746
2318
|
async getDriveItemAsync(itemId) {
|
|
2747
|
-
const graphId = this.
|
|
2748
|
-
if (graphId == null)
|
|
2749
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2319
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2750
2320
|
const item = await this.client.getDriveItem(graphId);
|
|
2751
2321
|
const fileObj = item.file;
|
|
2752
2322
|
const createdByObj = item.createdBy;
|
|
2753
2323
|
const createdByUser = createdByObj?.user;
|
|
2754
2324
|
return {
|
|
2755
|
-
id:
|
|
2325
|
+
id: mintSelfEncoded('driveItem', graphId),
|
|
2756
2326
|
name: item.name ?? '',
|
|
2757
2327
|
size: item.size ?? 0,
|
|
2758
2328
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2766,9 +2336,7 @@ export class GraphRepository {
|
|
|
2766
2336
|
* Downloads a drive item to a local file.
|
|
2767
2337
|
*/
|
|
2768
2338
|
async downloadFileAsync(itemId, outputPath) {
|
|
2769
|
-
const graphId = this.
|
|
2770
|
-
if (graphId == null)
|
|
2771
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2339
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2772
2340
|
const content = await this.client.downloadDriveItem(graphId);
|
|
2773
2341
|
const buffer = Buffer.from(content);
|
|
2774
2342
|
fs.writeFileSync(outputPath, buffer);
|
|
@@ -2781,9 +2349,7 @@ export class GraphRepository {
|
|
|
2781
2349
|
const content = fs.readFileSync(localFilePath);
|
|
2782
2350
|
const result = await this.client.uploadDriveItem(parentPath, fileName, content);
|
|
2783
2351
|
const resultId = result.id;
|
|
2784
|
-
|
|
2785
|
-
this.idCache.driveItems.set(numericId, resultId);
|
|
2786
|
-
return numericId;
|
|
2352
|
+
return mintSelfEncoded('driveItem', resultId);
|
|
2787
2353
|
}
|
|
2788
2354
|
/**
|
|
2789
2355
|
* Lists recently accessed drive items.
|
|
@@ -2792,10 +2358,8 @@ export class GraphRepository {
|
|
|
2792
2358
|
const items = await this.client.listRecentDriveItems();
|
|
2793
2359
|
return items.map((item) => {
|
|
2794
2360
|
const itemId = item.id;
|
|
2795
|
-
const numericId = hashStringToNumber(itemId);
|
|
2796
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2797
2361
|
return {
|
|
2798
|
-
id:
|
|
2362
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2799
2363
|
name: item.name ?? '',
|
|
2800
2364
|
size: item.size ?? 0,
|
|
2801
2365
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2811,10 +2375,8 @@ export class GraphRepository {
|
|
|
2811
2375
|
const items = await this.client.listSharedWithMe();
|
|
2812
2376
|
return items.map((item) => {
|
|
2813
2377
|
const itemId = item.id;
|
|
2814
|
-
const numericId = hashStringToNumber(itemId);
|
|
2815
|
-
this.idCache.driveItems.set(numericId, itemId);
|
|
2816
2378
|
return {
|
|
2817
|
-
id:
|
|
2379
|
+
id: itemId.length > 0 ? mintSelfEncoded('driveItem', itemId) : '',
|
|
2818
2380
|
name: item.name ?? '',
|
|
2819
2381
|
size: item.size ?? 0,
|
|
2820
2382
|
lastModified: item.lastModifiedDateTime ?? '',
|
|
@@ -2827,9 +2389,7 @@ export class GraphRepository {
|
|
|
2827
2389
|
* Creates a sharing link for a drive item.
|
|
2828
2390
|
*/
|
|
2829
2391
|
async createSharingLinkAsync(itemId, type, scope) {
|
|
2830
|
-
const graphId = this.
|
|
2831
|
-
if (graphId == null)
|
|
2832
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2392
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2833
2393
|
const result = await this.client.createSharingLink(graphId, type, scope);
|
|
2834
2394
|
const link = result.link;
|
|
2835
2395
|
return {
|
|
@@ -2842,17 +2402,14 @@ export class GraphRepository {
|
|
|
2842
2402
|
* Deletes a drive item.
|
|
2843
2403
|
*/
|
|
2844
2404
|
async deleteDriveItemAsync(itemId) {
|
|
2845
|
-
const graphId = this.
|
|
2846
|
-
if (graphId == null)
|
|
2847
|
-
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2405
|
+
const graphId = this.toGraphId(itemId, 'driveItem');
|
|
2848
2406
|
await this.client.deleteDriveItem(graphId);
|
|
2849
|
-
this.idCache.driveItems.delete(itemId);
|
|
2850
2407
|
}
|
|
2851
2408
|
// ===========================================================================
|
|
2852
2409
|
// SharePoint Sites & Document Libraries
|
|
2853
2410
|
// ===========================================================================
|
|
2854
2411
|
/**
|
|
2855
|
-
* Lists followed SharePoint sites,
|
|
2412
|
+
* Lists followed SharePoint sites, minting durable si_ tokens.
|
|
2856
2413
|
*/
|
|
2857
2414
|
async listSitesAsync() {
|
|
2858
2415
|
const sites = await this.client.listFollowedSites();
|
|
@@ -2860,10 +2417,8 @@ export class GraphRepository {
|
|
|
2860
2417
|
for (const site of sites) {
|
|
2861
2418
|
const siteId = site.id;
|
|
2862
2419
|
if (siteId != null) {
|
|
2863
|
-
const numericId = hashStringToNumber(siteId);
|
|
2864
|
-
this.idCache.sites.set(numericId, siteId);
|
|
2865
2420
|
result.push({
|
|
2866
|
-
id:
|
|
2421
|
+
id: this.mintAlias('site', siteId),
|
|
2867
2422
|
name: site.name ?? '',
|
|
2868
2423
|
webUrl: site.webUrl ?? '',
|
|
2869
2424
|
displayName: site.displayName ?? '',
|
|
@@ -2873,7 +2428,7 @@ export class GraphRepository {
|
|
|
2873
2428
|
return result;
|
|
2874
2429
|
}
|
|
2875
2430
|
/**
|
|
2876
|
-
* Searches SharePoint sites by keyword,
|
|
2431
|
+
* Searches SharePoint sites by keyword, minting durable si_ tokens.
|
|
2877
2432
|
*/
|
|
2878
2433
|
async searchSitesAsync(query) {
|
|
2879
2434
|
const sites = await this.client.searchSites(query);
|
|
@@ -2881,10 +2436,8 @@ export class GraphRepository {
|
|
|
2881
2436
|
for (const site of sites) {
|
|
2882
2437
|
const siteId = site.id;
|
|
2883
2438
|
if (siteId != null) {
|
|
2884
|
-
const numericId = hashStringToNumber(siteId);
|
|
2885
|
-
this.idCache.sites.set(numericId, siteId);
|
|
2886
2439
|
result.push({
|
|
2887
|
-
id:
|
|
2440
|
+
id: this.mintAlias('site', siteId),
|
|
2888
2441
|
name: site.name ?? '',
|
|
2889
2442
|
webUrl: site.webUrl ?? '',
|
|
2890
2443
|
displayName: site.displayName ?? '',
|
|
@@ -2897,12 +2450,10 @@ export class GraphRepository {
|
|
|
2897
2450
|
* Gets details for a specific SharePoint site.
|
|
2898
2451
|
*/
|
|
2899
2452
|
async getSiteAsync(siteId) {
|
|
2900
|
-
const graphId = this.
|
|
2901
|
-
if (graphId == null)
|
|
2902
|
-
throw new Error(`Site ID ${siteId} not found in cache. Try listing or searching sites first.`);
|
|
2453
|
+
const graphId = this.toGraphId(siteId, 'site');
|
|
2903
2454
|
const site = await this.client.getSite(graphId);
|
|
2904
2455
|
return {
|
|
2905
|
-
id: siteId,
|
|
2456
|
+
id: String(siteId),
|
|
2906
2457
|
name: site.name ?? '',
|
|
2907
2458
|
webUrl: site.webUrl ?? '',
|
|
2908
2459
|
displayName: site.displayName ?? '',
|
|
@@ -2910,21 +2461,17 @@ export class GraphRepository {
|
|
|
2910
2461
|
};
|
|
2911
2462
|
}
|
|
2912
2463
|
/**
|
|
2913
|
-
* Lists document libraries for a SharePoint site,
|
|
2464
|
+
* Lists document libraries for a SharePoint site, minting durable dl_ tokens.
|
|
2914
2465
|
*/
|
|
2915
2466
|
async listDocumentLibrariesAsync(siteId) {
|
|
2916
|
-
const graphSiteId = this.
|
|
2917
|
-
if (graphSiteId == null)
|
|
2918
|
-
throw new Error(`Site ID ${siteId} not found in cache. Try listing or searching sites first.`);
|
|
2467
|
+
const graphSiteId = this.toGraphId(siteId, 'site');
|
|
2919
2468
|
const drives = await this.client.listDocumentLibraries(graphSiteId);
|
|
2920
2469
|
const result = [];
|
|
2921
2470
|
for (const drive of drives) {
|
|
2922
2471
|
const driveId = drive.id;
|
|
2923
2472
|
if (driveId != null) {
|
|
2924
|
-
const numericId = hashStringToNumber(driveId);
|
|
2925
|
-
this.idCache.documentLibraries.set(numericId, { siteId: graphSiteId, driveId });
|
|
2926
2473
|
result.push({
|
|
2927
|
-
id:
|
|
2474
|
+
id: this.mintAliasComposite('documentLibrary', { siteId: graphSiteId, driveId }),
|
|
2928
2475
|
name: drive.name ?? '',
|
|
2929
2476
|
webUrl: drive.webUrl ?? '',
|
|
2930
2477
|
driveType: drive.driveType ?? '',
|
|
@@ -2934,28 +2481,21 @@ export class GraphRepository {
|
|
|
2934
2481
|
return result;
|
|
2935
2482
|
}
|
|
2936
2483
|
/**
|
|
2937
|
-
* Lists items in a document library or folder,
|
|
2484
|
+
* Lists items in a document library or folder, minting durable li_ tokens.
|
|
2938
2485
|
*/
|
|
2939
2486
|
async listLibraryItemsAsync(libraryId, folderId) {
|
|
2940
|
-
const libCached = this.
|
|
2941
|
-
if (libCached == null)
|
|
2942
|
-
throw new Error(`Library ID ${libraryId} not found in cache. Try listing document libraries first.`);
|
|
2487
|
+
const libCached = this.toGraphParts(libraryId, 'documentLibrary', ['siteId', 'driveId']);
|
|
2943
2488
|
let folderItemId;
|
|
2944
2489
|
if (folderId != null) {
|
|
2945
|
-
|
|
2946
|
-
if (folderCached == null)
|
|
2947
|
-
throw new Error(`Folder ID ${folderId} not found in cache. Try listing library items first.`);
|
|
2948
|
-
folderItemId = folderCached.itemId;
|
|
2490
|
+
folderItemId = this.toGraphParts(folderId, 'libraryDriveItem', ['driveId', 'itemId']).itemId;
|
|
2949
2491
|
}
|
|
2950
2492
|
const items = await this.client.listLibraryItems(libCached.driveId, folderItemId);
|
|
2951
2493
|
const result = [];
|
|
2952
2494
|
for (const item of items) {
|
|
2953
2495
|
const itemGraphId = item.id;
|
|
2954
2496
|
if (itemGraphId != null) {
|
|
2955
|
-
const numericId = hashStringToNumber(itemGraphId);
|
|
2956
|
-
this.idCache.libraryDriveItems.set(numericId, { driveId: libCached.driveId, itemId: itemGraphId });
|
|
2957
2497
|
result.push({
|
|
2958
|
-
id:
|
|
2498
|
+
id: this.mintAliasComposite('libraryDriveItem', { driveId: libCached.driveId, itemId: itemGraphId }),
|
|
2959
2499
|
name: item.name ?? '',
|
|
2960
2500
|
size: item.size ?? 0,
|
|
2961
2501
|
webUrl: item.webUrl ?? '',
|
|
@@ -2970,9 +2510,7 @@ export class GraphRepository {
|
|
|
2970
2510
|
* Downloads a file from a document library to the specified path.
|
|
2971
2511
|
*/
|
|
2972
2512
|
async downloadLibraryFileAsync(itemId, outputPath) {
|
|
2973
|
-
const cached = this.
|
|
2974
|
-
if (cached == null)
|
|
2975
|
-
throw new Error(`Item ID ${itemId} not found in cache. Try listing library items first.`);
|
|
2513
|
+
const cached = this.toGraphParts(itemId, 'libraryDriveItem', ['driveId', 'itemId']);
|
|
2976
2514
|
const content = await this.client.downloadLibraryFile(cached.driveId, cached.itemId);
|
|
2977
2515
|
const resolvedPath = path.resolve(outputPath);
|
|
2978
2516
|
const dir = path.dirname(resolvedPath);
|
|
@@ -2981,16 +2519,171 @@ export class GraphRepository {
|
|
|
2981
2519
|
return resolvedPath;
|
|
2982
2520
|
}
|
|
2983
2521
|
/**
|
|
2984
|
-
*
|
|
2522
|
+
* Resolves a folder id (durable `fd_` token or raw Graph id) to its Graph id.
|
|
2985
2523
|
*/
|
|
2986
2524
|
getFolderGraphId(folderId) {
|
|
2987
|
-
return this.
|
|
2525
|
+
return this.toGraphId(folderId, 'folder');
|
|
2988
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';
|
|
2989
2682
|
}
|
|
2990
2683
|
/**
|
|
2991
2684
|
* Creates a Microsoft Graph API repository.
|
|
2992
2685
|
*/
|
|
2993
|
-
export function createGraphRepository(deviceCodeCallback) {
|
|
2994
|
-
return new GraphRepository(deviceCodeCallback);
|
|
2686
|
+
export function createGraphRepository(deviceCodeCallback, store, accountId) {
|
|
2687
|
+
return new GraphRepository(deviceCodeCallback, store, accountId);
|
|
2995
2688
|
}
|
|
2996
2689
|
//# sourceMappingURL=repository.js.map
|