(config: MicrosoftGraphConfig)
| 156 | }); |
| 157 | |
| 158 | const addGraph = (config: MicrosoftGraphConfig) => |
| 159 | Effect.gen(function* () { |
| 160 | const graph = yield* buildMicrosoftGraphOpenApiSpec(config, httpClientLayer, urlPolicy); |
| 161 | const slug = IntegrationSlug.make(config.slug?.trim() || DEFAULT_MICROSOFT_SLUG); |
| 162 | |
| 163 | const existing = yield* ctx.core.integrations.get(slug); |
| 164 | if (existing) { |
| 165 | return yield* new IntegrationAlreadyExistsError({ slug }); |
| 166 | } |
| 167 | |
| 168 | const specHash = yield* sha256Hex(graph.specText); |
| 169 | const integrationConfig: MicrosoftGraphIntegrationConfig = { |
| 170 | specHash, |
| 171 | sourceUrl: graph.specUrl, |
| 172 | microsoftGraphPresetIds: graph.presetIds, |
| 173 | microsoftGraphCustomScopes: graph.customScopes, |
| 174 | microsoftGraphScopes: graph.scopes, |
| 175 | microsoftGraphExactPaths: graph.exactPaths, |
| 176 | microsoftGraphPathPrefixes: graph.pathPrefixes, |
| 177 | microsoftGraphTagPrefixes: graph.tagPrefixes, |
| 178 | microsoftGraphCoversFullGraph: graph.coversFullGraph, |
| 179 | microsoftGraphAuthorizationUrl: graph.authorizationUrl, |
| 180 | microsoftGraphTokenUrl: graph.tokenUrl, |
| 181 | microsoftGraphClientCredentialsTokenUrl: graph.clientCredentialsTokenUrl, |
| 182 | authenticationTemplate: graph.authenticationTemplate, |
| 183 | ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}), |
| 184 | }; |
| 185 | |
| 186 | yield* ctx.storage.putSpec(specHash, graph.specText); |
| 187 | |
| 188 | const persisted = yield* ctx.transaction( |
| 189 | Effect.gen(function* () { |
| 190 | yield* ctx.core.integrations.register({ |
| 191 | slug, |
| 192 | name: config.name?.trim() || "Microsoft Graph", |
| 193 | description: config.description ?? "Selected Microsoft Graph workloads.", |
| 194 | config: |
| 195 | integrationConfig satisfies MicrosoftGraphIntegrationConfig as IntegrationConfig, |
| 196 | canRemove: true, |
| 197 | canRefresh: true, |
| 198 | }); |
| 199 | return yield* persistGraphOperations(graph, String(slug), specHash); |
| 200 | }), |
| 201 | ); |
| 202 | |
| 203 | // Default the health check to `GET /me` (the canonical Graph identity |
| 204 | // endpoint) when the selected workloads include it, so connections report |
| 205 | // alive/expired + who-am-I out of the box. The operations were streamed |
| 206 | // to storage above, so find the binding there. Best-effort: a Graph |
| 207 | // selection without /me just leaves the check unconfigured (the editor |
| 208 | // remains available). |
| 209 | const meOperation = (yield* ctx.storage.listOperations(String(slug))).find( |
| 210 | (op) => op.binding.method.toLowerCase() === "get" && op.binding.pathTemplate === "/me", |
| 211 | ); |
| 212 | if (meOperation) { |
| 213 | yield* ctx.core.integrations.setHealthCheck(slug, { |
| 214 | operation: meOperation.toolName, |
| 215 | identityField: "userPrincipalName", |
nothing calls this directly
no test coverage detected