@backstage/plugin-catalog-backend-module-msgraph-incremental 0.0.0-nightly-20260507032228

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/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # @backstage/plugin-catalog-backend-module-msgraph-incremental
2
+
3
+ ## 0.0.0-nightly-20260507032228
4
+
5
+ ### Minor Changes
6
+
7
+ - f1279ea: Introduces a cursor-based incremental ingestion provider for Microsoft Graph that processes users and groups one page at a time. Unlike `MicrosoftGraphOrgEntityProvider`, this module never holds the full dataset in memory — each burst processes a single page (up to 999 users or 100 groups). The `@odata.nextLink` cursor is persisted so a pod restart resumes from the last completed page rather than starting over.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/catalog-model@0.0.0-nightly-20260507032228
13
+ - @backstage/plugin-catalog-node@0.0.0-nightly-20260507032228
14
+ - @backstage/plugin-catalog-backend-module-incremental-ingestion@0.0.0-nightly-20260507032228
15
+ - @backstage/plugin-catalog-backend-module-msgraph@0.0.0-nightly-20260507032228
16
+ - @backstage/backend-plugin-api@0.0.0-nightly-20260507032228
17
+ - @backstage/config@0.0.0-nightly-20260507032228
18
+ - @backstage/types@1.2.2
19
+
20
+ ## 0.1.0-next.0
21
+
22
+ ### Minor Changes
23
+
24
+ - f1279ea: Introduces a cursor-based incremental ingestion provider for Microsoft Graph that processes users and groups one page at a time. Unlike `MicrosoftGraphOrgEntityProvider`, this module never holds the full dataset in memory — each burst processes a single page (up to 999 users or 100 groups). The `@odata.nextLink` cursor is persisted so a pod restart resumes from the last completed page rather than starting over.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @backstage/plugin-catalog-backend-module-msgraph-incremental
2
+
3
+ This module incrementally ingests **users** and **groups** from Microsoft Graph
4
+ into the Backstage catalog, one page at a time. It is suitable for large Azure
5
+ AD tenants where holding the full dataset in memory at once is not practical.
6
+
7
+ ## Features
8
+
9
+ - **Cursor-based resumption** — the `@odata.nextLink` URL is persisted as the
10
+ cursor, so a pod restart during ingestion resumes from the last completed page
11
+ rather than starting over.
12
+ - **Memory-efficient** — each burst processes a single page (up to 999 users
13
+ or 100 groups), keeping memory usage flat regardless of tenant size.
14
+ - **Photo support** — user profile photos are fetched with a gated pre-check to
15
+ avoid unnecessary API calls for users without photos.
16
+ - **Transformer extension point** — user, group, organization, and provider
17
+ config transformers can be customised via the
18
+ `microsoftGraphIncrementalEntityProviderTransformExtensionPoint`.
19
+
20
+ ## Prerequisites
21
+
22
+ This module requires the incremental ingestion framework to be installed:
23
+
24
+ ```ts
25
+ backend.add(
26
+ import('@backstage/plugin-catalog-backend-module-incremental-ingestion'),
27
+ );
28
+ ```
29
+
30
+ ## Installation
31
+
32
+ ```ts
33
+ // packages/backend/src/index.ts
34
+ backend.add(
35
+ import('@backstage/plugin-catalog-backend-module-incremental-ingestion'),
36
+ );
37
+ backend.add(
38
+ import('@backstage/plugin-catalog-backend-module-msgraph-incremental'),
39
+ );
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ Uses the same `catalog.providers.microsoftGraphOrg` configuration as
45
+ `@backstage/plugin-catalog-backend-module-msgraph`. See that package's
46
+ documentation for full config reference.
47
+
48
+ ```yaml
49
+ catalog:
50
+ providers:
51
+ microsoftGraphOrg:
52
+ default:
53
+ tenantId: ${AZURE_TENANT_ID}
54
+ clientId: ${AZURE_CLIENT_ID}
55
+ clientSecret: ${AZURE_CLIENT_SECRET}
56
+ queryMode: advanced
57
+ user:
58
+ filter: 'accountEnabled eq true'
59
+ group:
60
+ filter: 'securityEnabled eq true'
61
+ schedule:
62
+ frequency: { hours: 12 }
63
+ timeout: { hours: 4 }
64
+ ```
65
+
66
+ ## Differences from `MicrosoftGraphOrgEntityProvider`
67
+
68
+ | | `MicrosoftGraphOrgEntityProvider` | This module |
69
+ | -------------------------- | --------------------------------- | ------------------- |
70
+ | Memory usage | Full dataset in RAM | One page at a time |
71
+ | Resume on restart | Starts from scratch | Resumes from cursor |
72
+ | `userGroupMember*` options | Supported | Not supported |
73
+ | `groupIncludeSubGroups` | Supported | Not supported |
74
+ | Suitable for large tenants | No | Yes |
@@ -0,0 +1,310 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('node:crypto');
4
+ var catalogModel = require('@backstage/catalog-model');
5
+ var limiterFactory = require('p-limit');
6
+ var pluginCatalogBackendModuleMsgraph = require('@backstage/plugin-catalog-backend-module-msgraph');
7
+ var clientHelpers = require('./clientHelpers.cjs.js');
8
+
9
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
+
11
+ var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
12
+ var limiterFactory__default = /*#__PURE__*/_interopDefaultCompat(limiterFactory);
13
+
14
+ const USER_PAGE_SIZE = 999;
15
+ const GROUP_PAGE_SIZE = 100;
16
+ function capEntityName(name) {
17
+ if (name.length <= 63) return name;
18
+ const hash = crypto__default.default.createHash("sha1").update(name).digest("hex").slice(0, 8);
19
+ return `${name.slice(0, 54)}_${hash}`;
20
+ }
21
+ function withLocations(providerId, entity) {
22
+ const uid = entity.metadata.annotations?.[pluginCatalogBackendModuleMsgraph.MICROSOFT_GRAPH_USER_ID_ANNOTATION] || entity.metadata.annotations?.[pluginCatalogBackendModuleMsgraph.MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] || entity.metadata.annotations?.[pluginCatalogBackendModuleMsgraph.MICROSOFT_GRAPH_TENANT_ID_ANNOTATION] || entity.metadata.name;
23
+ const location = `msgraph:${providerId}/${encodeURIComponent(uid)}`;
24
+ return {
25
+ ...entity,
26
+ metadata: {
27
+ ...entity.metadata,
28
+ annotations: {
29
+ ...entity.metadata.annotations,
30
+ [catalogModel.ANNOTATION_LOCATION]: location,
31
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: location
32
+ }
33
+ }
34
+ };
35
+ }
36
+ class MicrosoftGraphIncrementalEntityProvider {
37
+ /**
38
+ * Create one provider instance per provider entry in
39
+ * `catalog.providers.microsoftGraphOrg`.
40
+ */
41
+ static fromConfig(configRoot, options) {
42
+ function getTransformer(id, transformers) {
43
+ if (["undefined", "function"].includes(typeof transformers)) {
44
+ return transformers;
45
+ }
46
+ return transformers[id];
47
+ }
48
+ return pluginCatalogBackendModuleMsgraph.readProviderConfigs(configRoot).map(
49
+ (providerConfig) => new MicrosoftGraphIncrementalEntityProvider({
50
+ id: providerConfig.id,
51
+ provider: providerConfig,
52
+ logger: options.logger,
53
+ userTransformer: getTransformer(
54
+ providerConfig.id,
55
+ options.userTransformer
56
+ ),
57
+ groupTransformer: getTransformer(
58
+ providerConfig.id,
59
+ options.groupTransformer
60
+ ),
61
+ organizationTransformer: getTransformer(
62
+ providerConfig.id,
63
+ options.organizationTransformer
64
+ ),
65
+ providerConfigTransformer: getTransformer(
66
+ providerConfig.id,
67
+ options.providerConfigTransformer
68
+ )
69
+ })
70
+ );
71
+ }
72
+ options;
73
+ constructor(options) {
74
+ this.options = options;
75
+ }
76
+ /** {@inheritdoc @backstage/plugin-catalog-backend-module-incremental-ingestion#IncrementalEntityProvider.getProviderName} */
77
+ getProviderName() {
78
+ return `MicrosoftGraphIncrementalEntityProvider:${this.options.id}`;
79
+ }
80
+ /**
81
+ * Sets up the Microsoft Graph client for the duration of a full ingestion
82
+ * cycle. The optional `providerConfigTransformer` is applied here so that
83
+ * dynamic config changes (e.g., rotating credentials) take effect at the
84
+ * start of each cycle rather than mid-way through.
85
+ */
86
+ async around(burst) {
87
+ const provider = this.options.providerConfigTransformer ? await this.options.providerConfigTransformer(this.options.provider) : this.options.provider;
88
+ if (provider.userGroupMemberFilter || provider.userGroupMemberSearch || provider.userGroupMemberPath) {
89
+ this.options.logger.warn(
90
+ `${this.getProviderName()}: userGroupMemberFilter/Search/Path are not supported by MicrosoftGraphIncrementalEntityProvider. Users will be fetched via the standard userFilter/userPath options instead. Switch to MicrosoftGraphOrgEntityProvider if you require userGroupMember-based ingestion.`
91
+ );
92
+ }
93
+ if (provider.groupIncludeSubGroups) {
94
+ this.options.logger.warn(
95
+ `${this.getProviderName()}: groupIncludeSubGroups is not supported by MicrosoftGraphIncrementalEntityProvider and will be ignored. Switch to MicrosoftGraphOrgEntityProvider if you require this option.`
96
+ );
97
+ }
98
+ const client = pluginCatalogBackendModuleMsgraph.MicrosoftGraphClient.create(provider);
99
+ await burst({ client, provider });
100
+ }
101
+ /** {@inheritdoc @backstage/plugin-catalog-backend-module-incremental-ingestion#IncrementalEntityProvider.next} */
102
+ async next({ client, provider }, cursor) {
103
+ const phase = cursor?.phase ?? "users";
104
+ const nextLink = cursor?.nextLink;
105
+ if (phase === "users") {
106
+ return this.readUsersPage(client, provider, nextLink);
107
+ }
108
+ return this.readGroupsPage(client, provider, nextLink);
109
+ }
110
+ async readUsersPage(client, provider, nextLink) {
111
+ const { items: rawUsers, nextLink: newNextLink } = await clientHelpers.requestOnePage(
112
+ client,
113
+ provider.userPath ?? "users",
114
+ {
115
+ query: {
116
+ filter: provider.userFilter,
117
+ expand: provider.userExpand,
118
+ select: provider.userSelect,
119
+ top: USER_PAGE_SIZE
120
+ },
121
+ queryMode: provider.queryMode,
122
+ nextLink
123
+ }
124
+ );
125
+ const transformer = this.options.userTransformer ?? pluginCatalogBackendModuleMsgraph.defaultUserTransformer;
126
+ const limiter = limiterFactory__default.default(10);
127
+ const entities = [];
128
+ await Promise.all(
129
+ rawUsers.map(
130
+ (user) => limiter(async () => {
131
+ let userPhoto;
132
+ if (user.id && provider.loadUserPhotos !== false) {
133
+ try {
134
+ userPhoto = await clientHelpers.getUserPhotoGated(client, user.id, 120);
135
+ } catch (e) {
136
+ this.options.logger.debug(
137
+ `${this.getProviderName()}: failed to load photo for user ${user.id}`,
138
+ { error: e }
139
+ );
140
+ }
141
+ }
142
+ const entity = await transformer(user, userPhoto);
143
+ if (entity) {
144
+ entity.metadata.name = capEntityName(entity.metadata.name);
145
+ entities.push({
146
+ locationKey: `msgraph-org-provider:${this.options.id}`,
147
+ entity: withLocations(this.options.id, entity)
148
+ });
149
+ }
150
+ })
151
+ )
152
+ );
153
+ this.options.logger.debug(
154
+ `${this.getProviderName()}: read ${entities.length} users`,
155
+ { phase: "users", hasNextPage: !!newNextLink }
156
+ );
157
+ if (newNextLink) {
158
+ return {
159
+ done: false,
160
+ entities,
161
+ cursor: { phase: "users", nextLink: newNextLink }
162
+ };
163
+ }
164
+ return {
165
+ done: false,
166
+ entities,
167
+ cursor: { phase: "groups" }
168
+ };
169
+ }
170
+ async readGroupsPage(client, provider, nextLink) {
171
+ const { items: rawGroups, nextLink: newNextLink } = await clientHelpers.requestOnePage(
172
+ client,
173
+ provider.groupPath ?? "groups",
174
+ {
175
+ query: {
176
+ filter: provider.groupFilter,
177
+ search: provider.groupSearch,
178
+ expand: provider.groupExpand,
179
+ select: provider.groupSelect,
180
+ top: GROUP_PAGE_SIZE
181
+ },
182
+ queryMode: provider.queryMode,
183
+ nextLink
184
+ }
185
+ );
186
+ const groupTransformer = this.options.groupTransformer ?? pluginCatalogBackendModuleMsgraph.defaultGroupTransformer;
187
+ const userTransformer = this.options.userTransformer ?? pluginCatalogBackendModuleMsgraph.defaultUserTransformer;
188
+ const limiter = limiterFactory__default.default(10);
189
+ const entities = [];
190
+ if (!nextLink) {
191
+ try {
192
+ const organization = await client.getOrganization(provider.tenantId);
193
+ const orgTransformer = this.options.organizationTransformer ?? pluginCatalogBackendModuleMsgraph.defaultOrganizationTransformer;
194
+ const rootGroup = await orgTransformer(organization);
195
+ if (rootGroup) {
196
+ entities.push({
197
+ locationKey: `msgraph-org-provider:${this.options.id}`,
198
+ entity: withLocations(this.options.id, rootGroup)
199
+ });
200
+ }
201
+ } catch (e) {
202
+ this.options.logger.warn(
203
+ `${this.getProviderName()}: failed to read organization root group`,
204
+ { error: e }
205
+ );
206
+ }
207
+ }
208
+ await Promise.all(
209
+ rawGroups.map(
210
+ (group) => limiter(async () => {
211
+ const entity = await groupTransformer(group);
212
+ if (!entity) {
213
+ return;
214
+ }
215
+ entity.metadata.name = capEntityName(entity.metadata.name);
216
+ const userRefs = [];
217
+ const childRefs = [];
218
+ for await (const member of client.getGroupMembers(group.id, {
219
+ top: GROUP_PAGE_SIZE,
220
+ // Request the minimum fields needed by defaultUserTransformer and
221
+ // defaultGroupTransformer so member objects are never sparse.
222
+ select: [
223
+ "id",
224
+ "displayName",
225
+ "mail",
226
+ "mailNickname",
227
+ "userPrincipalName",
228
+ "description",
229
+ "securityEnabled"
230
+ ]
231
+ })) {
232
+ if (member["@odata.type"] === "#microsoft.graph.user") {
233
+ try {
234
+ const userEntity = await userTransformer(
235
+ member
236
+ );
237
+ if (userEntity) {
238
+ userEntity.metadata.name = capEntityName(
239
+ userEntity.metadata.name
240
+ );
241
+ userRefs.push(catalogModel.stringifyEntityRef(userEntity));
242
+ } else {
243
+ this.options.logger.debug(
244
+ `${this.getProviderName()}: group member user ${member.id} could not be transformed (sparse object?), skipping`
245
+ );
246
+ }
247
+ } catch (e) {
248
+ this.options.logger.warn(
249
+ `${this.getProviderName()}: group member user ${member.id} failed to transform, skipping`,
250
+ { error: e }
251
+ );
252
+ }
253
+ } else if (member["@odata.type"] === "#microsoft.graph.group") {
254
+ if (!provider.groupFilter && !provider.groupSearch) {
255
+ try {
256
+ const childEntity = await groupTransformer(
257
+ member
258
+ );
259
+ if (childEntity) {
260
+ childEntity.metadata.name = capEntityName(
261
+ childEntity.metadata.name
262
+ );
263
+ childRefs.push(catalogModel.stringifyEntityRef(childEntity));
264
+ } else {
265
+ this.options.logger.debug(
266
+ `${this.getProviderName()}: group member child group ${member.id} could not be transformed (sparse object?), skipping`
267
+ );
268
+ }
269
+ } catch (e) {
270
+ this.options.logger.warn(
271
+ `${this.getProviderName()}: group member child group ${member.id} failed to transform, skipping`,
272
+ { error: e }
273
+ );
274
+ }
275
+ }
276
+ }
277
+ }
278
+ const existingMembers = Array.isArray(entity.spec?.members) ? entity.spec.members : [];
279
+ const existingChildren = Array.isArray(entity.spec?.children) ? entity.spec.children : [];
280
+ entities.push({
281
+ locationKey: `msgraph-org-provider:${this.options.id}`,
282
+ entity: withLocations(this.options.id, {
283
+ ...entity,
284
+ spec: {
285
+ ...entity.spec,
286
+ members: [.../* @__PURE__ */ new Set([...existingMembers, ...userRefs])],
287
+ children: [.../* @__PURE__ */ new Set([...existingChildren, ...childRefs])]
288
+ }
289
+ })
290
+ });
291
+ })
292
+ )
293
+ );
294
+ this.options.logger.debug(
295
+ `${this.getProviderName()}: read ${rawGroups.length} groups`,
296
+ { phase: "groups", hasNextPage: !!newNextLink }
297
+ );
298
+ if (newNextLink) {
299
+ return {
300
+ done: false,
301
+ entities,
302
+ cursor: { phase: "groups", nextLink: newNextLink }
303
+ };
304
+ }
305
+ return { done: true, entities };
306
+ }
307
+ }
308
+
309
+ exports.MicrosoftGraphIncrementalEntityProvider = MicrosoftGraphIncrementalEntityProvider;
310
+ //# sourceMappingURL=MicrosoftGraphIncrementalEntityProvider.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MicrosoftGraphIncrementalEntityProvider.cjs.js","sources":["../src/MicrosoftGraphIncrementalEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport crypto from 'node:crypto';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n Entity,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport {\n IncrementalEntityProvider,\n EntityIteratorResult,\n} from '@backstage/plugin-catalog-backend-module-incremental-ingestion';\nimport { DeferredEntity } from '@backstage/plugin-catalog-node';\nimport limiterFactory from 'p-limit';\nimport * as MicrosoftGraph from '@microsoft/microsoft-graph-types';\nimport {\n GroupTransformer,\n MICROSOFT_GRAPH_GROUP_ID_ANNOTATION,\n MICROSOFT_GRAPH_TENANT_ID_ANNOTATION,\n MICROSOFT_GRAPH_USER_ID_ANNOTATION,\n MicrosoftGraphClient,\n MicrosoftGraphProviderConfig,\n OrganizationTransformer,\n ProviderConfigTransformer,\n UserTransformer,\n defaultGroupTransformer,\n defaultOrganizationTransformer,\n defaultUserTransformer,\n readProviderConfigs,\n} from '@backstage/plugin-catalog-backend-module-msgraph';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { getUserPhotoGated, requestOnePage } from './clientHelpers';\n\nconst USER_PAGE_SIZE = 999;\n// Groups phase fetches members for every group on the page, so a smaller page\n// size keeps each burst within its time budget.\nconst GROUP_PAGE_SIZE = 100;\n\n/**\n * Backstage entity names must be ≤63 chars ([a-zA-Z0-9] separated by [-_.]).\n * When MS Graph UPNs exceed that (e.g. calendar/booking accounts), we truncate\n * to 54 chars and append an 8-char SHA-1 hash to preserve uniqueness.\n */\nfunction capEntityName(name: string): string {\n if (name.length <= 63) return name;\n const hash = crypto.createHash('sha1').update(name).digest('hex').slice(0, 8);\n return `${name.slice(0, 54)}_${hash}`;\n}\n\n/** Stamps `annotations.backstage.io/location` on an entity using the MS Graph UID. */\nfunction withLocations(providerId: string, entity: Entity): Entity {\n const uid =\n entity.metadata.annotations?.[MICROSOFT_GRAPH_USER_ID_ANNOTATION] ||\n entity.metadata.annotations?.[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] ||\n entity.metadata.annotations?.[MICROSOFT_GRAPH_TENANT_ID_ANNOTATION] ||\n entity.metadata.name;\n const location = `msgraph:${providerId}/${encodeURIComponent(uid)}`;\n return {\n ...entity,\n metadata: {\n ...entity.metadata,\n annotations: {\n ...entity.metadata.annotations,\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n };\n}\n\n/**\n * Pagination cursor used by {@link MicrosoftGraphIncrementalEntityProvider}.\n *\n * The `nextLink` field holds the `@odata.nextLink` URL returned by the\n * Microsoft Graph API, which encodes all state needed to resume a paged\n * request. An absent value means the current phase is starting fresh.\n *\n * @public\n */\nexport type MSGraphCursor = {\n phase: 'users' | 'groups';\n nextLink?: string;\n};\n\n/**\n * Context passed to each burst of {@link MicrosoftGraphIncrementalEntityProvider}.\n *\n * @public\n */\nexport type MSGraphContext = {\n client: MicrosoftGraphClient;\n provider: MicrosoftGraphProviderConfig;\n};\n\n/**\n * Options for {@link MicrosoftGraphIncrementalEntityProvider}.\n *\n * @public\n */\nexport interface MicrosoftGraphIncrementalEntityProviderOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * The function that transforms a user entry in msgraph to an entity.\n * Optionally, you can pass separate transformers per provider ID.\n */\n userTransformer?: UserTransformer | Record<string, UserTransformer>;\n\n /**\n * The function that transforms a group entry in msgraph to an entity.\n * Optionally, you can pass separate transformers per provider ID.\n */\n groupTransformer?: GroupTransformer | Record<string, GroupTransformer>;\n\n /**\n * The function that transforms an organization entry in msgraph to an entity.\n * Optionally, you can pass separate transformers per provider ID.\n */\n organizationTransformer?:\n | OrganizationTransformer\n | Record<string, OrganizationTransformer>;\n\n /**\n * The function that transforms provider config dynamically before each sync.\n * Optionally, you can pass separate transformers per provider ID.\n */\n providerConfigTransformer?:\n | ProviderConfigTransformer\n | Record<string, ProviderConfigTransformer>;\n}\n\n/**\n * Incrementally reads user and group entries out of Microsoft Graph, one page\n * at a time, and provides them as User and Group entities for the catalog.\n *\n * Unlike `MicrosoftGraphOrgEntityProvider`, this provider never holds the full\n * dataset in memory at once. Each burst processes a single page (up to 999\n * users or 100 groups). This makes it suitable for very large tenants and\n * avoids the memory pressure and long-running task issues of the full-scan\n * provider.\n *\n * The Microsoft Graph `@odata.nextLink` URL is stored as the cursor, so a pod\n * restart during ingestion resumes from the last completed page.\n *\n * Group membership (`spec.members`) is resolved inline during the groups phase\n * by fetching the direct members of each group. The catalog's built-in relation\n * stitching derives `spec.memberOf` on users from these group membership lists.\n *\n * @remarks\n * `userGroupMemberFilter`, `userGroupMemberSearch`, `userGroupMemberPath`, and\n * `groupIncludeSubGroups` are not supported. Use `userFilter` / `userPath` to\n * restrict which users are ingested, and `groupFilter` / `groupSearch` to\n * restrict which groups. Switch to `MicrosoftGraphOrgEntityProvider` if you\n * require any of these options.\n *\n * @public\n */\nexport class MicrosoftGraphIncrementalEntityProvider\n implements IncrementalEntityProvider<MSGraphCursor, MSGraphContext>\n{\n /**\n * Create one provider instance per provider entry in\n * `catalog.providers.microsoftGraphOrg`.\n */\n static fromConfig(\n configRoot: Config,\n options: MicrosoftGraphIncrementalEntityProviderOptions,\n ): MicrosoftGraphIncrementalEntityProvider[] {\n function getTransformer<T extends Function>(\n id: string,\n transformers?: T | Record<string, T>,\n ): T | undefined {\n if (['undefined', 'function'].includes(typeof transformers)) {\n return transformers as T;\n }\n return (transformers as Record<string, T>)[id];\n }\n\n return readProviderConfigs(configRoot).map(\n providerConfig =>\n new MicrosoftGraphIncrementalEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger: options.logger,\n userTransformer: getTransformer(\n providerConfig.id,\n options.userTransformer,\n ),\n groupTransformer: getTransformer(\n providerConfig.id,\n options.groupTransformer,\n ),\n organizationTransformer: getTransformer(\n providerConfig.id,\n options.organizationTransformer,\n ),\n providerConfigTransformer: getTransformer(\n providerConfig.id,\n options.providerConfigTransformer,\n ),\n }),\n );\n }\n\n private readonly options: {\n id: string;\n provider: MicrosoftGraphProviderConfig;\n logger: LoggerService;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n providerConfigTransformer?: ProviderConfigTransformer;\n };\n\n constructor(options: {\n id: string;\n provider: MicrosoftGraphProviderConfig;\n logger: LoggerService;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n providerConfigTransformer?: ProviderConfigTransformer;\n }) {\n this.options = options;\n }\n\n /** {@inheritdoc @backstage/plugin-catalog-backend-module-incremental-ingestion#IncrementalEntityProvider.getProviderName} */\n getProviderName(): string {\n return `MicrosoftGraphIncrementalEntityProvider:${this.options.id}`;\n }\n\n /**\n * Sets up the Microsoft Graph client for the duration of a full ingestion\n * cycle. The optional `providerConfigTransformer` is applied here so that\n * dynamic config changes (e.g., rotating credentials) take effect at the\n * start of each cycle rather than mid-way through.\n */\n async around(\n burst: (context: MSGraphContext) => Promise<void>,\n ): Promise<void> {\n const provider = this.options.providerConfigTransformer\n ? await this.options.providerConfigTransformer(this.options.provider)\n : this.options.provider;\n\n if (\n provider.userGroupMemberFilter ||\n provider.userGroupMemberSearch ||\n provider.userGroupMemberPath\n ) {\n this.options.logger.warn(\n `${this.getProviderName()}: userGroupMemberFilter/Search/Path are not supported by ` +\n `MicrosoftGraphIncrementalEntityProvider. Users will be fetched via the standard ` +\n `userFilter/userPath options instead. Switch to MicrosoftGraphOrgEntityProvider if ` +\n `you require userGroupMember-based ingestion.`,\n );\n }\n\n if (provider.groupIncludeSubGroups) {\n this.options.logger.warn(\n `${this.getProviderName()}: groupIncludeSubGroups is not supported by ` +\n `MicrosoftGraphIncrementalEntityProvider and will be ignored. ` +\n `Switch to MicrosoftGraphOrgEntityProvider if you require this option.`,\n );\n }\n\n const client = MicrosoftGraphClient.create(provider);\n await burst({ client, provider });\n }\n\n /** {@inheritdoc @backstage/plugin-catalog-backend-module-incremental-ingestion#IncrementalEntityProvider.next} */\n async next(\n { client, provider }: MSGraphContext,\n cursor?: MSGraphCursor,\n ): Promise<EntityIteratorResult<MSGraphCursor>> {\n const phase = cursor?.phase ?? 'users';\n const nextLink = cursor?.nextLink;\n\n if (phase === 'users') {\n return this.readUsersPage(client, provider, nextLink);\n }\n return this.readGroupsPage(client, provider, nextLink);\n }\n\n private async readUsersPage(\n client: MicrosoftGraphClient,\n provider: MicrosoftGraphProviderConfig,\n nextLink: string | undefined,\n ): Promise<EntityIteratorResult<MSGraphCursor>> {\n const { items: rawUsers, nextLink: newNextLink } =\n await requestOnePage<MicrosoftGraph.User>(\n client,\n provider.userPath ?? 'users',\n {\n query: {\n filter: provider.userFilter,\n expand: provider.userExpand,\n select: provider.userSelect,\n top: USER_PAGE_SIZE,\n },\n queryMode: provider.queryMode,\n nextLink,\n },\n );\n\n const transformer = this.options.userTransformer ?? defaultUserTransformer;\n const limiter = limiterFactory(10);\n const entities: DeferredEntity[] = [];\n\n await Promise.all(\n rawUsers.map(user =>\n limiter(async () => {\n let userPhoto: string | undefined;\n if (user.id && provider.loadUserPhotos !== false) {\n try {\n userPhoto = await getUserPhotoGated(client, user.id, 120);\n } catch (e) {\n this.options.logger.debug(\n `${this.getProviderName()}: failed to load photo for user ${\n user.id\n }`,\n { error: e },\n );\n }\n }\n\n const entity = await transformer(user, userPhoto);\n if (entity) {\n entity.metadata.name = capEntityName(entity.metadata.name);\n entities.push({\n locationKey: `msgraph-org-provider:${this.options.id}`,\n entity: withLocations(this.options.id, entity),\n });\n }\n }),\n ),\n );\n\n this.options.logger.debug(\n `${this.getProviderName()}: read ${entities.length} users`,\n { phase: 'users', hasNextPage: !!newNextLink },\n );\n\n if (newNextLink) {\n return {\n done: false,\n entities,\n cursor: { phase: 'users', nextLink: newNextLink },\n };\n }\n\n return {\n done: false,\n entities,\n cursor: { phase: 'groups' },\n };\n }\n\n private async readGroupsPage(\n client: MicrosoftGraphClient,\n provider: MicrosoftGraphProviderConfig,\n nextLink: string | undefined,\n ): Promise<EntityIteratorResult<MSGraphCursor>> {\n const { items: rawGroups, nextLink: newNextLink } =\n await requestOnePage<MicrosoftGraph.Group>(\n client,\n provider.groupPath ?? 'groups',\n {\n query: {\n filter: provider.groupFilter,\n search: provider.groupSearch,\n expand: provider.groupExpand,\n select: provider.groupSelect,\n top: GROUP_PAGE_SIZE,\n },\n queryMode: provider.queryMode,\n nextLink,\n },\n );\n\n const groupTransformer =\n this.options.groupTransformer ?? defaultGroupTransformer;\n const userTransformer =\n this.options.userTransformer ?? defaultUserTransformer;\n const limiter = limiterFactory(10);\n const entities: DeferredEntity[] = [];\n\n // Emit the tenant root group on the very first groups page\n if (!nextLink) {\n try {\n const organization = await client.getOrganization(provider.tenantId);\n const orgTransformer =\n this.options.organizationTransformer ??\n defaultOrganizationTransformer;\n const rootGroup = await orgTransformer(organization);\n if (rootGroup) {\n entities.push({\n locationKey: `msgraph-org-provider:${this.options.id}`,\n entity: withLocations(this.options.id, rootGroup),\n });\n }\n } catch (e) {\n this.options.logger.warn(\n `${this.getProviderName()}: failed to read organization root group`,\n { error: e },\n );\n }\n }\n\n await Promise.all(\n rawGroups.map(group =>\n limiter(async () => {\n const entity = await groupTransformer(group);\n if (!entity) {\n return;\n }\n entity.metadata.name = capEntityName(entity.metadata.name);\n\n const userRefs: string[] = [];\n const childRefs: string[] = [];\n\n for await (const member of client.getGroupMembers(group.id!, {\n top: GROUP_PAGE_SIZE,\n // Request the minimum fields needed by defaultUserTransformer and\n // defaultGroupTransformer so member objects are never sparse.\n select: [\n 'id',\n 'displayName',\n 'mail',\n 'mailNickname',\n 'userPrincipalName',\n 'description',\n 'securityEnabled',\n ],\n })) {\n if (member['@odata.type'] === '#microsoft.graph.user') {\n try {\n const userEntity = await userTransformer(\n member as MicrosoftGraph.User,\n );\n if (userEntity) {\n userEntity.metadata.name = capEntityName(\n userEntity.metadata.name,\n );\n userRefs.push(stringifyEntityRef(userEntity));\n } else {\n this.options.logger.debug(\n `${this.getProviderName()}: group member user ${\n member.id\n } could not be transformed (sparse object?), skipping`,\n );\n }\n } catch (e) {\n this.options.logger.warn(\n `${this.getProviderName()}: group member user ${\n member.id\n } failed to transform, skipping`,\n { error: e },\n );\n }\n } else if (member['@odata.type'] === '#microsoft.graph.group') {\n // Only emit child refs when no group filter/search is active.\n // With a filter, child groups may not be ingested themselves,\n // which would produce dangling spec.children references.\n if (!provider.groupFilter && !provider.groupSearch) {\n try {\n const childEntity = await groupTransformer(\n member as MicrosoftGraph.Group,\n );\n if (childEntity) {\n childEntity.metadata.name = capEntityName(\n childEntity.metadata.name,\n );\n childRefs.push(stringifyEntityRef(childEntity));\n } else {\n this.options.logger.debug(\n `${this.getProviderName()}: group member child group ${\n member.id\n } could not be transformed (sparse object?), skipping`,\n );\n }\n } catch (e) {\n this.options.logger.warn(\n `${this.getProviderName()}: group member child group ${\n member.id\n } failed to transform, skipping`,\n { error: e },\n );\n }\n }\n }\n }\n\n // Merge fetched membership with any members/children the transformer\n // may have pre-populated, so custom transformers can augment the list.\n const existingMembers = Array.isArray(entity.spec?.members)\n ? (entity.spec.members as string[])\n : [];\n const existingChildren = Array.isArray(entity.spec?.children)\n ? (entity.spec.children as string[])\n : [];\n\n entities.push({\n locationKey: `msgraph-org-provider:${this.options.id}`,\n entity: withLocations(this.options.id, {\n ...entity,\n spec: {\n ...entity.spec,\n members: [...new Set([...existingMembers, ...userRefs])],\n children: [...new Set([...existingChildren, ...childRefs])],\n },\n }),\n });\n }),\n ),\n );\n\n this.options.logger.debug(\n `${this.getProviderName()}: read ${rawGroups.length} groups`,\n { phase: 'groups', hasNextPage: !!newNextLink },\n );\n\n if (newNextLink) {\n return {\n done: false,\n entities,\n cursor: { phase: 'groups', nextLink: newNextLink },\n };\n }\n\n return { done: true, entities };\n }\n}\n"],"names":["crypto","MICROSOFT_GRAPH_USER_ID_ANNOTATION","MICROSOFT_GRAPH_GROUP_ID_ANNOTATION","MICROSOFT_GRAPH_TENANT_ID_ANNOTATION","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","readProviderConfigs","MicrosoftGraphClient","requestOnePage","defaultUserTransformer","limiterFactory","getUserPhotoGated","defaultGroupTransformer","defaultOrganizationTransformer","stringifyEntityRef"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ //# sourceMappingURL=alpha.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alpha.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -0,0 +1,2 @@
1
+
2
+ export { };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ async function requestOnePage(client, path, options = {}) {
4
+ const { query, queryMode, nextLink, signal } = options;
5
+ const appliedQueryMode = query?.search ? "advanced" : queryMode ?? "basic";
6
+ const finalQuery = appliedQueryMode === "advanced" ? { ...query ?? {}, count: true } : query;
7
+ const headers = appliedQueryMode === "advanced" ? { ConsistencyLevel: "eventual" } : {};
8
+ const response = nextLink ? await client.requestRaw(nextLink, headers, 2, signal) : await client.requestApi(path, finalQuery, headers, signal);
9
+ if (response.status !== 200) {
10
+ let message = `HTTP ${response.status}`;
11
+ try {
12
+ const body = await response.json();
13
+ const err = body?.error;
14
+ if (err?.code || err?.message) {
15
+ message = `${err.code} - ${err.message}`;
16
+ }
17
+ } catch {
18
+ }
19
+ throw new Error(
20
+ `Error while reading ${nextLink ?? path} from Microsoft Graph: ${message}`
21
+ );
22
+ }
23
+ const result = await response.json();
24
+ return {
25
+ items: result.value,
26
+ nextLink: result["@odata.nextLink"]
27
+ };
28
+ }
29
+ async function getUserPhotoGated(client, userId, maxSize) {
30
+ const check = await client.requestApi(`users/${userId}/photo`);
31
+ if (check.status === 404) return void 0;
32
+ if (check.status !== 200) {
33
+ throw new Error(
34
+ `Unexpected status ${check.status} when checking photo for user ${userId}`
35
+ );
36
+ }
37
+ return await client.getUserPhotoWithSizeLimit(userId, maxSize);
38
+ }
39
+
40
+ exports.getUserPhotoGated = getUserPhotoGated;
41
+ exports.requestOnePage = requestOnePage;
42
+ //# sourceMappingURL=clientHelpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientHelpers.cjs.js","sources":["../src/clientHelpers.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MicrosoftGraphClient,\n ODataQuery,\n} from '@backstage/plugin-catalog-backend-module-msgraph';\n\n/**\n * Fetches a single page of Graph API results.\n *\n * When `options.nextLink` is provided it is followed directly (all query\n * parameters are already encoded in it). Otherwise the request is built from\n * `options.query`.\n *\n * MS Graph requires `ConsistencyLevel: eventual` + `$count=true` for advanced\n * queries using `ne`/`not` operators in `$filter` or using `$search`.\n */\nexport async function requestOnePage<T>(\n client: MicrosoftGraphClient,\n path: string,\n options: {\n query?: ODataQuery;\n queryMode?: 'basic' | 'advanced';\n nextLink?: string;\n signal?: AbortSignal;\n } = {},\n): Promise<{ items: T[]; nextLink?: string }> {\n const { query, queryMode, nextLink, signal } = options;\n const appliedQueryMode = query?.search ? 'advanced' : queryMode ?? 'basic';\n\n // Microsoft Graph requires $count=true whenever ConsistencyLevel: eventual is set,\n // including plain listing requests with no $filter or $search.\n const finalQuery =\n appliedQueryMode === 'advanced' ? { ...(query ?? {}), count: true } : query;\n\n const headers: Record<string, string> =\n appliedQueryMode === 'advanced' ? { ConsistencyLevel: 'eventual' } : {};\n\n const response = nextLink\n ? await client.requestRaw(nextLink, headers, 2, signal)\n : await client.requestApi(path, finalQuery, headers, signal);\n\n if (response.status !== 200) {\n let message = `HTTP ${response.status}`;\n try {\n const body = await response.json();\n const err = body?.error;\n if (err?.code || err?.message) {\n message = `${err.code} - ${err.message}`;\n }\n } catch {\n // Response body is not JSON; fall back to HTTP status above\n }\n throw new Error(\n `Error while reading ${\n nextLink ?? path\n } from Microsoft Graph: ${message}`,\n );\n }\n\n const result = await response.json();\n return {\n items: result.value as T[],\n nextLink: result['@odata.nextLink'],\n };\n}\n\n/**\n * Like `getUserPhotoWithSizeLimit` but skips the size-listing call for users\n * with no photo. For users without a photo: 1 fast check call. For users with\n * a photo: 1 check + the normal size-limited fetch (2 more calls).\n *\n * Returns `undefined` only for 404 (no photo assigned). Throws for any other\n * non-200 status so callers can distinguish \"no photo\" from real errors.\n */\nexport async function getUserPhotoGated(\n client: MicrosoftGraphClient,\n userId: string,\n maxSize: number,\n): Promise<string | undefined> {\n const check = await client.requestApi(`users/${userId}/photo`);\n if (check.status === 404) return undefined;\n if (check.status !== 200) {\n throw new Error(\n `Unexpected status ${check.status} when checking photo for user ${userId}`,\n );\n }\n return await client.getUserPhotoWithSizeLimit(userId, maxSize);\n}\n"],"names":[],"mappings":";;AA+BA,eAAsB,cAAA,CACpB,MAAA,EACA,IAAA,EACA,OAAA,GAKI,EAAC,EACuC;AAC5C,EAAA,MAAM,EAAE,KAAA,EAAO,SAAA,EAAW,QAAA,EAAU,QAAO,GAAI,OAAA;AAC/C,EAAA,MAAM,gBAAA,GAAmB,KAAA,EAAO,MAAA,GAAS,UAAA,GAAa,SAAA,IAAa,OAAA;AAInE,EAAA,MAAM,UAAA,GACJ,gBAAA,KAAqB,UAAA,GAAa,EAAE,GAAI,SAAS,EAAC,EAAI,KAAA,EAAO,IAAA,EAAK,GAAI,KAAA;AAExE,EAAA,MAAM,UACJ,gBAAA,KAAqB,UAAA,GAAa,EAAE,gBAAA,EAAkB,UAAA,KAAe,EAAC;AAExE,EAAA,MAAM,WAAW,QAAA,GACb,MAAM,MAAA,CAAO,UAAA,CAAW,UAAU,OAAA,EAAS,CAAA,EAAG,MAAM,CAAA,GACpD,MAAM,MAAA,CAAO,UAAA,CAAW,IAAA,EAAM,UAAA,EAAY,SAAS,MAAM,CAAA;AAE7D,EAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,IAAA,IAAI,OAAA,GAAU,CAAA,KAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,CAAA;AACrC,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,MAAA,MAAM,MAAM,IAAA,EAAM,KAAA;AAClB,MAAA,IAAI,GAAA,EAAK,IAAA,IAAQ,GAAA,EAAK,OAAA,EAAS;AAC7B,QAAA,OAAA,GAAU,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA,GAAA,EAAM,IAAI,OAAO,CAAA,CAAA;AAAA,MACxC;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,oBAAA,EACE,QAAA,IAAY,IACd,CAAA,uBAAA,EAA0B,OAAO,CAAA;AAAA,KACnC;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,IAAA,EAAK;AACnC,EAAA,OAAO;AAAA,IACL,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,QAAA,EAAU,OAAO,iBAAiB;AAAA,GACpC;AACF;AAUA,eAAsB,iBAAA,CACpB,MAAA,EACA,MAAA,EACA,OAAA,EAC6B;AAC7B,EAAA,MAAM,QAAQ,MAAM,MAAA,CAAO,UAAA,CAAW,CAAA,MAAA,EAAS,MAAM,CAAA,MAAA,CAAQ,CAAA;AAC7D,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,GAAA,EAAK,OAAO,MAAA;AACjC,EAAA,IAAI,KAAA,CAAM,WAAW,GAAA,EAAK;AACxB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,kBAAA,EAAqB,KAAA,CAAM,MAAM,CAAA,8BAAA,EAAiC,MAAM,CAAA;AAAA,KAC1E;AAAA,EACF;AACA,EAAA,OAAO,MAAM,MAAA,CAAO,yBAAA,CAA0B,MAAA,EAAQ,OAAO,CAAA;AAC/D;;;;;"}
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var catalogModuleMicrosoftGraphIncrementalEntityProvider = require('./module/catalogModuleMicrosoftGraphIncrementalEntityProvider.cjs.js');
6
+ var MicrosoftGraphIncrementalEntityProvider = require('./MicrosoftGraphIncrementalEntityProvider.cjs.js');
7
+
8
+
9
+
10
+ exports.catalogModuleMicrosoftGraphIncrementalEntityProvider = catalogModuleMicrosoftGraphIncrementalEntityProvider.catalogModuleMicrosoftGraphIncrementalEntityProvider;
11
+ exports.default = catalogModuleMicrosoftGraphIncrementalEntityProvider.catalogModuleMicrosoftGraphIncrementalEntityProvider;
12
+ exports.microsoftGraphIncrementalEntityProviderTransformExtensionPoint = catalogModuleMicrosoftGraphIncrementalEntityProvider.microsoftGraphIncrementalEntityProviderTransformExtensionPoint;
13
+ exports.MicrosoftGraphIncrementalEntityProvider = MicrosoftGraphIncrementalEntityProvider.MicrosoftGraphIncrementalEntityProvider;
14
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
@@ -0,0 +1,171 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+ import { LoggerService } from '@backstage/backend-plugin-api';
3
+ import { UserTransformer, GroupTransformer, OrganizationTransformer, ProviderConfigTransformer, MicrosoftGraphClient, MicrosoftGraphProviderConfig } from '@backstage/plugin-catalog-backend-module-msgraph';
4
+ import { Config } from '@backstage/config';
5
+ import { IncrementalEntityProvider, EntityIteratorResult } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
6
+
7
+ /**
8
+ * Interface for
9
+ * {@link microsoftGraphIncrementalEntityProviderTransformExtensionPoint}.
10
+ *
11
+ * @public
12
+ */
13
+ interface MicrosoftGraphIncrementalEntityProviderTransformsExtensionPoint {
14
+ /**
15
+ * Set the function that transforms a user entry in msgraph to an entity.
16
+ * Optionally, you can pass separate transformers per provider ID.
17
+ */
18
+ setUserTransformer(transformer: UserTransformer | Record<string, UserTransformer>): void;
19
+ /**
20
+ * Set the function that transforms a group entry in msgraph to an entity.
21
+ * Optionally, you can pass separate transformers per provider ID.
22
+ */
23
+ setGroupTransformer(transformer: GroupTransformer | Record<string, GroupTransformer>): void;
24
+ /**
25
+ * Set the function that transforms an organization entry in msgraph to an
26
+ * entity. Optionally, you can pass separate transformers per provider ID.
27
+ */
28
+ setOrganizationTransformer(transformer: OrganizationTransformer | Record<string, OrganizationTransformer>): void;
29
+ /**
30
+ * Set the function that transforms provider config dynamically.
31
+ * Optionally, you can pass separate transformers per provider ID.
32
+ */
33
+ setProviderConfigTransformer(transformer: ProviderConfigTransformer | Record<string, ProviderConfigTransformer>): void;
34
+ }
35
+ /**
36
+ * Extension point used to customize the transforms applied by the incremental
37
+ * module.
38
+ *
39
+ * @public
40
+ */
41
+ declare const microsoftGraphIncrementalEntityProviderTransformExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint<MicrosoftGraphIncrementalEntityProviderTransformsExtensionPoint>;
42
+ /**
43
+ * Registers {@link MicrosoftGraphIncrementalEntityProvider} instances with the
44
+ * catalog's incremental ingestion extension point.
45
+ *
46
+ * This module requires `catalogModuleIncrementalIngestionEntityProvider` to
47
+ * also be installed in the backend.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * // packages/backend/src/index.ts
52
+ * backend.add(import('@backstage/plugin-catalog-backend-module-incremental-ingestion'));
53
+ * backend.add(import('@backstage/plugin-catalog-backend-module-msgraph-incremental'));
54
+ * ```
55
+ *
56
+ * @public
57
+ */
58
+ declare const catalogModuleMicrosoftGraphIncrementalEntityProvider: _backstage_backend_plugin_api.BackendFeature;
59
+
60
+ /**
61
+ * Pagination cursor used by {@link MicrosoftGraphIncrementalEntityProvider}.
62
+ *
63
+ * The `nextLink` field holds the `@odata.nextLink` URL returned by the
64
+ * Microsoft Graph API, which encodes all state needed to resume a paged
65
+ * request. An absent value means the current phase is starting fresh.
66
+ *
67
+ * @public
68
+ */
69
+ type MSGraphCursor = {
70
+ phase: 'users' | 'groups';
71
+ nextLink?: string;
72
+ };
73
+ /**
74
+ * Context passed to each burst of {@link MicrosoftGraphIncrementalEntityProvider}.
75
+ *
76
+ * @public
77
+ */
78
+ type MSGraphContext = {
79
+ client: MicrosoftGraphClient;
80
+ provider: MicrosoftGraphProviderConfig;
81
+ };
82
+ /**
83
+ * Options for {@link MicrosoftGraphIncrementalEntityProvider}.
84
+ *
85
+ * @public
86
+ */
87
+ interface MicrosoftGraphIncrementalEntityProviderOptions {
88
+ /**
89
+ * The logger to use.
90
+ */
91
+ logger: LoggerService;
92
+ /**
93
+ * The function that transforms a user entry in msgraph to an entity.
94
+ * Optionally, you can pass separate transformers per provider ID.
95
+ */
96
+ userTransformer?: UserTransformer | Record<string, UserTransformer>;
97
+ /**
98
+ * The function that transforms a group entry in msgraph to an entity.
99
+ * Optionally, you can pass separate transformers per provider ID.
100
+ */
101
+ groupTransformer?: GroupTransformer | Record<string, GroupTransformer>;
102
+ /**
103
+ * The function that transforms an organization entry in msgraph to an entity.
104
+ * Optionally, you can pass separate transformers per provider ID.
105
+ */
106
+ organizationTransformer?: OrganizationTransformer | Record<string, OrganizationTransformer>;
107
+ /**
108
+ * The function that transforms provider config dynamically before each sync.
109
+ * Optionally, you can pass separate transformers per provider ID.
110
+ */
111
+ providerConfigTransformer?: ProviderConfigTransformer | Record<string, ProviderConfigTransformer>;
112
+ }
113
+ /**
114
+ * Incrementally reads user and group entries out of Microsoft Graph, one page
115
+ * at a time, and provides them as User and Group entities for the catalog.
116
+ *
117
+ * Unlike `MicrosoftGraphOrgEntityProvider`, this provider never holds the full
118
+ * dataset in memory at once. Each burst processes a single page (up to 999
119
+ * users or 100 groups). This makes it suitable for very large tenants and
120
+ * avoids the memory pressure and long-running task issues of the full-scan
121
+ * provider.
122
+ *
123
+ * The Microsoft Graph `@odata.nextLink` URL is stored as the cursor, so a pod
124
+ * restart during ingestion resumes from the last completed page.
125
+ *
126
+ * Group membership (`spec.members`) is resolved inline during the groups phase
127
+ * by fetching the direct members of each group. The catalog's built-in relation
128
+ * stitching derives `spec.memberOf` on users from these group membership lists.
129
+ *
130
+ * @remarks
131
+ * `userGroupMemberFilter`, `userGroupMemberSearch`, `userGroupMemberPath`, and
132
+ * `groupIncludeSubGroups` are not supported. Use `userFilter` / `userPath` to
133
+ * restrict which users are ingested, and `groupFilter` / `groupSearch` to
134
+ * restrict which groups. Switch to `MicrosoftGraphOrgEntityProvider` if you
135
+ * require any of these options.
136
+ *
137
+ * @public
138
+ */
139
+ declare class MicrosoftGraphIncrementalEntityProvider implements IncrementalEntityProvider<MSGraphCursor, MSGraphContext> {
140
+ /**
141
+ * Create one provider instance per provider entry in
142
+ * `catalog.providers.microsoftGraphOrg`.
143
+ */
144
+ static fromConfig(configRoot: Config, options: MicrosoftGraphIncrementalEntityProviderOptions): MicrosoftGraphIncrementalEntityProvider[];
145
+ private readonly options;
146
+ constructor(options: {
147
+ id: string;
148
+ provider: MicrosoftGraphProviderConfig;
149
+ logger: LoggerService;
150
+ userTransformer?: UserTransformer;
151
+ groupTransformer?: GroupTransformer;
152
+ organizationTransformer?: OrganizationTransformer;
153
+ providerConfigTransformer?: ProviderConfigTransformer;
154
+ });
155
+ /** {@inheritdoc @backstage/plugin-catalog-backend-module-incremental-ingestion#IncrementalEntityProvider.getProviderName} */
156
+ getProviderName(): string;
157
+ /**
158
+ * Sets up the Microsoft Graph client for the duration of a full ingestion
159
+ * cycle. The optional `providerConfigTransformer` is applied here so that
160
+ * dynamic config changes (e.g., rotating credentials) take effect at the
161
+ * start of each cycle rather than mid-way through.
162
+ */
163
+ around(burst: (context: MSGraphContext) => Promise<void>): Promise<void>;
164
+ /** {@inheritdoc @backstage/plugin-catalog-backend-module-incremental-ingestion#IncrementalEntityProvider.next} */
165
+ next({ client, provider }: MSGraphContext, cursor?: MSGraphCursor): Promise<EntityIteratorResult<MSGraphCursor>>;
166
+ private readUsersPage;
167
+ private readGroupsPage;
168
+ }
169
+
170
+ export { MicrosoftGraphIncrementalEntityProvider, catalogModuleMicrosoftGraphIncrementalEntityProvider, catalogModuleMicrosoftGraphIncrementalEntityProvider as default, microsoftGraphIncrementalEntityProviderTransformExtensionPoint };
171
+ export type { MSGraphContext, MSGraphCursor, MicrosoftGraphIncrementalEntityProviderOptions, MicrosoftGraphIncrementalEntityProviderTransformsExtensionPoint };
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var pluginCatalogBackendModuleIncrementalIngestion = require('@backstage/plugin-catalog-backend-module-incremental-ingestion');
5
+ var pluginCatalogBackendModuleMsgraph = require('@backstage/plugin-catalog-backend-module-msgraph');
6
+ var MicrosoftGraphIncrementalEntityProvider = require('../MicrosoftGraphIncrementalEntityProvider.cjs.js');
7
+
8
+ const microsoftGraphIncrementalEntityProviderTransformExtensionPoint = backendPluginApi.createExtensionPoint(
9
+ {
10
+ id: "catalog.microsoftGraphIncrementalEntityProvider.transforms"
11
+ }
12
+ );
13
+ const catalogModuleMicrosoftGraphIncrementalEntityProvider = backendPluginApi.createBackendModule({
14
+ pluginId: "catalog",
15
+ moduleId: "microsoftGraphIncrementalEntityProvider",
16
+ register(env) {
17
+ let userTransformer;
18
+ let groupTransformer;
19
+ let organizationTransformer;
20
+ let providerConfigTransformer;
21
+ env.registerExtensionPoint(
22
+ microsoftGraphIncrementalEntityProviderTransformExtensionPoint,
23
+ {
24
+ setUserTransformer(transformer) {
25
+ if (userTransformer) {
26
+ throw new Error("User transformer may only be set once");
27
+ }
28
+ userTransformer = transformer;
29
+ },
30
+ setGroupTransformer(transformer) {
31
+ if (groupTransformer) {
32
+ throw new Error("Group transformer may only be set once");
33
+ }
34
+ groupTransformer = transformer;
35
+ },
36
+ setOrganizationTransformer(transformer) {
37
+ if (organizationTransformer) {
38
+ throw new Error("Organization transformer may only be set once");
39
+ }
40
+ organizationTransformer = transformer;
41
+ },
42
+ setProviderConfigTransformer(transformer) {
43
+ if (providerConfigTransformer) {
44
+ throw new Error(
45
+ "Provider config transformer may only be set once"
46
+ );
47
+ }
48
+ providerConfigTransformer = transformer;
49
+ }
50
+ }
51
+ );
52
+ env.registerInit({
53
+ deps: {
54
+ config: backendPluginApi.coreServices.rootConfig,
55
+ logger: backendPluginApi.coreServices.logger,
56
+ incremental: pluginCatalogBackendModuleIncrementalIngestion.incrementalIngestionProvidersExtensionPoint
57
+ },
58
+ async init({ config, logger, incremental }) {
59
+ const providerConfigs = pluginCatalogBackendModuleMsgraph.readProviderConfigs(config);
60
+ for (const providerConfig of providerConfigs) {
61
+ const provider = new MicrosoftGraphIncrementalEntityProvider.MicrosoftGraphIncrementalEntityProvider({
62
+ id: providerConfig.id,
63
+ provider: providerConfig,
64
+ logger,
65
+ userTransformer: resolveTransformer(
66
+ providerConfig.id,
67
+ userTransformer
68
+ ),
69
+ groupTransformer: resolveTransformer(
70
+ providerConfig.id,
71
+ groupTransformer
72
+ ),
73
+ organizationTransformer: resolveTransformer(
74
+ providerConfig.id,
75
+ organizationTransformer
76
+ ),
77
+ providerConfigTransformer: resolveTransformer(
78
+ providerConfig.id,
79
+ providerConfigTransformer
80
+ )
81
+ });
82
+ const restLength = deriveRestLength(providerConfig, logger);
83
+ incremental.addProvider({
84
+ provider,
85
+ options: {
86
+ burstInterval: { seconds: 3 },
87
+ burstLength: { minutes: 5 },
88
+ restLength,
89
+ backoff: [
90
+ { seconds: 30 },
91
+ { minutes: 3 },
92
+ { minutes: 30 },
93
+ { hours: 3 }
94
+ ]
95
+ }
96
+ });
97
+ }
98
+ }
99
+ });
100
+ }
101
+ });
102
+ function resolveTransformer(id, transformer) {
103
+ if (["undefined", "function"].includes(typeof transformer)) {
104
+ return transformer;
105
+ }
106
+ return transformer[id];
107
+ }
108
+ function deriveRestLength(providerConfig, logger) {
109
+ const freq = providerConfig.schedule?.frequency;
110
+ if (freq && typeof freq === "object" && !("cron" in freq) && !("trigger" in freq)) {
111
+ return freq;
112
+ }
113
+ if (freq) {
114
+ logger.warn(
115
+ `MicrosoftGraphIncrementalEntityProvider:${providerConfig.id}: schedule.frequency is not a duration-based schedule; cannot derive restLength from it. Defaulting restLength to 8 hours.`
116
+ );
117
+ }
118
+ return { hours: 8 };
119
+ }
120
+
121
+ exports.catalogModuleMicrosoftGraphIncrementalEntityProvider = catalogModuleMicrosoftGraphIncrementalEntityProvider;
122
+ exports.microsoftGraphIncrementalEntityProviderTransformExtensionPoint = microsoftGraphIncrementalEntityProviderTransformExtensionPoint;
123
+ //# sourceMappingURL=catalogModuleMicrosoftGraphIncrementalEntityProvider.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogModuleMicrosoftGraphIncrementalEntityProvider.cjs.js","sources":["../../src/module/catalogModuleMicrosoftGraphIncrementalEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createBackendModule,\n createExtensionPoint,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { incrementalIngestionProvidersExtensionPoint } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';\nimport {\n GroupTransformer,\n OrganizationTransformer,\n ProviderConfigTransformer,\n UserTransformer,\n readProviderConfigs,\n} from '@backstage/plugin-catalog-backend-module-msgraph';\nimport { HumanDuration } from '@backstage/types';\nimport {\n MicrosoftGraphIncrementalEntityProvider,\n MSGraphContext,\n MSGraphCursor,\n} from '../MicrosoftGraphIncrementalEntityProvider';\n\n/**\n * Interface for\n * {@link microsoftGraphIncrementalEntityProviderTransformExtensionPoint}.\n *\n * @public\n */\nexport interface MicrosoftGraphIncrementalEntityProviderTransformsExtensionPoint {\n /**\n * Set the function that transforms a user entry in msgraph to an entity.\n * Optionally, you can pass separate transformers per provider ID.\n */\n setUserTransformer(\n transformer: UserTransformer | Record<string, UserTransformer>,\n ): void;\n\n /**\n * Set the function that transforms a group entry in msgraph to an entity.\n * Optionally, you can pass separate transformers per provider ID.\n */\n setGroupTransformer(\n transformer: GroupTransformer | Record<string, GroupTransformer>,\n ): void;\n\n /**\n * Set the function that transforms an organization entry in msgraph to an\n * entity. Optionally, you can pass separate transformers per provider ID.\n */\n setOrganizationTransformer(\n transformer:\n | OrganizationTransformer\n | Record<string, OrganizationTransformer>,\n ): void;\n\n /**\n * Set the function that transforms provider config dynamically.\n * Optionally, you can pass separate transformers per provider ID.\n */\n setProviderConfigTransformer(\n transformer:\n | ProviderConfigTransformer\n | Record<string, ProviderConfigTransformer>,\n ): void;\n}\n\n/**\n * Extension point used to customize the transforms applied by the incremental\n * module.\n *\n * @public\n */\nexport const microsoftGraphIncrementalEntityProviderTransformExtensionPoint =\n createExtensionPoint<MicrosoftGraphIncrementalEntityProviderTransformsExtensionPoint>(\n {\n id: 'catalog.microsoftGraphIncrementalEntityProvider.transforms',\n },\n );\n\n/**\n * Registers {@link MicrosoftGraphIncrementalEntityProvider} instances with the\n * catalog's incremental ingestion extension point.\n *\n * This module requires `catalogModuleIncrementalIngestionEntityProvider` to\n * also be installed in the backend.\n *\n * @example\n * ```ts\n * // packages/backend/src/index.ts\n * backend.add(import('@backstage/plugin-catalog-backend-module-incremental-ingestion'));\n * backend.add(import('@backstage/plugin-catalog-backend-module-msgraph-incremental'));\n * ```\n *\n * @public\n */\nexport const catalogModuleMicrosoftGraphIncrementalEntityProvider =\n createBackendModule({\n pluginId: 'catalog',\n moduleId: 'microsoftGraphIncrementalEntityProvider',\n register(env) {\n let userTransformer:\n | UserTransformer\n | Record<string, UserTransformer>\n | undefined;\n let groupTransformer:\n | GroupTransformer\n | Record<string, GroupTransformer>\n | undefined;\n let organizationTransformer:\n | OrganizationTransformer\n | Record<string, OrganizationTransformer>\n | undefined;\n let providerConfigTransformer:\n | ProviderConfigTransformer\n | Record<string, ProviderConfigTransformer>\n | undefined;\n\n env.registerExtensionPoint(\n microsoftGraphIncrementalEntityProviderTransformExtensionPoint,\n {\n setUserTransformer(transformer) {\n if (userTransformer) {\n throw new Error('User transformer may only be set once');\n }\n userTransformer = transformer;\n },\n setGroupTransformer(transformer) {\n if (groupTransformer) {\n throw new Error('Group transformer may only be set once');\n }\n groupTransformer = transformer;\n },\n setOrganizationTransformer(transformer) {\n if (organizationTransformer) {\n throw new Error('Organization transformer may only be set once');\n }\n organizationTransformer = transformer;\n },\n setProviderConfigTransformer(transformer) {\n if (providerConfigTransformer) {\n throw new Error(\n 'Provider config transformer may only be set once',\n );\n }\n providerConfigTransformer = transformer;\n },\n },\n );\n\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n incremental: incrementalIngestionProvidersExtensionPoint,\n },\n async init({ config, logger, incremental }) {\n const providerConfigs = readProviderConfigs(config);\n\n for (const providerConfig of providerConfigs) {\n const provider = new MicrosoftGraphIncrementalEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger,\n userTransformer: resolveTransformer(\n providerConfig.id,\n userTransformer,\n ),\n groupTransformer: resolveTransformer(\n providerConfig.id,\n groupTransformer,\n ),\n organizationTransformer: resolveTransformer(\n providerConfig.id,\n organizationTransformer,\n ),\n providerConfigTransformer: resolveTransformer(\n providerConfig.id,\n providerConfigTransformer,\n ),\n });\n\n const restLength = deriveRestLength(providerConfig, logger);\n\n incremental.addProvider<MSGraphCursor, MSGraphContext>({\n provider,\n options: {\n burstInterval: { seconds: 3 },\n burstLength: { minutes: 5 },\n restLength,\n backoff: [\n { seconds: 30 },\n { minutes: 3 },\n { minutes: 30 },\n { hours: 3 },\n ],\n },\n });\n }\n },\n });\n },\n });\n\nfunction resolveTransformer<T extends Function>(\n id: string,\n transformer?: T | Record<string, T>,\n): T | undefined {\n if (['undefined', 'function'].includes(typeof transformer)) {\n return transformer as T;\n }\n return (transformer as Record<string, T>)[id];\n}\n\nfunction deriveRestLength(\n providerConfig: ReturnType<typeof readProviderConfigs>[number],\n logger: LoggerService,\n): HumanDuration {\n const freq = providerConfig.schedule?.frequency;\n // Only treat plain duration objects as restLength — exclude cron expressions\n // and any other non-duration schedule types (e.g. manual triggers).\n if (\n freq &&\n typeof freq === 'object' &&\n !('cron' in freq) &&\n !('trigger' in freq)\n ) {\n return freq as HumanDuration;\n }\n if (freq) {\n logger.warn(\n `MicrosoftGraphIncrementalEntityProvider:${providerConfig.id}: ` +\n `schedule.frequency is not a duration-based schedule; cannot derive restLength from it. ` +\n `Defaulting restLength to 8 hours.`,\n );\n }\n return { hours: 8 };\n}\n"],"names":["createExtensionPoint","createBackendModule","coreServices","incrementalIngestionProvidersExtensionPoint","readProviderConfigs","MicrosoftGraphIncrementalEntityProvider"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@backstage/plugin-catalog-backend-module-msgraph-incremental",
3
+ "version": "0.0.0-nightly-20260507032228",
4
+ "description": "A Backstage catalog backend module that incrementally ingests users and groups from Microsoft Graph",
5
+ "backstage": {
6
+ "role": "backend-plugin-module",
7
+ "pluginId": "catalog",
8
+ "pluginPackage": "@backstage/plugin-catalog-backend",
9
+ "features": {
10
+ ".": "@backstage/BackendFeature"
11
+ }
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "keywords": [
17
+ "backstage"
18
+ ],
19
+ "homepage": "https://backstage.io",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/backstage/backstage",
23
+ "directory": "plugins/catalog-backend-module-msgraph-incremental"
24
+ },
25
+ "license": "Apache-2.0",
26
+ "exports": {
27
+ ".": {
28
+ "backstage": "@backstage/BackendFeature",
29
+ "require": "./dist/index.cjs.js",
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.cjs.js"
32
+ },
33
+ "./alpha": {
34
+ "require": "./dist/alpha.cjs.js",
35
+ "types": "./dist/alpha.d.ts",
36
+ "default": "./dist/alpha.cjs.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "main": "./dist/index.cjs.js",
41
+ "types": "./dist/index.d.ts",
42
+ "typesVersions": {
43
+ "*": {
44
+ "alpha": [
45
+ "dist/alpha.d.ts"
46
+ ],
47
+ "package.json": [
48
+ "package.json"
49
+ ]
50
+ }
51
+ },
52
+ "files": [
53
+ "dist"
54
+ ],
55
+ "scripts": {
56
+ "build": "backstage-cli package build",
57
+ "clean": "backstage-cli package clean",
58
+ "lint": "backstage-cli package lint",
59
+ "prepack": "backstage-cli package prepack",
60
+ "postpack": "backstage-cli package postpack",
61
+ "start": "backstage-cli package start",
62
+ "test": "backstage-cli package test"
63
+ },
64
+ "dependencies": {
65
+ "@backstage/backend-plugin-api": "0.0.0-nightly-20260507032228",
66
+ "@backstage/catalog-model": "0.0.0-nightly-20260507032228",
67
+ "@backstage/config": "0.0.0-nightly-20260507032228",
68
+ "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.0.0-nightly-20260507032228",
69
+ "@backstage/plugin-catalog-backend-module-msgraph": "0.0.0-nightly-20260507032228",
70
+ "@backstage/plugin-catalog-node": "0.0.0-nightly-20260507032228",
71
+ "@backstage/types": "1.2.2",
72
+ "@microsoft/microsoft-graph-types": "^2.6.0",
73
+ "p-limit": "^3.0.2"
74
+ },
75
+ "devDependencies": {
76
+ "@backstage/backend-test-utils": "0.0.0-nightly-20260507032228",
77
+ "@backstage/cli": "0.0.0-nightly-20260507032228"
78
+ }
79
+ }