@openhi/constructs 0.0.107 → 0.0.109
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-2TPJ6HOF.mjs +289 -0
- package/lib/chunk-2TPJ6HOF.mjs.map +1 -0
- package/lib/{chunk-L6UAP4KP.mjs → chunk-7FUAMZOF.mjs} +3 -3
- package/lib/{chunk-36UPY7YQ.mjs → chunk-7Q2IJ2J5.mjs} +5 -5
- package/lib/{chunk-YU2HRNUP.mjs → chunk-AJ3G3THO.mjs} +2 -2
- package/lib/chunk-BB5MK4L3.mjs +96 -0
- package/lib/chunk-BB5MK4L3.mjs.map +1 -0
- package/lib/chunk-IS4VQRI4.mjs +1390 -0
- package/lib/chunk-IS4VQRI4.mjs.map +1 -0
- package/lib/{chunk-AO3E22CS.mjs → chunk-MULKGFIJ.mjs} +74 -4
- package/lib/chunk-MULKGFIJ.mjs.map +1 -0
- package/lib/{chunk-VYDIGFIX.mjs → chunk-QR5JVSCF.mjs} +11 -1
- package/lib/chunk-QR5JVSCF.mjs.map +1 -0
- package/lib/index.js +1043 -5
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +5 -5
- package/lib/pre-token-generation.handler.js +1360 -11
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +102 -5
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +1189 -15
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +4 -5
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rest-api-lambda.handler.js +4374 -3691
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +2110 -2756
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js +1173 -9
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +5 -5
- package/lib/seed-system-data.handler.js +10 -0
- package/lib/seed-system-data.handler.js.map +1 -1
- package/lib/seed-system-data.handler.mjs +2 -2
- package/package.json +1 -1
- package/lib/chunk-AO3E22CS.mjs.map +0 -1
- package/lib/chunk-CHPEQRXU.mjs +0 -45
- package/lib/chunk-CHPEQRXU.mjs.map +0 -1
- package/lib/chunk-JUNL76HF.mjs +0 -428
- package/lib/chunk-JUNL76HF.mjs.map +0 -1
- package/lib/chunk-VYDIGFIX.mjs.map +0 -1
- package/lib/chunk-YZZDUJHI.mjs +0 -37
- package/lib/chunk-YZZDUJHI.mjs.map +0 -1
- /package/lib/{chunk-L6UAP4KP.mjs.map → chunk-7FUAMZOF.mjs.map} +0 -0
- /package/lib/{chunk-36UPY7YQ.mjs.map → chunk-7Q2IJ2J5.mjs.map} +0 -0
- /package/lib/{chunk-YU2HRNUP.mjs.map → chunk-AJ3G3THO.mjs.map} +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ForbiddenError,
|
|
3
|
+
NotFoundError,
|
|
4
|
+
ValidationError,
|
|
5
|
+
batchGetWithRetry,
|
|
6
|
+
dispatchListMode
|
|
7
|
+
} from "./chunk-IS4VQRI4.mjs";
|
|
8
|
+
import {
|
|
9
|
+
SHARD_COUNT,
|
|
10
|
+
getDynamoControlService
|
|
11
|
+
} from "./chunk-QR5JVSCF.mjs";
|
|
12
|
+
|
|
13
|
+
// src/data/operations/control/user/user-find-by-sub-operation.ts
|
|
14
|
+
async function findUserBySubOperation(params) {
|
|
15
|
+
const { cognitoSub, tableName } = params;
|
|
16
|
+
const service = getDynamoControlService(tableName);
|
|
17
|
+
const result = await service.entities.user.query.gsi2({ cognitoSub }).go({ limit: 1 });
|
|
18
|
+
const item = result.data?.[0];
|
|
19
|
+
if (!item) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
id: item.id,
|
|
24
|
+
cognitoSub: item.cognitoSub,
|
|
25
|
+
resource: item.resource,
|
|
26
|
+
vid: item.vid
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/data/operations/control/user/user-resource-helpers.ts
|
|
31
|
+
function parseUserResource(resource) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(resource);
|
|
34
|
+
} catch {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/data/operations/fhir-reference.ts
|
|
40
|
+
function idFromReference(reference, prefix) {
|
|
41
|
+
if (!reference || !reference.startsWith(prefix)) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
const id = reference.slice(prefix.length);
|
|
45
|
+
return id.length > 0 ? id : void 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/data/operations/control/user/user-create-operation.ts
|
|
49
|
+
import { extractSummary } from "@openhi/types";
|
|
50
|
+
async function createUserOperation(params) {
|
|
51
|
+
const { context, body, tableName } = params;
|
|
52
|
+
const service = getDynamoControlService(tableName);
|
|
53
|
+
const id = body.id ?? `user-${Date.now()}`;
|
|
54
|
+
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
55
|
+
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
56
|
+
const vid = `1`;
|
|
57
|
+
const resource = { resourceType: "User", id, ...parsedResource };
|
|
58
|
+
const summary = JSON.stringify(extractSummary(resource));
|
|
59
|
+
await service.entities.user.put({
|
|
60
|
+
id,
|
|
61
|
+
resource: JSON.stringify(resource),
|
|
62
|
+
summary,
|
|
63
|
+
vid,
|
|
64
|
+
lastUpdated
|
|
65
|
+
}).go();
|
|
66
|
+
return {
|
|
67
|
+
id,
|
|
68
|
+
resource,
|
|
69
|
+
meta: { lastUpdated, versionId: vid }
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/data/operations/control/user/user-get-by-id-operation.ts
|
|
74
|
+
async function getUserByIdOperation(params) {
|
|
75
|
+
const { id, tableName } = params;
|
|
76
|
+
const service = getDynamoControlService(tableName);
|
|
77
|
+
const response = await service.entities.user.get({ id, sk: "CURRENT" }).go();
|
|
78
|
+
const item = response.data;
|
|
79
|
+
if (!item) {
|
|
80
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
81
|
+
}
|
|
82
|
+
const parsedResource = JSON.parse(item.resource);
|
|
83
|
+
return {
|
|
84
|
+
id,
|
|
85
|
+
resource: { resourceType: "User", id, ...parsedResource }
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/data/operations/control/user/user-list-operation.ts
|
|
90
|
+
var SK = "CURRENT";
|
|
91
|
+
async function listUsersOperation(params) {
|
|
92
|
+
const { tableName, mode = "full" } = params;
|
|
93
|
+
const service = getDynamoControlService(tableName);
|
|
94
|
+
const shardResults = await Promise.all(
|
|
95
|
+
Array.from(
|
|
96
|
+
{ length: SHARD_COUNT },
|
|
97
|
+
(_, shard) => service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go()
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
return dispatchListMode(mode, shardResults, {
|
|
101
|
+
hydrate: (orderedIds) => batchGetWithRetry(
|
|
102
|
+
service.entities.user,
|
|
103
|
+
orderedIds.map((id) => ({ id, sk: SK }))
|
|
104
|
+
),
|
|
105
|
+
getId: (item) => item.id,
|
|
106
|
+
buildEntry: (id, item) => ({
|
|
107
|
+
id,
|
|
108
|
+
resource: {
|
|
109
|
+
resourceType: "User",
|
|
110
|
+
id,
|
|
111
|
+
...JSON.parse(item.resource)
|
|
112
|
+
}
|
|
113
|
+
}),
|
|
114
|
+
buildSummaryEntry: (id, parsed) => ({
|
|
115
|
+
id,
|
|
116
|
+
resource: { resourceType: "User", id, ...parsed }
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/data/operations/control/user/user-switch-tenant-workspace-operation.ts
|
|
122
|
+
import { extractSummary as extractSummary2 } from "@openhi/types";
|
|
123
|
+
|
|
124
|
+
// src/data/operations/control/membership/membership-find-for-user-in-tenant-operation.ts
|
|
125
|
+
var SK2 = "CURRENT";
|
|
126
|
+
async function findMembershipsForUserInTenantOperation(params) {
|
|
127
|
+
const { tenantId, userId, tableName } = params;
|
|
128
|
+
const service = getDynamoControlService(tableName);
|
|
129
|
+
const shardResults = await Promise.all(
|
|
130
|
+
Array.from(
|
|
131
|
+
{ length: SHARD_COUNT },
|
|
132
|
+
(_, shard) => service.entities.membership.query.gsi1({ tenantId, gsi1Shard: String(shard) }).go()
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
const expectedUserRef = `User/${userId}`;
|
|
136
|
+
const memberships = [];
|
|
137
|
+
for (const shard of shardResults) {
|
|
138
|
+
for (const item of shard.data ?? []) {
|
|
139
|
+
if (item.sk !== SK2) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(item.resource);
|
|
145
|
+
} catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const userRef = parsed.user?.reference;
|
|
149
|
+
if (userRef !== expectedUserRef) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
memberships.push({ id: item.id, resource: parsed });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { memberships };
|
|
156
|
+
}
|
|
157
|
+
function pickTenantMembership(memberships) {
|
|
158
|
+
return memberships.find(
|
|
159
|
+
(m) => m.resource.workspace == null
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
function pickWorkspaceMembership(memberships, workspaceId) {
|
|
163
|
+
const expectedWorkspaceRef = `Workspace/${workspaceId}`;
|
|
164
|
+
return memberships.find(
|
|
165
|
+
(m) => m.resource.workspace?.reference === expectedWorkspaceRef
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/data/operations/control/user/user-switch-tenant-workspace-operation.ts
|
|
170
|
+
var SK3 = "CURRENT";
|
|
171
|
+
async function switchUserTenantWorkspaceOperation(params) {
|
|
172
|
+
const { cognitoSub, tenantReference, workspaceReference, tableName } = params;
|
|
173
|
+
const tenantId = idFromReference(tenantReference, "Tenant/");
|
|
174
|
+
if (!tenantId) {
|
|
175
|
+
throw new ValidationError(
|
|
176
|
+
"tenant.reference must be a 'Tenant/<id>' reference."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const workspaceId = idFromReference(workspaceReference, "Workspace/");
|
|
180
|
+
if (!workspaceId) {
|
|
181
|
+
throw new ValidationError(
|
|
182
|
+
"workspace.reference must be a 'Workspace/<id>' reference."
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const user = await findUserBySubOperation({
|
|
186
|
+
// findUserBySubOperation does not read context fields; pass a stub.
|
|
187
|
+
context: {
|
|
188
|
+
tenantId: "",
|
|
189
|
+
workspaceId: "",
|
|
190
|
+
date: "",
|
|
191
|
+
actorId: "",
|
|
192
|
+
actorName: "",
|
|
193
|
+
actorType: "internal-system"
|
|
194
|
+
},
|
|
195
|
+
cognitoSub,
|
|
196
|
+
tableName
|
|
197
|
+
});
|
|
198
|
+
if (!user) {
|
|
199
|
+
throw new NotFoundError(
|
|
200
|
+
"User not yet provisioned for the authenticated Cognito subject."
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const { memberships } = await findMembershipsForUserInTenantOperation({
|
|
204
|
+
tenantId,
|
|
205
|
+
userId: user.id,
|
|
206
|
+
tableName
|
|
207
|
+
});
|
|
208
|
+
if (!pickTenantMembership(memberships)) {
|
|
209
|
+
throw new ForbiddenError(`User is not a member of Tenant/${tenantId}.`);
|
|
210
|
+
}
|
|
211
|
+
if (!pickWorkspaceMembership(memberships, workspaceId)) {
|
|
212
|
+
throw new ForbiddenError(
|
|
213
|
+
`User is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const existingResource = parseUserResource(user.resource) ?? {};
|
|
217
|
+
const updatedResource = {
|
|
218
|
+
...existingResource,
|
|
219
|
+
resourceType: "User",
|
|
220
|
+
id: user.id,
|
|
221
|
+
currentTenant: { reference: `Tenant/${tenantId}` },
|
|
222
|
+
currentWorkspace: { reference: `Workspace/${workspaceId}` }
|
|
223
|
+
};
|
|
224
|
+
const lastUpdated = (params.now ? params.now() : /* @__PURE__ */ new Date()).toISOString();
|
|
225
|
+
const vid = `${Date.now()}`;
|
|
226
|
+
const summary = JSON.stringify(
|
|
227
|
+
extractSummary2(updatedResource)
|
|
228
|
+
);
|
|
229
|
+
const service = getDynamoControlService(tableName);
|
|
230
|
+
await service.entities.user.patch({ id: user.id, sk: SK3 }).set({
|
|
231
|
+
resource: JSON.stringify(updatedResource),
|
|
232
|
+
summary,
|
|
233
|
+
vid,
|
|
234
|
+
lastUpdated
|
|
235
|
+
}).go();
|
|
236
|
+
return {
|
|
237
|
+
id: user.id,
|
|
238
|
+
resource: updatedResource,
|
|
239
|
+
meta: { lastUpdated, versionId: vid }
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/data/operations/control/user/user-update-operation.ts
|
|
244
|
+
import { extractSummary as extractSummary3 } from "@openhi/types";
|
|
245
|
+
async function updateUserOperation(params) {
|
|
246
|
+
const { context, id, body, tableName } = params;
|
|
247
|
+
const service = getDynamoControlService(tableName);
|
|
248
|
+
const existing = await service.entities.user.get({ id, sk: "CURRENT" }).go();
|
|
249
|
+
if (!existing.data) {
|
|
250
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
251
|
+
}
|
|
252
|
+
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
253
|
+
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
254
|
+
const vid = `${Date.now()}`;
|
|
255
|
+
const resource = { resourceType: "User", id, ...parsedResource };
|
|
256
|
+
const summary = JSON.stringify(extractSummary3(resource));
|
|
257
|
+
await service.entities.user.put({
|
|
258
|
+
id,
|
|
259
|
+
resource: JSON.stringify(resource),
|
|
260
|
+
summary,
|
|
261
|
+
vid,
|
|
262
|
+
lastUpdated
|
|
263
|
+
}).go();
|
|
264
|
+
return {
|
|
265
|
+
id,
|
|
266
|
+
resource,
|
|
267
|
+
meta: { lastUpdated, versionId: vid }
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/data/operations/control/user/user-delete-operation.ts
|
|
272
|
+
async function deleteUserOperation(params) {
|
|
273
|
+
const { id, tableName } = params;
|
|
274
|
+
const service = getDynamoControlService(tableName);
|
|
275
|
+
await service.entities.user.delete({ id, sk: "CURRENT" }).go();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export {
|
|
279
|
+
createUserOperation,
|
|
280
|
+
deleteUserOperation,
|
|
281
|
+
getUserByIdOperation,
|
|
282
|
+
listUsersOperation,
|
|
283
|
+
updateUserOperation,
|
|
284
|
+
findUserBySubOperation,
|
|
285
|
+
parseUserResource,
|
|
286
|
+
idFromReference,
|
|
287
|
+
switchUserTenantWorkspaceOperation
|
|
288
|
+
};
|
|
289
|
+
//# sourceMappingURL=chunk-2TPJ6HOF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/user/user-find-by-sub-operation.ts","../src/data/operations/control/user/user-resource-helpers.ts","../src/data/operations/fhir-reference.ts","../src/data/operations/control/user/user-create-operation.ts","../src/data/operations/control/user/user-get-by-id-operation.ts","../src/data/operations/control/user/user-list-operation.ts","../src/data/operations/control/user/user-switch-tenant-workspace-operation.ts","../src/data/operations/control/membership/membership-find-for-user-in-tenant-operation.ts","../src/data/operations/control/user/user-update-operation.ts","../src/data/operations/control/user/user-delete-operation.ts"],"sourcesContent":["import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface FindUserBySubParams {\n context: OpenHiContext;\n cognitoSub: string;\n tableName?: string;\n}\n\nexport interface FindUserBySubResult {\n id: string;\n cognitoSub?: string;\n resource: string;\n vid: string;\n}\n\n/**\n * Look up a User by Cognito sub via GSI2, projecting the row to a stable\n * result shape. Returns `undefined` when no row matches.\n */\nexport async function findUserBySubOperation(\n params: FindUserBySubParams,\n): Promise<FindUserBySubResult | undefined> {\n const { cognitoSub, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const result = await service.entities.user.query\n .gsi2({ cognitoSub })\n .go({ limit: 1 });\n const item = result.data?.[0];\n if (!item) {\n return undefined;\n }\n return {\n id: item.id,\n cognitoSub: item.cognitoSub,\n resource: item.resource,\n vid: item.vid,\n };\n}\n","import type { User } from \"@openhi/types\";\n\n/**\n * Helpers for working with persisted OpenHI User resources. Co-located with\n * the User operations because both the Cognito triggers and the onboarding\n * workflow consume these alongside `findUserBySubOperation`.\n */\n\n// Defensive parse — JSON.parse may yield any shape, so every field is optional.\nexport type UserResource = Partial<User>;\n\n/**\n * Existing User resources are stored as JSON strings in the data store; parse\n * defensively so a malformed payload returns `undefined` rather than throwing.\n */\nexport function parseUserResource(resource: string): UserResource | undefined {\n try {\n return JSON.parse(resource) as UserResource;\n } catch {\n return undefined;\n }\n}\n","/**\n * Pure helpers for working with FHIR Reference fields. Shared across data-plane\n * and control-plane operations and the handlers that wrap them.\n */\n\n/**\n * Extract the id portion from a FHIR-style reference such as `Patient/<id>` or\n * `Tenant/<id>`. Returns `undefined` if the reference is missing, does not\n * match the prefix, or has an empty id after the prefix.\n */\nexport function idFromReference(\n reference: string | undefined,\n prefix: string,\n): string | undefined {\n if (!reference || !reference.startsWith(prefix)) {\n return undefined;\n }\n const id = reference.slice(prefix.length);\n return id.length > 0 ? id : undefined;\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface UserCreateParams {\n context: OpenHiContext;\n body: { id?: string; resource?: Record<string, unknown> | string };\n tableName?: string;\n}\n\nexport interface UserCreateResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n meta: { lastUpdated: string; versionId: string };\n}\n\nexport async function createUserOperation(\n params: UserCreateParams,\n): Promise<UserCreateResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `user-${Date.now()}`;\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const lastUpdated = context.date ?? new Date().toISOString();\n const vid = `1`;\n\n const resource = { resourceType: \"User\", id, ...parsedResource };\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.user\n .put({\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { NotFoundError } from \"../../../errors\";\nimport { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface UserGetByIdParams {\n context: OpenHiContext;\n id: string;\n tableName?: string;\n}\n\nexport interface UserGetByIdResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n}\n\nexport async function getUserByIdOperation(\n params: UserGetByIdParams,\n): Promise<UserGetByIdResult> {\n const { id, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const response = await service.entities.user.get({ id, sk: \"CURRENT\" }).go();\n\n const item = response.data;\n if (!item) {\n throw new NotFoundError(`User not found: ${id}`);\n }\n\n const parsedResource = JSON.parse(item.resource) as Record<string, unknown>;\n\n return {\n id,\n resource: { resourceType: \"User\", id, ...parsedResource },\n };\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { SHARD_COUNT } from \"../../../dynamo/shard\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport {\n batchGetWithRetry,\n dispatchListMode,\n type ListOperationMode,\n} from \"../../data-operations-common\";\n\nconst SK = \"CURRENT\";\n\nexport interface UserListParams {\n context: OpenHiContext;\n tableName?: string;\n /** #853: defaults to `\"full\"`. `\"summary\"` skips BatchGet, `\"count\"` returns total only. */\n mode?: ListOperationMode;\n}\n\nexport interface UserListResult {\n entries: Array<{\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n }>;\n total: number;\n}\n\n/**\n * Lists Users via GSI1 (sharded). `mode` (default `\"full\"`) selects between BatchGet hydration,\n * summary-only (parse `summary` JSON projected on GSI1), or count-only (skip both). See\n * `dispatchListMode` in data-operations-common for the canonical mode contract.\n */\nexport async function listUsersOperation(\n params: UserListParams,\n): Promise<UserListResult> {\n const { tableName, mode = \"full\" } = params;\n const service = getDynamoControlService(tableName);\n\n const shardResults = await Promise.all(\n Array.from({ length: SHARD_COUNT }, (_, shard) =>\n service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go(),\n ),\n );\n\n return dispatchListMode<\n { id: string; resource: string },\n UserListResult[\"entries\"][number]\n >(mode, shardResults, {\n hydrate: (orderedIds) =>\n batchGetWithRetry(\n service.entities.user,\n orderedIds.map((id) => ({ id, sk: SK })),\n ) as Promise<Array<{ id: string; resource: string }>>,\n getId: (item) => item.id,\n buildEntry: (id, item) => ({\n id,\n resource: {\n resourceType: \"User\",\n id,\n ...(JSON.parse(item.resource) as Record<string, unknown>),\n },\n }),\n buildSummaryEntry: (id, parsed) => ({\n id,\n resource: { resourceType: \"User\", id, ...parsed },\n }),\n });\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { findUserBySubOperation } from \"./user-find-by-sub-operation\";\nimport { parseUserResource } from \"./user-resource-helpers\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport {\n ForbiddenError,\n NotFoundError,\n ValidationError,\n} from \"../../../errors\";\nimport { idFromReference } from \"../../fhir-reference\";\nimport {\n findMembershipsForUserInTenantOperation,\n pickTenantMembership,\n pickWorkspaceMembership,\n} from \"../membership/membership-find-for-user-in-tenant-operation\";\n\nconst SK = \"CURRENT\";\n\nexport interface UserSwitchTenantWorkspaceParams {\n cognitoSub: string;\n tenantReference: string;\n workspaceReference: string;\n tableName?: string;\n /** Override the clock — used by tests for deterministic `lastUpdated`. */\n now?: () => Date;\n}\n\nexport interface UserSwitchTenantWorkspaceResult {\n id: string;\n resource: Record<string, unknown>;\n meta: { lastUpdated: string; versionId: string };\n}\n\n/**\n * Update `currentTenant` and `currentWorkspace` on the User resource for the\n * caller authenticated by the given Cognito `sub`. All other fields on the\n * User are preserved.\n *\n * Throws:\n * - `ValidationError` when either reference is missing or malformed\n * - `NotFoundError` when no User matches the Cognito subject\n * - `ForbiddenError` when the caller lacks a tenant-level OR workspace-level\n * Membership in the requested tenant / workspace\n *\n * @see https://github.com/codedrifters/openhi/issues/769\n */\nexport async function switchUserTenantWorkspaceOperation(\n params: UserSwitchTenantWorkspaceParams,\n): Promise<UserSwitchTenantWorkspaceResult> {\n const { cognitoSub, tenantReference, workspaceReference, tableName } = params;\n\n const tenantId = idFromReference(tenantReference, \"Tenant/\");\n if (!tenantId) {\n throw new ValidationError(\n \"tenant.reference must be a 'Tenant/<id>' reference.\",\n );\n }\n const workspaceId = idFromReference(workspaceReference, \"Workspace/\");\n if (!workspaceId) {\n throw new ValidationError(\n \"workspace.reference must be a 'Workspace/<id>' reference.\",\n );\n }\n\n const user = await findUserBySubOperation({\n // findUserBySubOperation does not read context fields; pass a stub.\n context: {\n tenantId: \"\",\n workspaceId: \"\",\n date: \"\",\n actorId: \"\",\n actorName: \"\",\n actorType: \"internal-system\",\n },\n cognitoSub,\n tableName,\n });\n if (!user) {\n throw new NotFoundError(\n \"User not yet provisioned for the authenticated Cognito subject.\",\n );\n }\n\n const { memberships } = await findMembershipsForUserInTenantOperation({\n tenantId,\n userId: user.id,\n tableName,\n });\n if (!pickTenantMembership(memberships)) {\n throw new ForbiddenError(`User is not a member of Tenant/${tenantId}.`);\n }\n if (!pickWorkspaceMembership(memberships, workspaceId)) {\n throw new ForbiddenError(\n `User is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`,\n );\n }\n\n const existingResource = parseUserResource(user.resource) ?? {};\n const updatedResource: Record<string, unknown> = {\n ...existingResource,\n resourceType: \"User\",\n id: user.id,\n currentTenant: { reference: `Tenant/${tenantId}` },\n currentWorkspace: { reference: `Workspace/${workspaceId}` },\n };\n\n const lastUpdated = (params.now ? params.now() : new Date()).toISOString();\n const vid = `${Date.now()}`;\n const summary = JSON.stringify(\n extractSummary(updatedResource as FhirResourceLike),\n );\n\n const service = getDynamoControlService(tableName);\n await service.entities.user\n .patch({ id: user.id, sk: SK })\n .set({\n resource: JSON.stringify(updatedResource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return {\n id: user.id,\n resource: updatedResource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { SHARD_COUNT } from \"../../../dynamo/shard\";\n\nconst SK = \"CURRENT\";\n\nexport interface MembershipFindForUserInTenantParams {\n tenantId: string;\n userId: string;\n tableName?: string;\n}\n\nexport interface MembershipMatch {\n id: string;\n resource: Record<string, unknown>;\n}\n\nexport interface MembershipFindForUserInTenantResult {\n memberships: Array<MembershipMatch>;\n}\n\n/**\n * Return every Membership in `tenantId` whose `resource.user.reference`\n * equals `User/<userId>` — including both the tenant-level membership\n * (no `workspace` reference) and any workspace-level memberships.\n *\n * Implementation: fan-out GSI1 query across all four shards for the\n * tenant, parse the resource JSON, and filter by `user.reference`.\n *\n * Interim implementation — to be replaced by the adjacency-list\n * `USER#ID#<userId>` projection in #977 (ADR-018) once it lands. The\n * scan cost is bounded by the size of the target tenant; that's fine\n * for `POST /User/$switch` membership validation in v1 and avoids\n * pulling forward a partially-landed GSI.\n */\nexport async function findMembershipsForUserInTenantOperation(\n params: MembershipFindForUserInTenantParams,\n): Promise<MembershipFindForUserInTenantResult> {\n const { tenantId, userId, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const shardResults = await Promise.all(\n Array.from({ length: SHARD_COUNT }, (_, shard) =>\n service.entities.membership.query\n .gsi1({ tenantId, gsi1Shard: String(shard) })\n .go(),\n ),\n );\n\n const expectedUserRef = `User/${userId}`;\n const memberships: Array<MembershipMatch> = [];\n\n for (const shard of shardResults) {\n for (const item of shard.data ?? []) {\n if (item.sk !== SK) {\n continue;\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(item.resource) as Record<string, unknown>;\n } catch {\n continue;\n }\n const userRef = (parsed.user as { reference?: unknown } | undefined)\n ?.reference;\n if (userRef !== expectedUserRef) {\n continue;\n }\n memberships.push({ id: item.id, resource: parsed });\n }\n }\n\n return { memberships };\n}\n\n/**\n * Tenant-level Membership: caller is a member of the tenant but not\n * scoped to a specific Workspace (no `workspace.reference`).\n */\nexport function pickTenantMembership(\n memberships: ReadonlyArray<MembershipMatch>,\n): MembershipMatch | undefined {\n return memberships.find(\n (m) =>\n (m.resource.workspace as { reference?: unknown } | undefined) == null,\n );\n}\n\n/**\n * Workspace-level Membership for the given workspace id, if the caller\n * is a member.\n */\nexport function pickWorkspaceMembership(\n memberships: ReadonlyArray<MembershipMatch>,\n workspaceId: string,\n): MembershipMatch | undefined {\n const expectedWorkspaceRef = `Workspace/${workspaceId}`;\n return memberships.find(\n (m) =>\n (m.resource.workspace as { reference?: unknown } | undefined)\n ?.reference === expectedWorkspaceRef,\n );\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { NotFoundError } from \"../../../errors\";\nimport { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface UserUpdateParams {\n context: OpenHiContext;\n id: string;\n body: { resource?: Record<string, unknown> | string };\n tableName?: string;\n}\n\nexport interface UserUpdateResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n meta: { lastUpdated: string; versionId: string };\n}\n\nexport async function updateUserOperation(\n params: UserUpdateParams,\n): Promise<UserUpdateResult> {\n const { context, id, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const existing = await service.entities.user.get({ id, sk: \"CURRENT\" }).go();\n if (!existing.data) {\n throw new NotFoundError(`User not found: ${id}`);\n }\n\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const lastUpdated = context.date ?? new Date().toISOString();\n const vid = `${Date.now()}`;\n\n const resource = { resourceType: \"User\", id, ...parsedResource };\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.user\n .put({\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface UserDeleteParams {\n context: OpenHiContext;\n id: string;\n tableName?: string;\n}\n\nexport async function deleteUserOperation(\n params: UserDeleteParams,\n): Promise<void> {\n const { id, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n await service.entities.user.delete({ id, sk: \"CURRENT\" }).go();\n}\n"],"mappings":"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","names":["extractSummary","SK","SK","extractSummary","extractSummary","extractSummary"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NotFoundError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IS4VQRI4.mjs";
|
|
4
4
|
import {
|
|
5
5
|
getDynamoControlService
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QR5JVSCF.mjs";
|
|
7
7
|
|
|
8
8
|
// src/data/operations/control/role/role-get-by-id-operation.ts
|
|
9
9
|
async function getRoleByIdOperation(params) {
|
|
@@ -24,4 +24,4 @@ async function getRoleByIdOperation(params) {
|
|
|
24
24
|
export {
|
|
25
25
|
getRoleByIdOperation
|
|
26
26
|
};
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
27
|
+
//# sourceMappingURL=chunk-7FUAMZOF.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getRoleByIdOperation
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7FUAMZOF.mjs";
|
|
4
4
|
import {
|
|
5
5
|
require_lib
|
|
6
6
|
} from "./chunk-SYBADQXI.mjs";
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
createRoleAssignmentOperation,
|
|
10
10
|
createTenantOperation,
|
|
11
11
|
createWorkspaceOperation
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-MULKGFIJ.mjs";
|
|
13
13
|
import {
|
|
14
14
|
NotFoundError
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IS4VQRI4.mjs";
|
|
16
16
|
import {
|
|
17
17
|
getDynamoControlService
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-QR5JVSCF.mjs";
|
|
19
19
|
import {
|
|
20
20
|
__toESM
|
|
21
21
|
} from "./chunk-LZOMFHX3.mjs";
|
|
@@ -526,4 +526,4 @@ export {
|
|
|
526
526
|
productionCognitoProvisioner,
|
|
527
527
|
handler
|
|
528
528
|
};
|
|
529
|
-
//# sourceMappingURL=chunk-
|
|
529
|
+
//# sourceMappingURL=chunk-7Q2IJ2J5.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDynamoControlService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QR5JVSCF.mjs";
|
|
4
4
|
|
|
5
5
|
// src/data/operations/control/role/role-create-operation.ts
|
|
6
6
|
import { extractSummary } from "@openhi/types";
|
|
@@ -30,4 +30,4 @@ async function createRoleOperation(params) {
|
|
|
30
30
|
export {
|
|
31
31
|
createRoleOperation
|
|
32
32
|
};
|
|
33
|
-
//# sourceMappingURL=chunk-
|
|
33
|
+
//# sourceMappingURL=chunk-AJ3G3THO.mjs.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
batchGetWithRetry,
|
|
3
|
+
dispatchListMode,
|
|
4
|
+
getDynamoDataService,
|
|
5
|
+
listDataEntitiesByWorkspace
|
|
6
|
+
} from "./chunk-IS4VQRI4.mjs";
|
|
7
|
+
import {
|
|
8
|
+
SHARD_COUNT,
|
|
9
|
+
getDynamoControlService
|
|
10
|
+
} from "./chunk-QR5JVSCF.mjs";
|
|
11
|
+
|
|
12
|
+
// src/data/operations/data/practitionerrole/practitionerrole-list-operation.ts
|
|
13
|
+
async function listPractitionerRolesOperation(params) {
|
|
14
|
+
const { context, tableName, mode } = params;
|
|
15
|
+
const { tenantId, workspaceId } = context;
|
|
16
|
+
const service = getDynamoDataService(tableName);
|
|
17
|
+
return listDataEntitiesByWorkspace(
|
|
18
|
+
service.entities.practitionerrole,
|
|
19
|
+
tenantId,
|
|
20
|
+
workspaceId,
|
|
21
|
+
mode
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/data/operations/control/membership/membership-list-operation.ts
|
|
26
|
+
var SK = "CURRENT";
|
|
27
|
+
async function listMembershipsOperation(params) {
|
|
28
|
+
const { context, tableName, mode = "full" } = params;
|
|
29
|
+
const tenantId = context.tenantId;
|
|
30
|
+
const service = getDynamoControlService(tableName);
|
|
31
|
+
const shardResults = await Promise.all(
|
|
32
|
+
Array.from(
|
|
33
|
+
{ length: SHARD_COUNT },
|
|
34
|
+
(_, shard) => service.entities.membership.query.gsi1({ tenantId, gsi1Shard: String(shard) }).go()
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
return dispatchListMode(mode, shardResults, {
|
|
38
|
+
hydrate: (orderedIds) => batchGetWithRetry(
|
|
39
|
+
service.entities.membership,
|
|
40
|
+
orderedIds.map((id) => ({ tenantId, id, sk: SK }))
|
|
41
|
+
),
|
|
42
|
+
getId: (item) => item.id,
|
|
43
|
+
buildEntry: (id, item) => ({
|
|
44
|
+
id,
|
|
45
|
+
resource: {
|
|
46
|
+
resourceType: "Membership",
|
|
47
|
+
id,
|
|
48
|
+
...JSON.parse(item.resource)
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
buildSummaryEntry: (id, parsed) => ({
|
|
52
|
+
id,
|
|
53
|
+
resource: { resourceType: "Membership", id, ...parsed }
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/data/operations/control/roleassignment/roleassignment-list-operation.ts
|
|
59
|
+
var SK2 = "CURRENT";
|
|
60
|
+
async function listRoleAssignmentsOperation(params) {
|
|
61
|
+
const { context, tableName, mode = "full" } = params;
|
|
62
|
+
const tenantId = context.tenantId;
|
|
63
|
+
const service = getDynamoControlService(tableName);
|
|
64
|
+
const shardResults = await Promise.all(
|
|
65
|
+
Array.from(
|
|
66
|
+
{ length: SHARD_COUNT },
|
|
67
|
+
(_, shard) => service.entities.roleAssignment.query.gsi1({ tenantId, gsi1Shard: String(shard) }).go()
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
return dispatchListMode(mode, shardResults, {
|
|
71
|
+
hydrate: (orderedIds) => batchGetWithRetry(
|
|
72
|
+
service.entities.roleAssignment,
|
|
73
|
+
orderedIds.map((id) => ({ tenantId, id, sk: SK2 }))
|
|
74
|
+
),
|
|
75
|
+
getId: (item) => item.id,
|
|
76
|
+
buildEntry: (id, item) => ({
|
|
77
|
+
id,
|
|
78
|
+
resource: {
|
|
79
|
+
resourceType: "RoleAssignment",
|
|
80
|
+
id,
|
|
81
|
+
...JSON.parse(item.resource)
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
buildSummaryEntry: (id, parsed) => ({
|
|
85
|
+
id,
|
|
86
|
+
resource: { resourceType: "RoleAssignment", id, ...parsed }
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
listPractitionerRolesOperation,
|
|
93
|
+
listMembershipsOperation,
|
|
94
|
+
listRoleAssignmentsOperation
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=chunk-BB5MK4L3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/data/practitionerrole/practitionerrole-list-operation.ts","../src/data/operations/control/membership/membership-list-operation.ts","../src/data/operations/control/roleassignment/roleassignment-list-operation.ts"],"sourcesContent":["import type { PractitionerRole } from \"@openhi/types\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-data-service\";\nimport {\n type ListParams,\n listDataEntitiesByWorkspace,\n type ListResult,\n type ListEntry,\n} from \"../../data-operations-common\";\n\n/**\n * List PractitionerRoles in a workspace (GSI1, sharded). Returns domain result for adapters to map to FHIR Bundle or other formats.\n *\n * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md\n */\nexport type ListPractitionerRolesParams = ListParams;\n\nexport type PractitionerRoleListEntry = ListEntry<PractitionerRole>;\n\nexport type ListPractitionerRolesResult = ListResult<PractitionerRole>;\n\n/**\n * Lists all PractitionerRoles in the workspace. Uses GSI1 (Unified Sharded List per ADR-011).\n * Throws on service errors; adapters map to HTTP/GraphQL/Step Function.\n */\nexport async function listPractitionerRolesOperation(\n params: ListPractitionerRolesParams,\n): Promise<ListPractitionerRolesResult> {\n const { context, tableName, mode } = params;\n const { tenantId, workspaceId } = context;\n const service = getDynamoDataService(tableName);\n return listDataEntitiesByWorkspace<PractitionerRole>(\n service.entities.practitionerrole as Parameters<\n typeof listDataEntitiesByWorkspace\n >[0],\n tenantId,\n workspaceId,\n mode,\n );\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { SHARD_COUNT } from \"../../../dynamo/shard\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport {\n batchGetWithRetry,\n dispatchListMode,\n type ListOperationMode,\n} from \"../../data-operations-common\";\n\nconst SK = \"CURRENT\";\n\nexport interface MembershipListParams {\n context: OpenHiContext;\n tableName?: string;\n /** #853: defaults to `\"full\"`. `\"summary\"` skips BatchGet, `\"count\"` returns total only. */\n mode?: ListOperationMode;\n}\n\nexport interface MembershipListResult {\n entries: Array<{\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n }>;\n total: number;\n}\n\n/**\n * Lists Memberships for the context tenant via GSI1 (sharded). See `dispatchListMode` for\n * the mode contract (#853).\n */\nexport async function listMembershipsOperation(\n params: MembershipListParams,\n): Promise<MembershipListResult> {\n const { context, tableName, mode = \"full\" } = params;\n const tenantId = context.tenantId;\n const service = getDynamoControlService(tableName);\n\n const shardResults = await Promise.all(\n Array.from({ length: SHARD_COUNT }, (_, shard) =>\n service.entities.membership.query\n .gsi1({ tenantId, gsi1Shard: String(shard) })\n .go(),\n ),\n );\n\n return dispatchListMode<\n { id: string; resource: string },\n MembershipListResult[\"entries\"][number]\n >(mode, shardResults, {\n hydrate: (orderedIds) =>\n batchGetWithRetry(\n service.entities.membership,\n orderedIds.map((id) => ({ tenantId, id, sk: SK })),\n ) as Promise<Array<{ id: string; resource: string }>>,\n getId: (item) => item.id,\n buildEntry: (id, item) => ({\n id,\n resource: {\n resourceType: \"Membership\",\n id,\n ...(JSON.parse(item.resource) as Record<string, unknown>),\n },\n }),\n buildSummaryEntry: (id, parsed) => ({\n id,\n resource: { resourceType: \"Membership\", id, ...parsed },\n }),\n });\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { SHARD_COUNT } from \"../../../dynamo/shard\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport {\n batchGetWithRetry,\n dispatchListMode,\n type ListOperationMode,\n} from \"../../data-operations-common\";\n\nconst SK = \"CURRENT\";\n\nexport interface RoleAssignmentListParams {\n context: OpenHiContext;\n tableName?: string;\n /** #853: defaults to `\"full\"`. `\"summary\"` skips BatchGet, `\"count\"` returns total only. */\n mode?: ListOperationMode;\n}\n\nexport interface RoleAssignmentListResult {\n entries: Array<{\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n }>;\n total: number;\n}\n\n/**\n * Lists RoleAssignments for the context tenant via GSI1 (sharded). See `dispatchListMode` for\n * the mode contract (#853).\n */\nexport async function listRoleAssignmentsOperation(\n params: RoleAssignmentListParams,\n): Promise<RoleAssignmentListResult> {\n const { context, tableName, mode = \"full\" } = params;\n const tenantId = context.tenantId;\n const service = getDynamoControlService(tableName);\n\n const shardResults = await Promise.all(\n Array.from({ length: SHARD_COUNT }, (_, shard) =>\n service.entities.roleAssignment.query\n .gsi1({ tenantId, gsi1Shard: String(shard) })\n .go(),\n ),\n );\n\n return dispatchListMode<\n { id: string; resource: string },\n RoleAssignmentListResult[\"entries\"][number]\n >(mode, shardResults, {\n hydrate: (orderedIds) =>\n batchGetWithRetry(\n service.entities.roleAssignment,\n orderedIds.map((id) => ({ tenantId, id, sk: SK })),\n ) as Promise<Array<{ id: string; resource: string }>>,\n getId: (item) => item.id,\n buildEntry: (id, item) => ({\n id,\n resource: {\n resourceType: \"RoleAssignment\",\n id,\n ...(JSON.parse(item.resource) as Record<string, unknown>),\n },\n }),\n buildSummaryEntry: (id, parsed) => ({\n id,\n resource: { resourceType: \"RoleAssignment\", id, ...parsed },\n }),\n });\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,eAAsB,+BACpB,QACsC;AACtC,QAAM,EAAE,SAAS,WAAW,KAAK,IAAI;AACrC,QAAM,EAAE,UAAU,YAAY,IAAI;AAClC,QAAM,UAAU,qBAAqB,SAAS;AAC9C,SAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IAGjB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC7BA,IAAM,KAAK;AAqBX,eAAsB,yBACpB,QAC+B;AAC/B,QAAM,EAAE,SAAS,WAAW,OAAO,OAAO,IAAI;AAC9C,QAAM,WAAW,QAAQ;AACzB,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,eAAe,MAAM,QAAQ;AAAA,IACjC,MAAM;AAAA,MAAK,EAAE,QAAQ,YAAY;AAAA,MAAG,CAAC,GAAG,UACtC,QAAQ,SAAS,WAAW,MACzB,KAAK,EAAE,UAAU,WAAW,OAAO,KAAK,EAAE,CAAC,EAC3C,GAAG;AAAA,IACR;AAAA,EACF;AAEA,SAAO,iBAGL,MAAM,cAAc;AAAA,IACpB,SAAS,CAAC,eACR;AAAA,MACE,QAAQ,SAAS;AAAA,MACjB,WAAW,IAAI,CAAC,QAAQ,EAAE,UAAU,IAAI,IAAI,GAAG,EAAE;AAAA,IACnD;AAAA,IACF,OAAO,CAAC,SAAS,KAAK;AAAA,IACtB,YAAY,CAAC,IAAI,UAAU;AAAA,MACzB;AAAA,MACA,UAAU;AAAA,QACR,cAAc;AAAA,QACd;AAAA,QACA,GAAI,KAAK,MAAM,KAAK,QAAQ;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,mBAAmB,CAAC,IAAI,YAAY;AAAA,MAClC;AAAA,MACA,UAAU,EAAE,cAAc,cAAc,IAAI,GAAG,OAAO;AAAA,IACxD;AAAA,EACF,CAAC;AACH;;;AC3DA,IAAMA,MAAK;AAqBX,eAAsB,6BACpB,QACmC;AACnC,QAAM,EAAE,SAAS,WAAW,OAAO,OAAO,IAAI;AAC9C,QAAM,WAAW,QAAQ;AACzB,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,eAAe,MAAM,QAAQ;AAAA,IACjC,MAAM;AAAA,MAAK,EAAE,QAAQ,YAAY;AAAA,MAAG,CAAC,GAAG,UACtC,QAAQ,SAAS,eAAe,MAC7B,KAAK,EAAE,UAAU,WAAW,OAAO,KAAK,EAAE,CAAC,EAC3C,GAAG;AAAA,IACR;AAAA,EACF;AAEA,SAAO,iBAGL,MAAM,cAAc;AAAA,IACpB,SAAS,CAAC,eACR;AAAA,MACE,QAAQ,SAAS;AAAA,MACjB,WAAW,IAAI,CAAC,QAAQ,EAAE,UAAU,IAAI,IAAIA,IAAG,EAAE;AAAA,IACnD;AAAA,IACF,OAAO,CAAC,SAAS,KAAK;AAAA,IACtB,YAAY,CAAC,IAAI,UAAU;AAAA,MACzB;AAAA,MACA,UAAU;AAAA,QACR,cAAc;AAAA,QACd;AAAA,QACA,GAAI,KAAK,MAAM,KAAK,QAAQ;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,mBAAmB,CAAC,IAAI,YAAY;AAAA,MAClC;AAAA,MACA,UAAU,EAAE,cAAc,kBAAkB,IAAI,GAAG,OAAO;AAAA,IAC5D;AAAA,EACF,CAAC;AACH;","names":["SK"]}
|