@openhi/constructs 0.0.111 → 0.0.112
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/lib/chunk-23PUSHBV.mjs +24 -0
- package/lib/chunk-23PUSHBV.mjs.map +1 -0
- package/lib/{chunk-7FUAMZOF.mjs → chunk-53OHXLIL.mjs} +3 -3
- package/lib/chunk-6NBGYGFL.mjs +1803 -0
- package/lib/chunk-6NBGYGFL.mjs.map +1 -0
- package/lib/chunk-7RZHFI77.mjs +22 -0
- package/lib/chunk-7RZHFI77.mjs.map +1 -0
- package/lib/{chunk-7Q2IJ2J5.mjs → chunk-CUUKXDB2.mjs} +6 -6
- package/lib/chunk-FYHBHHWK.mjs +47 -0
- package/lib/chunk-FYHBHHWK.mjs.map +1 -0
- package/lib/{chunk-MULKGFIJ.mjs → chunk-GBDIGTNV.mjs} +165 -10
- package/lib/chunk-GBDIGTNV.mjs.map +1 -0
- package/lib/chunk-HQ67J7BP.mjs +199 -0
- package/lib/chunk-HQ67J7BP.mjs.map +1 -0
- package/lib/{chunk-AJ3G3THO.mjs → chunk-KO64HPWQ.mjs} +2 -2
- package/lib/{chunk-BB5MK4L3.mjs → chunk-KSFC72TT.mjs} +3 -3
- package/lib/{chunk-2TPJ6HOF.mjs → chunk-NZRW7ROK.mjs} +72 -54
- package/lib/chunk-NZRW7ROK.mjs.map +1 -0
- package/lib/chunk-QJDHVMKT.mjs +117 -0
- package/lib/chunk-QJDHVMKT.mjs.map +1 -0
- package/lib/{chunk-IS4VQRI4.mjs → chunk-QMBJ4VHC.mjs} +12 -47
- package/lib/chunk-QMBJ4VHC.mjs.map +1 -0
- package/lib/chunk-TRY7JGWO.mjs +16 -0
- package/lib/chunk-TRY7JGWO.mjs.map +1 -0
- package/lib/chunk-W4KR4CSL.mjs +236 -0
- package/lib/chunk-W4KR4CSL.mjs.map +1 -0
- package/lib/{chunk-AGF3RAAZ.mjs → chunk-WPCBVDFZ.mjs} +2 -2
- package/lib/chunk-WQWFVEVX.mjs +66 -0
- package/lib/chunk-WQWFVEVX.mjs.map +1 -0
- package/lib/{chunk-SYBADQXI.mjs → chunk-ZM4GDHHC.mjs} +77 -2
- package/lib/chunk-ZM4GDHHC.mjs.map +1 -0
- package/lib/delete-chunk.handler.d.mts +29 -0
- package/lib/delete-chunk.handler.d.ts +29 -0
- package/lib/delete-chunk.handler.js +2716 -0
- package/lib/delete-chunk.handler.js.map +1 -0
- package/lib/delete-chunk.handler.mjs +47 -0
- package/lib/delete-chunk.handler.mjs.map +1 -0
- package/lib/events-CjS-sm0W.d.mts +107 -0
- package/lib/events-CjS-sm0W.d.ts +107 -0
- package/lib/events-Da_cFgtc.d.mts +208 -0
- package/lib/events-Da_cFgtc.d.ts +208 -0
- package/lib/finalize.handler.d.mts +35 -0
- package/lib/finalize.handler.d.ts +35 -0
- package/lib/finalize.handler.js +875 -0
- package/lib/finalize.handler.js.map +1 -0
- package/lib/finalize.handler.mjs +166 -0
- package/lib/finalize.handler.mjs.map +1 -0
- package/lib/index.d.mts +189 -2
- package/lib/index.d.ts +500 -3
- package/lib/index.js +1753 -174
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +571 -17
- package/lib/index.mjs.map +1 -1
- package/lib/list-chunks.handler.d.mts +28 -0
- package/lib/list-chunks.handler.d.ts +28 -0
- package/lib/list-chunks.handler.js +2746 -0
- package/lib/list-chunks.handler.js.map +1 -0
- package/lib/list-chunks.handler.mjs +54 -0
- package/lib/list-chunks.handler.mjs.map +1 -0
- package/lib/platform-deploy-bridge.handler.js +76 -1
- package/lib/platform-deploy-bridge.handler.js.map +1 -1
- package/lib/platform-deploy-bridge.handler.mjs +1 -1
- package/lib/pre-token-generation.handler.js +1106 -155
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +6 -4
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +1529 -142
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +8 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rename-finalize.handler.d.mts +30 -0
- package/lib/rename-finalize.handler.d.ts +30 -0
- package/lib/rename-finalize.handler.js +795 -0
- package/lib/rename-finalize.handler.js.map +1 -0
- package/lib/rename-finalize.handler.mjs +90 -0
- package/lib/rename-finalize.handler.mjs.map +1 -0
- package/lib/rename-list-targets.handler.d.mts +26 -0
- package/lib/rename-list-targets.handler.d.ts +26 -0
- package/lib/rename-list-targets.handler.js +2985 -0
- package/lib/rename-list-targets.handler.js.map +1 -0
- package/lib/rename-list-targets.handler.mjs +431 -0
- package/lib/rename-list-targets.handler.mjs.map +1 -0
- package/lib/rename-rewrite-chunk.handler.d.mts +35 -0
- package/lib/rename-rewrite-chunk.handler.d.ts +35 -0
- package/lib/rename-rewrite-chunk.handler.js +2021 -0
- package/lib/rename-rewrite-chunk.handler.js.map +1 -0
- package/lib/rename-rewrite-chunk.handler.mjs +27 -0
- package/lib/rename-rewrite-chunk.handler.mjs.map +1 -0
- package/lib/rest-api-lambda.handler.js +4021 -932
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1786 -80
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js +1588 -124
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +10 -6
- package/lib/seed-system-data.handler.js +1179 -155
- package/lib/seed-system-data.handler.js.map +1 -1
- package/lib/seed-system-data.handler.mjs +5 -4
- package/lib/seed-system-data.handler.mjs.map +1 -1
- package/package.json +3 -3
- package/lib/chunk-2TPJ6HOF.mjs.map +0 -1
- package/lib/chunk-IS4VQRI4.mjs.map +0 -1
- package/lib/chunk-MULKGFIJ.mjs.map +0 -1
- package/lib/chunk-QR5JVSCF.mjs +0 -862
- package/lib/chunk-QR5JVSCF.mjs.map +0 -1
- package/lib/chunk-SYBADQXI.mjs.map +0 -1
- /package/lib/{chunk-7FUAMZOF.mjs.map → chunk-53OHXLIL.mjs.map} +0 -0
- /package/lib/{chunk-7Q2IJ2J5.mjs.map → chunk-CUUKXDB2.mjs.map} +0 -0
- /package/lib/{chunk-AJ3G3THO.mjs.map → chunk-KO64HPWQ.mjs.map} +0 -0
- /package/lib/{chunk-BB5MK4L3.mjs.map → chunk-KSFC72TT.mjs.map} +0 -0
- /package/lib/{chunk-AGF3RAAZ.mjs.map → chunk-WPCBVDFZ.mjs.map} +0 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chunkRenameCascadeTargets
|
|
3
|
+
} from "./chunk-WQWFVEVX.mjs";
|
|
4
|
+
import {
|
|
5
|
+
require_lib
|
|
6
|
+
} from "./chunk-ZM4GDHHC.mjs";
|
|
7
|
+
import {
|
|
8
|
+
buildMembershipUserProjectionSkTenantLane,
|
|
9
|
+
buildMembershipUserProjectionSkWorkspaceLane,
|
|
10
|
+
buildMembershipWorkspaceProjectionSk,
|
|
11
|
+
buildRoleAssignmentUserProjectionSkTenantLane,
|
|
12
|
+
buildRoleAssignmentUserProjectionSkWorkspaceLane,
|
|
13
|
+
buildRoleAssignmentWorkspaceProjectionSk,
|
|
14
|
+
extractReferenceSlug
|
|
15
|
+
} from "./chunk-HQ67J7BP.mjs";
|
|
16
|
+
import "./chunk-QJDHVMKT.mjs";
|
|
17
|
+
import "./chunk-FYHBHHWK.mjs";
|
|
18
|
+
import {
|
|
19
|
+
getDynamoControlService
|
|
20
|
+
} from "./chunk-6NBGYGFL.mjs";
|
|
21
|
+
import "./chunk-TRY7JGWO.mjs";
|
|
22
|
+
import {
|
|
23
|
+
__toESM
|
|
24
|
+
} from "./chunk-LZOMFHX3.mjs";
|
|
25
|
+
|
|
26
|
+
// src/workflows/control-plane/rename-cascade/rename-list-targets.handler.ts
|
|
27
|
+
import { randomUUID } from "crypto";
|
|
28
|
+
|
|
29
|
+
// src/data/operations/control/rename-cascade/rename-cascade-list-targets-operation.ts
|
|
30
|
+
var import_workflows = __toESM(require_lib());
|
|
31
|
+
var DEFAULT_PAGE_SIZE = 100;
|
|
32
|
+
var STREAMS_FOR_ENTITY_TYPE = {
|
|
33
|
+
Tenant: ["membershipUserProjection", "roleAssignmentUserProjection"],
|
|
34
|
+
User: [
|
|
35
|
+
"membershipUserProjection",
|
|
36
|
+
"roleAssignmentUserProjection",
|
|
37
|
+
"membershipWorkspaceProjection",
|
|
38
|
+
"roleAssignmentWorkspaceProjection"
|
|
39
|
+
],
|
|
40
|
+
Role: ["roleAssignmentUserProjection", "roleAssignmentWorkspaceProjection"]
|
|
41
|
+
};
|
|
42
|
+
async function listRenameCascadeTargetsOperation(params) {
|
|
43
|
+
const {
|
|
44
|
+
entityType,
|
|
45
|
+
entityId,
|
|
46
|
+
tenantId,
|
|
47
|
+
oldName,
|
|
48
|
+
newName,
|
|
49
|
+
oldNormalizedName,
|
|
50
|
+
newNormalizedName,
|
|
51
|
+
cursors = {},
|
|
52
|
+
limit = DEFAULT_PAGE_SIZE,
|
|
53
|
+
tableName
|
|
54
|
+
} = params;
|
|
55
|
+
if (!entityId || entityId.length === 0) {
|
|
56
|
+
throw new Error("listRenameCascadeTargetsOperation: entityId is required");
|
|
57
|
+
}
|
|
58
|
+
switch (entityType) {
|
|
59
|
+
case import_workflows.RENAMABLE_ENTITY_TYPE.User:
|
|
60
|
+
return pageUserRename({
|
|
61
|
+
userId: entityId,
|
|
62
|
+
oldNormalizedName,
|
|
63
|
+
newNormalizedName,
|
|
64
|
+
newName,
|
|
65
|
+
cursors,
|
|
66
|
+
limit,
|
|
67
|
+
tableName
|
|
68
|
+
});
|
|
69
|
+
case import_workflows.RENAMABLE_ENTITY_TYPE.Role:
|
|
70
|
+
return pageRoleRename({
|
|
71
|
+
roleId: entityId,
|
|
72
|
+
tenantId,
|
|
73
|
+
newName,
|
|
74
|
+
cursors,
|
|
75
|
+
limit,
|
|
76
|
+
tableName
|
|
77
|
+
});
|
|
78
|
+
case import_workflows.RENAMABLE_ENTITY_TYPE.Tenant:
|
|
79
|
+
return pageTenantRename({
|
|
80
|
+
tenantId: entityId,
|
|
81
|
+
oldName,
|
|
82
|
+
newName,
|
|
83
|
+
cursors,
|
|
84
|
+
limit,
|
|
85
|
+
tableName
|
|
86
|
+
});
|
|
87
|
+
default: {
|
|
88
|
+
const exhaustive = entityType;
|
|
89
|
+
throw new Error(
|
|
90
|
+
`listRenameCascadeTargetsOperation: unsupported entityType '${String(
|
|
91
|
+
exhaustive
|
|
92
|
+
)}'`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function pageUserRename(params) {
|
|
98
|
+
const { userId, newName, cursors, limit, tableName } = params;
|
|
99
|
+
const service = getDynamoControlService(tableName);
|
|
100
|
+
const nextCursors = {};
|
|
101
|
+
const targets = [];
|
|
102
|
+
const muStream = cursors.membershipUserProjection;
|
|
103
|
+
if (muStream !== null) {
|
|
104
|
+
const page = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: "MEMBERSHIP#" }).go({ cursor: muStream ?? null, limit });
|
|
105
|
+
for (const row of page.data ?? []) {
|
|
106
|
+
const oldKey = { userId: row.userId, sk: row.sk };
|
|
107
|
+
const newSk = row.sk;
|
|
108
|
+
const newKey = { userId: row.userId, sk: newSk };
|
|
109
|
+
targets.push({
|
|
110
|
+
entity: "membershipUserProjection",
|
|
111
|
+
oldKey,
|
|
112
|
+
newKey,
|
|
113
|
+
newItem: {
|
|
114
|
+
...row,
|
|
115
|
+
sk: newSk,
|
|
116
|
+
denormalizedUserName: newName
|
|
117
|
+
},
|
|
118
|
+
skRewriteRequired: false
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
nextCursors.membershipUserProjection = page.cursor ?? null;
|
|
122
|
+
} else {
|
|
123
|
+
nextCursors.membershipUserProjection = null;
|
|
124
|
+
}
|
|
125
|
+
const raUStream = cursors.roleAssignmentUserProjection;
|
|
126
|
+
if (raUStream !== null) {
|
|
127
|
+
const page = await service.entities.roleAssignmentUserProjection.query.record({ userId }).begins({ sk: "ROLEASSIGNMENT#" }).go({ cursor: raUStream ?? null, limit });
|
|
128
|
+
for (const row of page.data ?? []) {
|
|
129
|
+
const oldKey = { userId: row.userId, sk: row.sk };
|
|
130
|
+
const newKey = { userId: row.userId, sk: row.sk };
|
|
131
|
+
targets.push({
|
|
132
|
+
entity: "roleAssignmentUserProjection",
|
|
133
|
+
oldKey,
|
|
134
|
+
newKey,
|
|
135
|
+
newItem: {
|
|
136
|
+
...row,
|
|
137
|
+
denormalizedUserName: newName
|
|
138
|
+
},
|
|
139
|
+
skRewriteRequired: false
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
nextCursors.roleAssignmentUserProjection = page.cursor ?? null;
|
|
143
|
+
} else {
|
|
144
|
+
nextCursors.roleAssignmentUserProjection = null;
|
|
145
|
+
}
|
|
146
|
+
const discoveryCursor = cursors.workspaceDiscovery;
|
|
147
|
+
if (discoveryCursor !== null) {
|
|
148
|
+
const discovery = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: "MEMBERSHIP#WORKSPACE#" }).go({ cursor: discoveryCursor ?? null, limit });
|
|
149
|
+
for (const member of discovery.data ?? []) {
|
|
150
|
+
if (!member.workspaceId || !member.tenantId) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
await collectWorkspaceUserRenameTargets({
|
|
154
|
+
service,
|
|
155
|
+
tenantId: member.tenantId,
|
|
156
|
+
workspaceId: member.workspaceId,
|
|
157
|
+
userId,
|
|
158
|
+
oldNormalizedName: params.oldNormalizedName,
|
|
159
|
+
newNormalizedName: params.newNormalizedName,
|
|
160
|
+
newName,
|
|
161
|
+
targets
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
nextCursors.workspaceDiscovery = discovery.cursor ?? null;
|
|
165
|
+
} else {
|
|
166
|
+
nextCursors.workspaceDiscovery = null;
|
|
167
|
+
}
|
|
168
|
+
nextCursors.membershipWorkspaceProjection = null;
|
|
169
|
+
nextCursors.roleAssignmentWorkspaceProjection = null;
|
|
170
|
+
const exhausted = STREAMS_FOR_ENTITY_TYPE.User.every((s) => nextCursors[s] === null) && nextCursors.workspaceDiscovery === null;
|
|
171
|
+
return { targets, cursors: nextCursors, exhausted };
|
|
172
|
+
}
|
|
173
|
+
async function collectWorkspaceUserRenameTargets(params) {
|
|
174
|
+
const {
|
|
175
|
+
service,
|
|
176
|
+
tenantId,
|
|
177
|
+
workspaceId,
|
|
178
|
+
userId,
|
|
179
|
+
oldNormalizedName,
|
|
180
|
+
newName,
|
|
181
|
+
targets
|
|
182
|
+
} = params;
|
|
183
|
+
const mwPage = await service.entities.membershipWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: `MEMBERSHIP#${oldNormalizedName}#USER#${userId}#` }).go({});
|
|
184
|
+
for (const row of mwPage.data ?? []) {
|
|
185
|
+
const newSk = buildMembershipWorkspaceProjectionSk({
|
|
186
|
+
userId: row.userId,
|
|
187
|
+
membershipId: row.membershipId,
|
|
188
|
+
denormalizedUserName: newName
|
|
189
|
+
});
|
|
190
|
+
targets.push({
|
|
191
|
+
entity: "membershipWorkspaceProjection",
|
|
192
|
+
oldKey: {
|
|
193
|
+
tenantId: row.tenantId,
|
|
194
|
+
workspaceId: row.workspaceId,
|
|
195
|
+
sk: row.sk
|
|
196
|
+
},
|
|
197
|
+
newKey: {
|
|
198
|
+
tenantId: row.tenantId,
|
|
199
|
+
workspaceId: row.workspaceId,
|
|
200
|
+
sk: newSk
|
|
201
|
+
},
|
|
202
|
+
newItem: {
|
|
203
|
+
...row,
|
|
204
|
+
sk: newSk,
|
|
205
|
+
denormalizedUserName: newName
|
|
206
|
+
},
|
|
207
|
+
skRewriteRequired: row.sk !== newSk
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const raPage = await service.entities.roleAssignmentWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: "ROLEASSIGNMENT#" }).where((attr, op) => op.eq(attr.userId, userId)).go({});
|
|
211
|
+
for (const row of raPage.data ?? []) {
|
|
212
|
+
const newSk = buildRoleAssignmentWorkspaceProjectionSk({
|
|
213
|
+
roleId: row.roleId,
|
|
214
|
+
userId: row.userId,
|
|
215
|
+
roleAssignmentId: row.roleAssignmentId,
|
|
216
|
+
denormalizedUserName: newName
|
|
217
|
+
});
|
|
218
|
+
targets.push({
|
|
219
|
+
entity: "roleAssignmentWorkspaceProjection",
|
|
220
|
+
oldKey: {
|
|
221
|
+
tenantId: row.tenantId,
|
|
222
|
+
workspaceId: row.workspaceId,
|
|
223
|
+
sk: row.sk
|
|
224
|
+
},
|
|
225
|
+
newKey: {
|
|
226
|
+
tenantId: row.tenantId,
|
|
227
|
+
workspaceId: row.workspaceId,
|
|
228
|
+
sk: newSk
|
|
229
|
+
},
|
|
230
|
+
newItem: {
|
|
231
|
+
...row,
|
|
232
|
+
sk: newSk,
|
|
233
|
+
denormalizedUserName: newName
|
|
234
|
+
},
|
|
235
|
+
skRewriteRequired: row.sk !== newSk
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function pageRoleRename(params) {
|
|
240
|
+
const { roleId, tenantId, newName, cursors, limit, tableName } = params;
|
|
241
|
+
if (!tenantId) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"listRenameCascadeTargetsOperation: tenantId is required for Role rename"
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const service = getDynamoControlService(tableName);
|
|
247
|
+
const nextCursors = {};
|
|
248
|
+
const targets = [];
|
|
249
|
+
const discoveryCursor = cursors.roleDiscovery;
|
|
250
|
+
if (discoveryCursor !== null) {
|
|
251
|
+
const page = await service.entities.roleAssignment.query.gsi1({ tenantId, gsi1Shard: "0" }).begins({ gsi1sk: `${roleId}#` }).go({ cursor: discoveryCursor ?? null, limit });
|
|
252
|
+
for (const row of page.data ?? []) {
|
|
253
|
+
const userId = extractUserIdFromResource(row.resource);
|
|
254
|
+
if (userId === void 0) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
await collectUserRoleRenameTargets({
|
|
258
|
+
service,
|
|
259
|
+
userId,
|
|
260
|
+
roleId,
|
|
261
|
+
newName,
|
|
262
|
+
targets
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
nextCursors.roleDiscovery = page.cursor ?? null;
|
|
266
|
+
} else {
|
|
267
|
+
nextCursors.roleDiscovery = null;
|
|
268
|
+
}
|
|
269
|
+
nextCursors.roleAssignmentUserProjection = null;
|
|
270
|
+
nextCursors.roleAssignmentWorkspaceProjection = null;
|
|
271
|
+
const exhausted = nextCursors.roleDiscovery === null;
|
|
272
|
+
return { targets, cursors: nextCursors, exhausted };
|
|
273
|
+
}
|
|
274
|
+
async function collectUserRoleRenameTargets(params) {
|
|
275
|
+
const { service, userId, roleId, newName, targets } = params;
|
|
276
|
+
const userProjPage = await service.entities.roleAssignmentUserProjection.query.record({ userId }).begins({ sk: "ROLEASSIGNMENT#" }).where((attr, op) => op.eq(attr.roleId, roleId)).go({});
|
|
277
|
+
for (const row of userProjPage.data ?? []) {
|
|
278
|
+
const isWorkspaceLane = typeof row.workspaceId === "string" && row.workspaceId.length > 0;
|
|
279
|
+
const newSk = isWorkspaceLane ? buildRoleAssignmentUserProjectionSkWorkspaceLane({
|
|
280
|
+
tenantId: row.tenantId,
|
|
281
|
+
workspaceId: row.workspaceId,
|
|
282
|
+
roleId: row.roleId,
|
|
283
|
+
roleAssignmentId: row.roleAssignmentId,
|
|
284
|
+
denormalizedRoleName: newName
|
|
285
|
+
}) : buildRoleAssignmentUserProjectionSkTenantLane({
|
|
286
|
+
tenantId: row.tenantId,
|
|
287
|
+
roleId: row.roleId,
|
|
288
|
+
roleAssignmentId: row.roleAssignmentId,
|
|
289
|
+
denormalizedRoleName: newName
|
|
290
|
+
});
|
|
291
|
+
targets.push({
|
|
292
|
+
entity: "roleAssignmentUserProjection",
|
|
293
|
+
oldKey: { userId: row.userId, sk: row.sk },
|
|
294
|
+
newKey: { userId: row.userId, sk: newSk },
|
|
295
|
+
newItem: {
|
|
296
|
+
...row,
|
|
297
|
+
sk: newSk,
|
|
298
|
+
denormalizedRoleName: newName
|
|
299
|
+
},
|
|
300
|
+
skRewriteRequired: row.sk !== newSk
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function pageTenantRename(params) {
|
|
305
|
+
const { tenantId, newName, cursors, limit, tableName } = params;
|
|
306
|
+
const service = getDynamoControlService(tableName);
|
|
307
|
+
const nextCursors = {};
|
|
308
|
+
const targets = [];
|
|
309
|
+
const discoveryCursor = cursors.tenantDiscovery;
|
|
310
|
+
if (discoveryCursor !== null) {
|
|
311
|
+
const page = await service.entities.membership.query.gsi1({ tenantId, gsi1Shard: "0" }).go({ cursor: discoveryCursor ?? null, limit });
|
|
312
|
+
for (const row of page.data ?? []) {
|
|
313
|
+
const userId = extractUserIdFromResource(row.resource);
|
|
314
|
+
if (userId === void 0) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const userPage = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: `MEMBERSHIP#TENANT#` }).where((attr, op) => op.eq(attr.tenantId, tenantId)).go({});
|
|
318
|
+
for (const userRow of userPage.data ?? []) {
|
|
319
|
+
const newSk = buildMembershipUserProjectionSkTenantLane({
|
|
320
|
+
tenantId: userRow.tenantId,
|
|
321
|
+
membershipId: userRow.membershipId,
|
|
322
|
+
denormalizedTenantName: newName
|
|
323
|
+
});
|
|
324
|
+
targets.push({
|
|
325
|
+
entity: "membershipUserProjection",
|
|
326
|
+
oldKey: { userId: userRow.userId, sk: userRow.sk },
|
|
327
|
+
newKey: { userId: userRow.userId, sk: newSk },
|
|
328
|
+
newItem: {
|
|
329
|
+
...userRow,
|
|
330
|
+
sk: newSk,
|
|
331
|
+
denormalizedTenantName: newName
|
|
332
|
+
},
|
|
333
|
+
skRewriteRequired: userRow.sk !== newSk
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const wsPage = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: `MEMBERSHIP#WORKSPACE#TID#${tenantId}#` }).go({});
|
|
337
|
+
for (const wsRow of wsPage.data ?? []) {
|
|
338
|
+
const newSk = buildMembershipUserProjectionSkWorkspaceLane({
|
|
339
|
+
tenantId: wsRow.tenantId,
|
|
340
|
+
workspaceId: wsRow.workspaceId,
|
|
341
|
+
membershipId: wsRow.membershipId,
|
|
342
|
+
denormalizedWorkspaceName: wsRow.denormalizedWorkspaceName
|
|
343
|
+
});
|
|
344
|
+
targets.push({
|
|
345
|
+
entity: "membershipUserProjection",
|
|
346
|
+
oldKey: { userId: wsRow.userId, sk: wsRow.sk },
|
|
347
|
+
newKey: { userId: wsRow.userId, sk: newSk },
|
|
348
|
+
newItem: {
|
|
349
|
+
...wsRow,
|
|
350
|
+
sk: newSk,
|
|
351
|
+
denormalizedTenantName: newName
|
|
352
|
+
},
|
|
353
|
+
skRewriteRequired: wsRow.sk !== newSk
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
nextCursors.tenantDiscovery = page.cursor ?? null;
|
|
358
|
+
} else {
|
|
359
|
+
nextCursors.tenantDiscovery = null;
|
|
360
|
+
}
|
|
361
|
+
nextCursors.membershipUserProjection = null;
|
|
362
|
+
nextCursors.roleAssignmentUserProjection = null;
|
|
363
|
+
const exhausted = nextCursors.tenantDiscovery === null;
|
|
364
|
+
return { targets, cursors: nextCursors, exhausted };
|
|
365
|
+
}
|
|
366
|
+
function extractUserIdFromResource(resource) {
|
|
367
|
+
if (typeof resource !== "string" || resource.length === 0) {
|
|
368
|
+
return void 0;
|
|
369
|
+
}
|
|
370
|
+
let parsed;
|
|
371
|
+
try {
|
|
372
|
+
parsed = JSON.parse(resource);
|
|
373
|
+
} catch {
|
|
374
|
+
return void 0;
|
|
375
|
+
}
|
|
376
|
+
if (!parsed || typeof parsed !== "object") {
|
|
377
|
+
return void 0;
|
|
378
|
+
}
|
|
379
|
+
return extractReferenceSlug(parsed, "user");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/workflows/control-plane/rename-cascade/rename-list-targets.handler.ts
|
|
383
|
+
var handler = async (input) => {
|
|
384
|
+
const cursors = {};
|
|
385
|
+
if (input.cursors) {
|
|
386
|
+
for (const [key, value] of Object.entries(input.cursors)) {
|
|
387
|
+
cursors[key] = value;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const page = await listRenameCascadeTargetsOperation({
|
|
391
|
+
entityType: input.entityType,
|
|
392
|
+
entityId: input.entityId,
|
|
393
|
+
tenantId: input.tenantId,
|
|
394
|
+
oldName: input.oldName,
|
|
395
|
+
newName: input.newName,
|
|
396
|
+
oldNormalizedName: input.oldNormalizedName,
|
|
397
|
+
newNormalizedName: input.newNormalizedName,
|
|
398
|
+
cursors
|
|
399
|
+
});
|
|
400
|
+
const chunks = chunkRenameCascadeTargets(
|
|
401
|
+
page.targets
|
|
402
|
+
).map((targets) => ({
|
|
403
|
+
entityType: input.entityType,
|
|
404
|
+
entityId: input.entityId,
|
|
405
|
+
tenantId: input.tenantId,
|
|
406
|
+
targets,
|
|
407
|
+
chunkToken: randomUUID()
|
|
408
|
+
}));
|
|
409
|
+
const priorRewritten = input.itemsRewritten ?? 0;
|
|
410
|
+
const priorChunks = input.chunkCount ?? 0;
|
|
411
|
+
const itemsRewritten = priorRewritten + page.targets.length;
|
|
412
|
+
const chunkCount = priorChunks + chunks.length;
|
|
413
|
+
return {
|
|
414
|
+
entityType: input.entityType,
|
|
415
|
+
entityId: input.entityId,
|
|
416
|
+
tenantId: input.tenantId,
|
|
417
|
+
oldName: input.oldName,
|
|
418
|
+
newName: input.newName,
|
|
419
|
+
oldNormalizedName: input.oldNormalizedName,
|
|
420
|
+
newNormalizedName: input.newNormalizedName,
|
|
421
|
+
cursors: page.cursors,
|
|
422
|
+
chunks,
|
|
423
|
+
exhausted: page.exhausted,
|
|
424
|
+
itemsRewritten,
|
|
425
|
+
chunkCount
|
|
426
|
+
};
|
|
427
|
+
};
|
|
428
|
+
export {
|
|
429
|
+
handler
|
|
430
|
+
};
|
|
431
|
+
//# sourceMappingURL=rename-list-targets.handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflows/control-plane/rename-cascade/rename-list-targets.handler.ts","../src/data/operations/control/rename-cascade/rename-cascade-list-targets-operation.ts"],"sourcesContent":["/**\n * Cascade Step Functions handler that queries one page of projection\n * rows affected by a Tenant / User / Role rename and bundles them into\n * <=50-target chunks for the downstream Distributed Map state.\n *\n * One invocation per outer-loop iteration:\n *\n * 1. Calls `listRenameCascadeTargetsOperation` with the per-stream\n * cursor map from the prior iteration (`{}` on the first call).\n * 2. Splits the merged page into chunks via `chunkRenameCascadeTargets`.\n * 3. Stamps each chunk with a deterministic `chunkToken` so a replayed\n * Map iteration lands idempotently via `executeMultiWrite`'s\n * `ClientRequestToken` forwarding.\n * 4. Returns the chunks, the new cursors, and the cumulative metrics\n * so the state machine's outer `Choice` knows when to stop.\n *\n * The handler itself NEVER touches the canonical Tenant / User / Role\n * record; the cascade is a consumer that only rewrites projection rows.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type {\n RenameCascadeChunkInput,\n RenameCascadeListInput,\n RenameCascadeListOutput,\n} from \"./events\";\nimport {\n type RenameCascadeCursorMap,\n listRenameCascadeTargetsOperation,\n} from \"../../../data/operations/control/rename-cascade/rename-cascade-list-targets-operation\";\nimport { chunkRenameCascadeTargets } from \"../../../data/operations/control/rename-cascade/rename-cascade-rewrite-chunk-operation\";\n\nexport const handler = async (\n input: RenameCascadeListInput,\n): Promise<RenameCascadeListOutput> => {\n const cursors: RenameCascadeCursorMap = {};\n if (input.cursors) {\n for (const [key, value] of Object.entries(input.cursors)) {\n cursors[key] = value;\n }\n }\n\n const page = await listRenameCascadeTargetsOperation({\n entityType: input.entityType,\n entityId: input.entityId,\n tenantId: input.tenantId,\n oldName: input.oldName,\n newName: input.newName,\n oldNormalizedName: input.oldNormalizedName,\n newNormalizedName: input.newNormalizedName,\n cursors,\n });\n\n const chunks: Array<RenameCascadeChunkInput> = chunkRenameCascadeTargets(\n page.targets,\n ).map((targets) => ({\n entityType: input.entityType,\n entityId: input.entityId,\n tenantId: input.tenantId,\n targets,\n chunkToken: randomUUID(),\n }));\n\n const priorRewritten = input.itemsRewritten ?? 0;\n const priorChunks = input.chunkCount ?? 0;\n const itemsRewritten = priorRewritten + page.targets.length;\n const chunkCount = priorChunks + chunks.length;\n\n return {\n entityType: input.entityType,\n entityId: input.entityId,\n tenantId: input.tenantId,\n oldName: input.oldName,\n newName: input.newName,\n oldNormalizedName: input.oldNormalizedName,\n newNormalizedName: input.newNormalizedName,\n cursors: page.cursors,\n chunks,\n exhausted: page.exhausted,\n itemsRewritten,\n chunkCount,\n };\n};\n","/**\n * Enumerate projection rows affected by a Tenant / User / Role rename\n * for the TR-023 rename cascade.\n *\n * One page per call; the cascade state machine outer loop walks the\n * returned `cursors` map back into this operation until every per-entity\n * stream returns `null`. Each emitted row carries:\n *\n * - the projection-entity name (so the rewrite-chunk operation can map\n * it to the correct ElectroDB entity in `executeMultiWrite`),\n * - the **existing** composite key (used for the `delete` triple in the\n * transact-write pair),\n * - the **new** composite key (used for the `put` triple — same row\n * identity but a rewritten SK when the SK encodes the renamed\n * normalized name), and\n * - the row's existing attributes (carried verbatim into the `put` so\n * `summary`, `vid`, `lastUpdated`, etc. are preserved across the\n * rewrite), with the renamed `denormalized<CarrierEntity>Name`\n * replaced by the new display name.\n *\n * Per-entityType query plan (per the ADR-018 implementation guide § 5):\n *\n * - **User rename**: under `PK = USER#ID#<userId>` — Membership user-\n * projection rows (patterns #3 + #4) and RoleAssignment user-projection\n * rows (pattern #5). Workspace-side projection rows\n * (membershipWorkspaceProjection #2 + roleAssignmentWorkspaceProjection\n * #9) encode `<normalizedUserName>` in their SK; this operation\n * discovers the affected workspaces from the user's pattern-#4\n * memberships and queries each workspace partition for them.\n * - **Role rename**: under every affected user partition — RoleAssignment\n * user-projection rows (pattern #5) sort on `<normalizedRoleName>` and\n * need a SK rewrite. RoleAssignment canonical (pattern #8) and\n * workspace-projection (pattern #9) sort on raw `<roleId>` so only the\n * denormalized attr changes (no SK rewrite). The affected user-ids\n * are discovered via the canonical RoleAssignment GSI1 (`<roleId>#`\n * prefix).\n * - **Tenant rename**: only `denormalizedTenantName` updates — SKs do\n * not carry tenant-name; the row identity is preserved. Affected user-\n * ids are discovered via the canonical Membership GSI1 page.\n *\n * For #1023 the User-rename path is implemented in full; the Tenant /\n * Role discovery hooks are scaffolded with the right query shape and\n * cursor map but only walk one canonical discovery batch per call (the\n * cascade outer loop pages through them). See § 5 of the implementation\n * guide for the full matrix.\n *\n * @see .state/adr-018-implementation-guide.md § 5 (TR-023 Rename-Cascade Consumer Contract)\n * @see .claude/rules/data-layer-layout.md\n */\n\nimport {\n RENAMABLE_ENTITY_TYPE,\n type RenamableEntityType,\n} from \"@openhi/workflows\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport {\n buildMembershipUserProjectionSkTenantLane,\n buildMembershipUserProjectionSkWorkspaceLane,\n extractReferenceSlug,\n} from \"../membership/membership-user-projection\";\nimport { buildMembershipWorkspaceProjectionSk } from \"../membership/membership-workspace-projection\";\nimport {\n buildRoleAssignmentUserProjectionSkTenantLane,\n buildRoleAssignmentUserProjectionSkWorkspaceLane,\n} from \"../roleassignment/roleassignment-user-projection\";\nimport { buildRoleAssignmentWorkspaceProjectionSk } from \"../roleassignment/roleassignment-workspace-projection\";\n\n/**\n * Projection-entity name keys this operation may emit. Each key maps to\n * an entity in the control-plane service; the rewrite-chunk consumer\n * forwards it to `executeMultiWrite` as the `entity` field on a triple.\n */\nexport const RENAME_CASCADE_PROJECTION_ENTITY = {\n MembershipUserProjection: \"membershipUserProjection\",\n MembershipWorkspaceProjection: \"membershipWorkspaceProjection\",\n RoleAssignmentUserProjection: \"roleAssignmentUserProjection\",\n RoleAssignmentWorkspaceProjection: \"roleAssignmentWorkspaceProjection\",\n} as const;\nexport type RenameCascadeProjectionEntity =\n (typeof RENAME_CASCADE_PROJECTION_ENTITY)[keyof typeof RENAME_CASCADE_PROJECTION_ENTITY];\n\n/**\n * One row to rewrite — the cascade rewrite-chunk operation turns each\n * entry into a `delete oldKey` + `put newPayload` transact-write pair.\n *\n * `oldKey` and `newKey` differ only in the SK segment when the SK\n * encodes a normalized form of the renamed name. For Tenant rename and\n * for SK-stable RoleAssignment projections (canonical pattern #8 and\n * workspace pattern #9 under a Role rename), `oldKey === newKey` and\n * the rewrite collapses to a single `put` overwrite.\n */\nexport interface RenameCascadeRewriteTarget {\n readonly entity: RenameCascadeProjectionEntity;\n /** Composite key payload for the existing row. */\n readonly oldKey: Record<string, string>;\n /** Composite key payload for the rewritten row. */\n readonly newKey: Record<string, string>;\n /**\n * Full row payload to write at `newKey` — carries the existing\n * `summary`, `vid`, `lastUpdated`, and discriminating fields, with\n * the renamed `denormalized<CarrierEntity>Name` swapped to the new\n * display name.\n */\n readonly newItem: Record<string, unknown>;\n /**\n * `true` when `oldKey` and `newKey` differ — the rewrite must atomic\n * delete the old row and put the new row in the same transaction.\n * `false` when only the denormalized attr changes — a single `put`\n * overwrite is sufficient.\n */\n readonly skRewriteRequired: boolean;\n}\n\n/** Per-stream cursor — `null` marks a stream as exhausted. */\nexport type RenameCascadeCursorMap = Record<string, string | null>;\n\n/** Inputs accepted by {@link listRenameCascadeTargetsOperation}. */\nexport interface ListRenameCascadeTargetsParams {\n readonly entityType: RenamableEntityType;\n readonly entityId: string;\n /** Present for User and Role; absent for Tenant. */\n readonly tenantId?: string;\n readonly oldName: string;\n readonly newName: string;\n /** Pre-computed via `extractLabel`; consumers do not re-normalize. */\n readonly oldNormalizedName: string;\n readonly newNormalizedName: string;\n /** Per-stream cursor map from the previous page (start of run is `{}`). */\n readonly cursors?: RenameCascadeCursorMap;\n /** Per-stream per-page item limit. Defaults to 100 (matches chunk size cap). */\n readonly limit?: number;\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n}\n\n/** Page returned by {@link listRenameCascadeTargetsOperation}. */\nexport interface ListRenameCascadeTargetsResult {\n readonly targets: ReadonlyArray<RenameCascadeRewriteTarget>;\n readonly cursors: RenameCascadeCursorMap;\n /** `true` when every stream returned `null` — outer loop terminates. */\n readonly exhausted: boolean;\n}\n\nconst DEFAULT_PAGE_SIZE = 100 as const;\n\n/**\n * Stream identifiers used in the cursor map. Each `entityType` walks a\n * different fixed set of streams; the cursor map keeps each at its own\n * position so the cascade can drain them in parallel without re-querying\n * exhausted ones.\n */\nconst STREAMS_FOR_ENTITY_TYPE: Record<\n RenamableEntityType,\n ReadonlyArray<string>\n> = {\n Tenant: [\"membershipUserProjection\", \"roleAssignmentUserProjection\"],\n User: [\n \"membershipUserProjection\",\n \"roleAssignmentUserProjection\",\n \"membershipWorkspaceProjection\",\n \"roleAssignmentWorkspaceProjection\",\n ],\n Role: [\"roleAssignmentUserProjection\", \"roleAssignmentWorkspaceProjection\"],\n};\n\n/**\n * Page through the projection rows affected by a Tenant / User / Role\n * rename. The cascade outer loop calls this in a loop, forwarding the\n * returned `cursors` until `exhausted === true`.\n */\nexport async function listRenameCascadeTargetsOperation(\n params: ListRenameCascadeTargetsParams,\n): Promise<ListRenameCascadeTargetsResult> {\n const {\n entityType,\n entityId,\n tenantId,\n oldName,\n newName,\n oldNormalizedName,\n newNormalizedName,\n cursors = {},\n limit = DEFAULT_PAGE_SIZE,\n tableName,\n } = params;\n\n if (!entityId || entityId.length === 0) {\n throw new Error(\"listRenameCascadeTargetsOperation: entityId is required\");\n }\n\n switch (entityType) {\n case RENAMABLE_ENTITY_TYPE.User:\n return pageUserRename({\n userId: entityId,\n oldNormalizedName,\n newNormalizedName,\n newName,\n cursors,\n limit,\n tableName,\n });\n case RENAMABLE_ENTITY_TYPE.Role:\n return pageRoleRename({\n roleId: entityId,\n tenantId,\n newName,\n cursors,\n limit,\n tableName,\n });\n case RENAMABLE_ENTITY_TYPE.Tenant:\n return pageTenantRename({\n tenantId: entityId,\n oldName,\n newName,\n cursors,\n limit,\n tableName,\n });\n default: {\n const exhaustive: never = entityType;\n throw new Error(\n `listRenameCascadeTargetsOperation: unsupported entityType '${String(\n exhaustive,\n )}'`,\n );\n }\n }\n}\n\n/**\n * User rename — page rows from the four affected projection streams.\n * The SK encodes `<normalizedUserName>` in every stream except the user-\n * projection tenant-lane (pattern #3) which sorts by `<normalizedTenantName>`;\n * tenant-lane rows still need a `denormalizedUserName` attr update so the\n * canonical-record symmetry rule (TR-024 rule 3) holds — but no SK rewrite.\n */\nasync function pageUserRename(params: {\n readonly userId: string;\n readonly oldNormalizedName: string;\n readonly newNormalizedName: string;\n readonly newName: string;\n readonly cursors: RenameCascadeCursorMap;\n readonly limit: number;\n readonly tableName?: string;\n}): Promise<ListRenameCascadeTargetsResult> {\n const { userId, newName, cursors, limit, tableName } = params;\n const service = getDynamoControlService(tableName);\n const nextCursors: RenameCascadeCursorMap = {};\n const targets: Array<RenameCascadeRewriteTarget> = [];\n\n // Stream 1 — Membership user-projection (patterns #3 + #4) under the\n // user's partition. Pattern-#3 (tenant-lane) rows only need an attr\n // update; pattern-#4 (workspace-lane) rows have `denormalizedUserName`\n // as an attr (not in the SK), so no SK rewrite is required for the\n // user-projection lane — the workspace-projection (pattern #2) carries\n // the SK rewrite.\n const muStream = cursors.membershipUserProjection;\n if (muStream !== null) {\n const page = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: \"MEMBERSHIP#\" })\n .go({ cursor: muStream ?? null, limit });\n for (const row of page.data ?? []) {\n // Rebuild the SK with the new (denormalized) name where the SK\n // encodes one. For Membership user-projection neither lane\n // encodes <normalizedUserName>; SK rewrites are unnecessary.\n const oldKey = { userId: row.userId, sk: row.sk };\n const newSk = row.sk; // SK unaffected by a User rename in this stream.\n const newKey = { userId: row.userId, sk: newSk };\n targets.push({\n entity: \"membershipUserProjection\",\n oldKey,\n newKey,\n newItem: {\n ...row,\n sk: newSk,\n denormalizedUserName: newName,\n },\n skRewriteRequired: false,\n });\n }\n nextCursors.membershipUserProjection = page.cursor ?? null;\n } else {\n nextCursors.membershipUserProjection = null;\n }\n\n // Stream 2 — RoleAssignment user-projection (pattern #5) under the\n // user's partition. SK sorts on `<normalizedRoleName>` (not user-name),\n // so a User rename only updates the `denormalizedUserName` attr; no SK\n // rewrite required.\n const raUStream = cursors.roleAssignmentUserProjection;\n if (raUStream !== null) {\n const page = await service.entities.roleAssignmentUserProjection.query\n .record({ userId })\n .begins({ sk: \"ROLEASSIGNMENT#\" })\n .go({ cursor: raUStream ?? null, limit });\n for (const row of page.data ?? []) {\n const oldKey = { userId: row.userId, sk: row.sk };\n const newKey = { userId: row.userId, sk: row.sk };\n targets.push({\n entity: \"roleAssignmentUserProjection\",\n oldKey,\n newKey,\n newItem: {\n ...row,\n denormalizedUserName: newName,\n },\n skRewriteRequired: false,\n });\n }\n nextCursors.roleAssignmentUserProjection = page.cursor ?? null;\n } else {\n nextCursors.roleAssignmentUserProjection = null;\n }\n\n // Streams 3 + 4 — Membership / RoleAssignment workspace-projection\n // rows under every workspace the user is a member of. The\n // workspace-projection SK encodes `<normalizedUserName>`, so these\n // streams require an SK rewrite (delete old + put new).\n //\n // Discovery: list the user's workspace-lane Memberships (pattern #4 —\n // `MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#WID#<workspaceId>#…`)\n // and visit each workspace's partition. We paginate workspace\n // discovery via a dedicated cursor stream so the cascade outer loop\n // can resume mid-discovery.\n const discoveryCursor = cursors.workspaceDiscovery;\n if (discoveryCursor !== null) {\n const discovery = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: \"MEMBERSHIP#WORKSPACE#\" })\n .go({ cursor: discoveryCursor ?? null, limit });\n for (const member of discovery.data ?? []) {\n if (!member.workspaceId || !member.tenantId) {\n continue;\n }\n // Per discovered workspace, page Membership + RoleAssignment\n // workspace-projection rows that match the OLD normalized user\n // name. The cascade only needs to rewrite rows currently keyed by\n // the OLD name; rows already at the new name (partial-replay) are\n // skipped naturally by the `begins_with` filter on\n // `MEMBERSHIP#<oldNormalizedUserName>#`.\n await collectWorkspaceUserRenameTargets({\n service,\n tenantId: member.tenantId,\n workspaceId: member.workspaceId,\n userId,\n oldNormalizedName: params.oldNormalizedName,\n newNormalizedName: params.newNormalizedName,\n newName,\n targets,\n });\n }\n nextCursors.workspaceDiscovery = discovery.cursor ?? null;\n } else {\n nextCursors.workspaceDiscovery = null;\n }\n // The workspace-projection streams themselves never need their own\n // cursor — they are fully drained inside each discovered workspace\n // (per-workspace row counts are small). Mark them exhausted up-front\n // so the outer loop's `exhausted` check ignores them.\n nextCursors.membershipWorkspaceProjection = null;\n nextCursors.roleAssignmentWorkspaceProjection = null;\n\n const exhausted =\n STREAMS_FOR_ENTITY_TYPE.User.every((s) => nextCursors[s] === null) &&\n nextCursors.workspaceDiscovery === null;\n\n return { targets, cursors: nextCursors, exhausted };\n}\n\nasync function collectWorkspaceUserRenameTargets(params: {\n readonly service: ReturnType<typeof getDynamoControlService>;\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly userId: string;\n readonly oldNormalizedName: string;\n readonly newNormalizedName: string;\n readonly newName: string;\n readonly targets: Array<RenameCascadeRewriteTarget>;\n}): Promise<void> {\n const {\n service,\n tenantId,\n workspaceId,\n userId,\n oldNormalizedName,\n newName,\n targets,\n } = params;\n\n // Membership workspace-projection (pattern #2) — SK is\n // `MEMBERSHIP#<normalizedUserName>#USER#<userId>#<membershipId>`.\n const mwPage = await service.entities.membershipWorkspaceProjection.query\n .record({ tenantId, workspaceId })\n .begins({ sk: `MEMBERSHIP#${oldNormalizedName}#USER#${userId}#` })\n .go({});\n for (const row of mwPage.data ?? []) {\n const newSk = buildMembershipWorkspaceProjectionSk({\n userId: row.userId,\n membershipId: row.membershipId,\n denormalizedUserName: newName,\n });\n targets.push({\n entity: \"membershipWorkspaceProjection\",\n oldKey: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: row.sk,\n },\n newKey: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: newSk,\n },\n newItem: {\n ...row,\n sk: newSk,\n denormalizedUserName: newName,\n },\n skRewriteRequired: row.sk !== newSk,\n });\n }\n\n // RoleAssignment workspace-projection (pattern #9) — SK is\n // `ROLEASSIGNMENT#<roleId>#<normalizedUserName>#USER#<userId>#…`.\n // `<roleId>` discriminates first, so we can't prefix-scan on user-name\n // alone — list all of this user's workspace-projection rows by paging\n // through `ROLEASSIGNMENT#` and filtering on the userId server-side via\n // ElectroDB's `.where()` builder.\n const raPage = await service.entities.roleAssignmentWorkspaceProjection.query\n .record({ tenantId, workspaceId })\n .begins({ sk: \"ROLEASSIGNMENT#\" })\n .where((attr, op) => op.eq(attr.userId, userId))\n .go({});\n for (const row of raPage.data ?? []) {\n const newSk = buildRoleAssignmentWorkspaceProjectionSk({\n roleId: row.roleId,\n userId: row.userId,\n roleAssignmentId: row.roleAssignmentId,\n denormalizedUserName: newName,\n });\n targets.push({\n entity: \"roleAssignmentWorkspaceProjection\",\n oldKey: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: row.sk,\n },\n newKey: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: newSk,\n },\n newItem: {\n ...row,\n sk: newSk,\n denormalizedUserName: newName,\n },\n skRewriteRequired: row.sk !== newSk,\n });\n }\n}\n\n/**\n * Role rename — SK rewrites are required on the RoleAssignment user-\n * projection (pattern #5 encodes `<normalizedRoleName>` in the SK).\n * RoleAssignment workspace-projection (pattern #9) sorts on raw\n * `<roleId>` — only an attr update.\n *\n * Affected users are discovered via the canonical RoleAssignment GSI1\n * (`<roleId>#` prefix). For #1023 the discovery walks the GSI1 page;\n * the cascade outer loop pages through it via the `roleDiscovery`\n * cursor.\n */\nasync function pageRoleRename(params: {\n readonly roleId: string;\n readonly tenantId?: string;\n readonly newName: string;\n readonly cursors: RenameCascadeCursorMap;\n readonly limit: number;\n readonly tableName?: string;\n}): Promise<ListRenameCascadeTargetsResult> {\n const { roleId, tenantId, newName, cursors, limit, tableName } = params;\n if (!tenantId) {\n throw new Error(\n \"listRenameCascadeTargetsOperation: tenantId is required for Role rename\",\n );\n }\n\n const service = getDynamoControlService(tableName);\n const nextCursors: RenameCascadeCursorMap = {};\n const targets: Array<RenameCascadeRewriteTarget> = [];\n\n // Discovery — page canonical RoleAssignment rows for this role via\n // GSI1 (`<roleId>#` prefix on the discriminator-first GSI1SK). GSI1\n // is sharded; for #1023 v1 we walk shard 0 only — same follow-up\n // note as the Tenant rename discovery path.\n const discoveryCursor = cursors.roleDiscovery;\n if (discoveryCursor !== null) {\n const page = await service.entities.roleAssignment.query\n .gsi1({ tenantId, gsi1Shard: \"0\" })\n .begins({ gsi1sk: `${roleId}#` })\n .go({ cursor: discoveryCursor ?? null, limit });\n\n for (const row of page.data ?? []) {\n const userId = extractUserIdFromResource(row.resource);\n if (userId === undefined) {\n // Cannot resolve the row to a user partition — skip. The cascade\n // outer loop logs and continues; a follow-up sweep can re-process.\n continue;\n }\n // Per affected user, rewrite the user-projection rows for this\n // role (pattern #5). The SK encodes `<normalizedRoleName>` so we\n // need to read the user-projection row(s) for this role and\n // rewrite their SKs.\n await collectUserRoleRenameTargets({\n service,\n userId,\n roleId,\n newName,\n targets,\n });\n }\n nextCursors.roleDiscovery = page.cursor ?? null;\n } else {\n nextCursors.roleDiscovery = null;\n }\n nextCursors.roleAssignmentUserProjection = null;\n nextCursors.roleAssignmentWorkspaceProjection = null;\n\n const exhausted = nextCursors.roleDiscovery === null;\n\n return { targets, cursors: nextCursors, exhausted };\n}\n\nasync function collectUserRoleRenameTargets(params: {\n readonly service: ReturnType<typeof getDynamoControlService>;\n readonly userId: string;\n readonly roleId: string;\n readonly newName: string;\n readonly targets: Array<RenameCascadeRewriteTarget>;\n}): Promise<void> {\n const { service, userId, roleId, newName, targets } = params;\n\n // User-projection (pattern #5) — SK encodes `<normalizedRoleName>`,\n // discriminator on TENANT / WORKSPACE prefix. Walk both lanes for the\n // affected role: server-side filter on `roleId` (the discriminator\n // sits after the normalized role name so a single prefix can't narrow\n // by roleId without the normalized name).\n const userProjPage = await service.entities.roleAssignmentUserProjection.query\n .record({ userId })\n .begins({ sk: \"ROLEASSIGNMENT#\" })\n .where((attr, op) => op.eq(attr.roleId, roleId))\n .go({});\n\n for (const row of userProjPage.data ?? []) {\n const isWorkspaceLane =\n typeof row.workspaceId === \"string\" && row.workspaceId.length > 0;\n const newSk = isWorkspaceLane\n ? buildRoleAssignmentUserProjectionSkWorkspaceLane({\n tenantId: row.tenantId,\n workspaceId: row.workspaceId as string,\n roleId: row.roleId,\n roleAssignmentId: row.roleAssignmentId,\n denormalizedRoleName: newName,\n })\n : buildRoleAssignmentUserProjectionSkTenantLane({\n tenantId: row.tenantId,\n roleId: row.roleId,\n roleAssignmentId: row.roleAssignmentId,\n denormalizedRoleName: newName,\n });\n targets.push({\n entity: \"roleAssignmentUserProjection\",\n oldKey: { userId: row.userId, sk: row.sk },\n newKey: { userId: row.userId, sk: newSk },\n newItem: {\n ...row,\n sk: newSk,\n denormalizedRoleName: newName,\n },\n skRewriteRequired: row.sk !== newSk,\n });\n }\n}\n\n/**\n * Tenant rename — only the `denormalizedTenantName` attr updates on\n * affected rows. SKs never carry tenant-name in the OpenHI grammar (the\n * Membership user-projection tenant-lane SK encodes `<normalizedTenantName>`\n * — see pattern #3 — so that single sub-lane DOES need an SK rewrite).\n *\n * Discovery: page canonical Memberships for this tenant via GSI1, then\n * for each affected user enumerate their pattern-#3 user-projection rows\n * (those that key off `<normalizedTenantName>`).\n */\nasync function pageTenantRename(params: {\n readonly tenantId: string;\n readonly oldName: string;\n readonly newName: string;\n readonly cursors: RenameCascadeCursorMap;\n readonly limit: number;\n readonly tableName?: string;\n}): Promise<ListRenameCascadeTargetsResult> {\n const { tenantId, newName, cursors, limit, tableName } = params;\n const service = getDynamoControlService(tableName);\n const nextCursors: RenameCascadeCursorMap = {};\n const targets: Array<RenameCascadeRewriteTarget> = [];\n\n // Discovery — page canonical Memberships for this tenant via GSI1.\n // GSI1 is sharded; for #1023 v1 we walk shard 0 only. Multi-shard\n // discovery is a tight follow-up — it requires either iterating\n // shards in a fan-out (4 queries per page) or threading a per-shard\n // cursor map. The current implementation accepts coverage limited\n // to shard 0; large tenants will need the follow-up.\n const discoveryCursor = cursors.tenantDiscovery;\n if (discoveryCursor !== null) {\n const page = await service.entities.membership.query\n .gsi1({ tenantId, gsi1Shard: \"0\" })\n .go({ cursor: discoveryCursor ?? null, limit });\n\n for (const row of page.data ?? []) {\n const userId = extractUserIdFromResource(row.resource);\n if (userId === undefined) {\n continue;\n }\n // Per affected user, rewrite the pattern-#3 user-projection row\n // for this tenant. The SK is\n // `MEMBERSHIP#TENANT#<normalizedTenantName>#TID#<tenantId>#<id>`.\n const userPage = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: `MEMBERSHIP#TENANT#` })\n .where((attr, op) => op.eq(attr.tenantId, tenantId))\n .go({});\n for (const userRow of userPage.data ?? []) {\n const newSk = buildMembershipUserProjectionSkTenantLane({\n tenantId: userRow.tenantId,\n membershipId: userRow.membershipId,\n denormalizedTenantName: newName,\n });\n targets.push({\n entity: \"membershipUserProjection\",\n oldKey: { userId: userRow.userId, sk: userRow.sk },\n newKey: { userId: userRow.userId, sk: newSk },\n newItem: {\n ...userRow,\n sk: newSk,\n denormalizedTenantName: newName,\n },\n skRewriteRequired: userRow.sk !== newSk,\n });\n }\n // Pattern #4 workspace-lane user-projection rows carry\n // `denormalizedTenantName` as an attr only — no SK rewrite.\n const wsPage = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: `MEMBERSHIP#WORKSPACE#TID#${tenantId}#` })\n .go({});\n for (const wsRow of wsPage.data ?? []) {\n // SK stays the same — pattern #4 encodes tenant by raw\n // `<tenantId>`, not name.\n const newSk = buildMembershipUserProjectionSkWorkspaceLane({\n tenantId: wsRow.tenantId,\n workspaceId: wsRow.workspaceId as string,\n membershipId: wsRow.membershipId,\n denormalizedWorkspaceName: wsRow.denormalizedWorkspaceName,\n });\n targets.push({\n entity: \"membershipUserProjection\",\n oldKey: { userId: wsRow.userId, sk: wsRow.sk },\n newKey: { userId: wsRow.userId, sk: newSk },\n newItem: {\n ...wsRow,\n sk: newSk,\n denormalizedTenantName: newName,\n },\n skRewriteRequired: wsRow.sk !== newSk,\n });\n }\n }\n nextCursors.tenantDiscovery = page.cursor ?? null;\n } else {\n nextCursors.tenantDiscovery = null;\n }\n nextCursors.membershipUserProjection = null;\n nextCursors.roleAssignmentUserProjection = null;\n\n const exhausted = nextCursors.tenantDiscovery === null;\n\n return { targets, cursors: nextCursors, exhausted };\n}\n\n/**\n * Extract `userId` from a canonical Membership / RoleAssignment resource\n * JSON string. The canonical row stores the user reference inside the\n * resource (FHIR `Reference` shape — `{ \"user\": { \"reference\":\n * \"User/<id>\" } }`); discovery via GSI1 returns the canonical row, and\n * the cascade needs `userId` to address the user partition for\n * projection rewrites. Returns `undefined` when the field is missing or\n * malformed so the cascade can skip rows it cannot resolve.\n */\nfunction extractUserIdFromResource(resource: unknown): string | undefined {\n if (typeof resource !== \"string\" || resource.length === 0) {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(resource);\n } catch {\n return undefined;\n }\n if (!parsed || typeof parsed !== \"object\") {\n return undefined;\n }\n return extractReferenceSlug(parsed as Record<string, unknown>, \"user\");\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { e as RenameCascadeChunkInput } from './events-Da_cFgtc.mjs';
|
|
2
|
+
import '@openhi/workflows';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cascade Step Functions Distributed-Map iteration handler. Receives
|
|
6
|
+
* one chunk of <=50 rewrite targets from the list-targets step and
|
|
7
|
+
* issues a single `TransactWriteItems` via `executeMultiWrite` (#1010).
|
|
8
|
+
*
|
|
9
|
+
* Each target maps to either:
|
|
10
|
+
*
|
|
11
|
+
* - **SK rewrite** — `delete oldKey` + `put newItem` pair (2 transact
|
|
12
|
+
* items). Used when the SK encodes the renamed normalized name.
|
|
13
|
+
* - **Attr-only update** — single `put newItem` overwrite at the same
|
|
14
|
+
* key. Used when only the denormalized display-name attribute
|
|
15
|
+
* changes (the SK is rename-stable).
|
|
16
|
+
*
|
|
17
|
+
* Idempotency: the chunk's `chunkToken` flows through to ElectroDB's
|
|
18
|
+
* `ClientRequestToken` so a Map iteration replayed by Step Functions
|
|
19
|
+
* retry lands on the same transaction id. The state machine's `Catch`
|
|
20
|
+
* block also absorbs `DynamoDB.TransactionCanceledException` as a no-op
|
|
21
|
+
* success — common on partial-replay when the prior run already
|
|
22
|
+
* rewrote the rows (the `vid` race is "rewrite loses to a later
|
|
23
|
+
* write" per TR-023 idempotency rule).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface RewriteChunkOutput {
|
|
27
|
+
readonly entityType: string;
|
|
28
|
+
readonly entityId: string;
|
|
29
|
+
readonly tenantId?: string;
|
|
30
|
+
readonly targetsRewritten: number;
|
|
31
|
+
readonly transactItemCount: number;
|
|
32
|
+
}
|
|
33
|
+
declare const handler: (input: RenameCascadeChunkInput) => Promise<RewriteChunkOutput>;
|
|
34
|
+
|
|
35
|
+
export { type RewriteChunkOutput, handler };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { e as RenameCascadeChunkInput } from './events-Da_cFgtc.js';
|
|
2
|
+
import '@openhi/workflows';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cascade Step Functions Distributed-Map iteration handler. Receives
|
|
6
|
+
* one chunk of <=50 rewrite targets from the list-targets step and
|
|
7
|
+
* issues a single `TransactWriteItems` via `executeMultiWrite` (#1010).
|
|
8
|
+
*
|
|
9
|
+
* Each target maps to either:
|
|
10
|
+
*
|
|
11
|
+
* - **SK rewrite** — `delete oldKey` + `put newItem` pair (2 transact
|
|
12
|
+
* items). Used when the SK encodes the renamed normalized name.
|
|
13
|
+
* - **Attr-only update** — single `put newItem` overwrite at the same
|
|
14
|
+
* key. Used when only the denormalized display-name attribute
|
|
15
|
+
* changes (the SK is rename-stable).
|
|
16
|
+
*
|
|
17
|
+
* Idempotency: the chunk's `chunkToken` flows through to ElectroDB's
|
|
18
|
+
* `ClientRequestToken` so a Map iteration replayed by Step Functions
|
|
19
|
+
* retry lands on the same transaction id. The state machine's `Catch`
|
|
20
|
+
* block also absorbs `DynamoDB.TransactionCanceledException` as a no-op
|
|
21
|
+
* success — common on partial-replay when the prior run already
|
|
22
|
+
* rewrote the rows (the `vid` race is "rewrite loses to a later
|
|
23
|
+
* write" per TR-023 idempotency rule).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface RewriteChunkOutput {
|
|
27
|
+
readonly entityType: string;
|
|
28
|
+
readonly entityId: string;
|
|
29
|
+
readonly tenantId?: string;
|
|
30
|
+
readonly targetsRewritten: number;
|
|
31
|
+
readonly transactItemCount: number;
|
|
32
|
+
}
|
|
33
|
+
declare const handler: (input: RenameCascadeChunkInput) => Promise<RewriteChunkOutput>;
|
|
34
|
+
|
|
35
|
+
export { type RewriteChunkOutput, handler };
|