@openhi/constructs 0.0.177 → 0.0.179
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-Z4PZSLYY.mjs → chunk-3M4QTQH6.mjs} +2 -2
- package/lib/{chunk-JUSVETWK.mjs → chunk-4LQR32D2.mjs} +38 -40
- package/lib/{chunk-JUSVETWK.mjs.map → chunk-4LQR32D2.mjs.map} +1 -1
- package/lib/{chunk-XNUCKVSE.mjs → chunk-7GMTHOYF.mjs} +2 -2
- package/lib/{chunk-E2OWEBBH.mjs → chunk-DIVYB6GD.mjs} +18 -4
- package/lib/chunk-DIVYB6GD.mjs.map +1 -0
- package/lib/chunk-F2LY4TEI.mjs +272 -0
- package/lib/chunk-F2LY4TEI.mjs.map +1 -0
- package/lib/{chunk-GG2WD6TA.mjs → chunk-JJ3AQ6G5.mjs} +9 -3
- package/lib/{chunk-GG2WD6TA.mjs.map → chunk-JJ3AQ6G5.mjs.map} +1 -1
- package/lib/{chunk-EBB4RNUG.mjs → chunk-PIQISEGW.mjs} +2 -2
- package/lib/{chunk-FDBBTNCI.mjs → chunk-Q4KQD2NB.mjs} +117 -5
- package/lib/chunk-Q4KQD2NB.mjs.map +1 -0
- package/lib/{chunk-Y4RGUAM2.mjs → chunk-V6KLFEHC.mjs} +105 -34
- package/lib/chunk-V6KLFEHC.mjs.map +1 -0
- package/lib/chunk-VQY57NOV.mjs +60 -0
- package/lib/chunk-VQY57NOV.mjs.map +1 -0
- package/lib/counter-maintenance.handler.mjs +4 -4
- package/lib/counter-reconciliation.handler.js +2 -2
- package/lib/counter-reconciliation.handler.js.map +1 -1
- package/lib/counter-reconciliation.handler.mjs +9 -267
- package/lib/counter-reconciliation.handler.mjs.map +1 -1
- package/lib/index.d.mts +117 -2
- package/lib/index.d.ts +117 -2
- package/lib/index.js +6454 -6243
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +106 -4
- package/lib/index.mjs.map +1 -1
- package/lib/pre-token-generation.handler.js +28 -19
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +4 -5
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +22 -19
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +3 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rest-api-lambda.handler.js +400 -214
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +243 -171
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.d.mts +19 -0
- package/lib/seed-demo-data.handler.d.ts +19 -0
- package/lib/seed-demo-data.handler.js +805 -159
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +8 -4
- package/package.json +3 -3
- package/lib/chunk-6HGSR3TG.mjs +0 -123
- package/lib/chunk-6HGSR3TG.mjs.map +0 -1
- package/lib/chunk-E2OWEBBH.mjs.map +0 -1
- package/lib/chunk-FDBBTNCI.mjs.map +0 -1
- package/lib/chunk-Y4RGUAM2.mjs.map +0 -1
- /package/lib/{chunk-Z4PZSLYY.mjs.map → chunk-3M4QTQH6.mjs.map} +0 -0
- /package/lib/{chunk-XNUCKVSE.mjs.map → chunk-7GMTHOYF.mjs.map} +0 -0
- /package/lib/{chunk-EBB4RNUG.mjs.map → chunk-PIQISEGW.mjs.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
membershipListByUserOperation
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Q4KQD2NB.mjs";
|
|
4
4
|
import {
|
|
5
5
|
ForbiddenError,
|
|
6
6
|
NotFoundError,
|
|
@@ -10,6 +10,15 @@ import {
|
|
|
10
10
|
getDynamoControlService
|
|
11
11
|
} from "./chunk-EUIP2U5F.mjs";
|
|
12
12
|
|
|
13
|
+
// src/data/operations/fhir-reference.ts
|
|
14
|
+
function idFromReference(reference, prefix) {
|
|
15
|
+
if (!reference || !reference.startsWith(prefix)) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
const id = reference.slice(prefix.length);
|
|
19
|
+
return id.length > 0 ? id : void 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
// src/data/operations/control/user/user-find-by-sub-operation.ts
|
|
14
23
|
async function findUserBySubOperation(params) {
|
|
15
24
|
const { cognitoSub, tableName } = params;
|
|
@@ -43,17 +52,94 @@ function parseUserResource(resource) {
|
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
// src/data/operations/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
// src/data/operations/control/user/user-admin-set-context-operation.ts
|
|
56
|
+
import { extractSummary } from "@openhi/types";
|
|
57
|
+
|
|
58
|
+
// src/data/operations/control/user/user-get-by-id-operation.ts
|
|
59
|
+
async function getUserByIdOperation(params) {
|
|
60
|
+
const { id, tableName } = params;
|
|
61
|
+
const service = getDynamoControlService(tableName);
|
|
62
|
+
const response = await service.entities.user.get({ id, sk: "CURRENT" }).go();
|
|
63
|
+
const item = response.data;
|
|
64
|
+
if (!item) {
|
|
65
|
+
throw new NotFoundError(`User not found: ${id}`);
|
|
50
66
|
}
|
|
51
|
-
const
|
|
52
|
-
return
|
|
67
|
+
const parsedResource = JSON.parse(item.resource);
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
resource: { resourceType: "User", id, ...parsedResource }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/data/operations/control/user/user-admin-set-context-operation.ts
|
|
75
|
+
var SK = "CURRENT";
|
|
76
|
+
async function adminSetUserContextOperation(params) {
|
|
77
|
+
const {
|
|
78
|
+
context,
|
|
79
|
+
targetUserId,
|
|
80
|
+
tenantReference,
|
|
81
|
+
workspaceReference,
|
|
82
|
+
tableName
|
|
83
|
+
} = params;
|
|
84
|
+
const tenantId = idFromReference(tenantReference, "Tenant/");
|
|
85
|
+
if (!tenantId) {
|
|
86
|
+
throw new ValidationError(
|
|
87
|
+
"tenant.reference must be a 'Tenant/<id>' reference."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const workspaceId = idFromReference(workspaceReference, "Workspace/");
|
|
91
|
+
if (!workspaceId) {
|
|
92
|
+
throw new ValidationError(
|
|
93
|
+
"workspace.reference must be a 'Workspace/<id>' reference."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const target = await getUserByIdOperation({
|
|
97
|
+
context,
|
|
98
|
+
id: targetUserId,
|
|
99
|
+
tableName
|
|
100
|
+
});
|
|
101
|
+
const projection = await membershipListByUserOperation({
|
|
102
|
+
userId: targetUserId,
|
|
103
|
+
mode: "workspaceInTenant",
|
|
104
|
+
tenantId,
|
|
105
|
+
tableName
|
|
106
|
+
});
|
|
107
|
+
const hasMembership = projection.items.some(
|
|
108
|
+
(row) => row.workspaceId === workspaceId
|
|
109
|
+
);
|
|
110
|
+
if (!hasMembership) {
|
|
111
|
+
throw new ForbiddenError(
|
|
112
|
+
`User is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const updatedResource = {
|
|
116
|
+
...target.resource,
|
|
117
|
+
resourceType: "User",
|
|
118
|
+
id: targetUserId,
|
|
119
|
+
currentTenant: { reference: `Tenant/${tenantId}` },
|
|
120
|
+
currentWorkspace: { reference: `Workspace/${workspaceId}` }
|
|
121
|
+
};
|
|
122
|
+
const lastUpdated = (params.now ? params.now() : /* @__PURE__ */ new Date()).toISOString();
|
|
123
|
+
const vid = `${Date.now()}`;
|
|
124
|
+
const summary = JSON.stringify(
|
|
125
|
+
extractSummary(updatedResource)
|
|
126
|
+
);
|
|
127
|
+
const service = getDynamoControlService(tableName);
|
|
128
|
+
await service.entities.user.patch({ id: targetUserId, sk: SK }).set({
|
|
129
|
+
resource: JSON.stringify(updatedResource),
|
|
130
|
+
summary,
|
|
131
|
+
vid,
|
|
132
|
+
lastUpdated
|
|
133
|
+
}).go();
|
|
134
|
+
return {
|
|
135
|
+
id: targetUserId,
|
|
136
|
+
resource: updatedResource,
|
|
137
|
+
meta: { lastUpdated, versionId: vid }
|
|
138
|
+
};
|
|
53
139
|
}
|
|
54
140
|
|
|
55
141
|
// src/data/operations/control/user/user-create-operation.ts
|
|
56
|
-
import { extractSummary } from "@openhi/types";
|
|
142
|
+
import { extractSummary as extractSummary2 } from "@openhi/types";
|
|
57
143
|
import { ulid } from "ulid";
|
|
58
144
|
async function createUserOperation(params) {
|
|
59
145
|
const { context, body, tableName } = params;
|
|
@@ -63,7 +149,7 @@ async function createUserOperation(params) {
|
|
|
63
149
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
64
150
|
const vid = `1`;
|
|
65
151
|
const resource = { resourceType: "User", id, ...parsedResource };
|
|
66
|
-
const summary = JSON.stringify(
|
|
152
|
+
const summary = JSON.stringify(extractSummary2(resource));
|
|
67
153
|
await service.entities.user.put({
|
|
68
154
|
id,
|
|
69
155
|
resource: JSON.stringify(resource),
|
|
@@ -78,25 +164,9 @@ async function createUserOperation(params) {
|
|
|
78
164
|
};
|
|
79
165
|
}
|
|
80
166
|
|
|
81
|
-
// src/data/operations/control/user/user-get-by-id-operation.ts
|
|
82
|
-
async function getUserByIdOperation(params) {
|
|
83
|
-
const { id, tableName } = params;
|
|
84
|
-
const service = getDynamoControlService(tableName);
|
|
85
|
-
const response = await service.entities.user.get({ id, sk: "CURRENT" }).go();
|
|
86
|
-
const item = response.data;
|
|
87
|
-
if (!item) {
|
|
88
|
-
throw new NotFoundError(`User not found: ${id}`);
|
|
89
|
-
}
|
|
90
|
-
const parsedResource = JSON.parse(item.resource);
|
|
91
|
-
return {
|
|
92
|
-
id,
|
|
93
|
-
resource: { resourceType: "User", id, ...parsedResource }
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
167
|
// src/data/operations/control/user/user-switch-tenant-workspace-operation.ts
|
|
98
|
-
import { extractSummary as
|
|
99
|
-
var
|
|
168
|
+
import { extractSummary as extractSummary3 } from "@openhi/types";
|
|
169
|
+
var SK2 = "CURRENT";
|
|
100
170
|
async function switchUserTenantWorkspaceOperation(params) {
|
|
101
171
|
const { cognitoSub, tenantReference, workspaceReference, tableName } = params;
|
|
102
172
|
const tenantId = idFromReference(tenantReference, "Tenant/");
|
|
@@ -154,10 +224,10 @@ async function switchUserTenantWorkspaceOperation(params) {
|
|
|
154
224
|
const lastUpdated = (params.now ? params.now() : /* @__PURE__ */ new Date()).toISOString();
|
|
155
225
|
const vid = `${Date.now()}`;
|
|
156
226
|
const summary = JSON.stringify(
|
|
157
|
-
|
|
227
|
+
extractSummary3(updatedResource)
|
|
158
228
|
);
|
|
159
229
|
const service = getDynamoControlService(tableName);
|
|
160
|
-
await service.entities.user.patch({ id: user.id, sk:
|
|
230
|
+
await service.entities.user.patch({ id: user.id, sk: SK2 }).set({
|
|
161
231
|
resource: JSON.stringify(updatedResource),
|
|
162
232
|
summary,
|
|
163
233
|
vid,
|
|
@@ -171,7 +241,7 @@ async function switchUserTenantWorkspaceOperation(params) {
|
|
|
171
241
|
}
|
|
172
242
|
|
|
173
243
|
// src/data/operations/control/user/user-update-operation.ts
|
|
174
|
-
import { extractSummary as
|
|
244
|
+
import { extractSummary as extractSummary4 } from "@openhi/types";
|
|
175
245
|
async function updateUserOperation(params) {
|
|
176
246
|
const { context, id, body, tableName } = params;
|
|
177
247
|
const service = getDynamoControlService(tableName);
|
|
@@ -183,7 +253,7 @@ async function updateUserOperation(params) {
|
|
|
183
253
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
184
254
|
const vid = `${Date.now()}`;
|
|
185
255
|
const resource = { resourceType: "User", id, ...parsedResource };
|
|
186
|
-
const summary = JSON.stringify(
|
|
256
|
+
const summary = JSON.stringify(extractSummary4(resource));
|
|
187
257
|
await service.entities.user.put({
|
|
188
258
|
id,
|
|
189
259
|
resource: JSON.stringify(resource),
|
|
@@ -206,13 +276,14 @@ async function deleteUserOperation(params) {
|
|
|
206
276
|
}
|
|
207
277
|
|
|
208
278
|
export {
|
|
279
|
+
getUserByIdOperation,
|
|
280
|
+
idFromReference,
|
|
281
|
+
adminSetUserContextOperation,
|
|
209
282
|
createUserOperation,
|
|
210
283
|
deleteUserOperation,
|
|
211
|
-
getUserByIdOperation,
|
|
212
284
|
updateUserOperation,
|
|
213
285
|
findUserBySubOperation,
|
|
214
286
|
parseUserResource,
|
|
215
|
-
idFromReference,
|
|
216
287
|
switchUserTenantWorkspaceOperation
|
|
217
288
|
};
|
|
218
|
-
//# sourceMappingURL=chunk-
|
|
289
|
+
//# sourceMappingURL=chunk-V6KLFEHC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/fhir-reference.ts","../src/data/operations/control/user/user-find-by-sub-operation.ts","../src/data/operations/control/user/user-resource-helpers.ts","../src/data/operations/control/user/user-admin-set-context-operation.ts","../src/data/operations/control/user/user-get-by-id-operation.ts","../src/data/operations/control/user/user-create-operation.ts","../src/data/operations/control/user/user-switch-tenant-workspace-operation.ts","../src/data/operations/control/user/user-update-operation.ts","../src/data/operations/control/user/user-delete-operation.ts"],"sourcesContent":["/**\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 { 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 summary?: string;\n vid: string;\n lastUpdated?: string;\n}\n\n/**\n * Look up a User by Cognito sub via GSI2, then fetch the canonical row\n * from the base table so projection-skipped attributes (`resource`,\n * `summary`, `vid`, `lastUpdated`) are populated. Returns `undefined`\n * when no row matches the sub, or when the GSI2 hit points at a\n * canonical row that has since been hard-deleted.\n *\n * GSI2 projects only `id` (+ ElectroDB markers), so a single index\n * query is insufficient — see #1175.\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 indexResult = await service.entities.user.query\n .gsi2({ cognitoSub })\n .go({ limit: 1, attributes: [\"id\"] });\n const indexItem = indexResult.data?.[0];\n if (!indexItem) {\n return undefined;\n }\n\n const canonical = await service.entities.user\n .get({ id: indexItem.id, sk: \"CURRENT\" })\n .go();\n const row = canonical.data;\n if (!row) {\n return undefined;\n }\n\n return {\n id: row.id,\n cognitoSub: row.cognitoSub,\n resource: row.resource,\n summary: row.summary,\n vid: row.vid,\n lastUpdated: row.lastUpdated,\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","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getUserByIdOperation } from \"./user-get-by-id-operation\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { ForbiddenError, ValidationError } from \"../../../errors\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport { idFromReference } from \"../../fhir-reference\";\nimport { membershipListByUserOperation } from \"../membership/membership-list-by-user-operation\";\n\nconst SK = \"CURRENT\";\n\nexport interface UserAdminSetContextParams {\n /** Caller context (audit). Authorization is enforced at the edge today. */\n context: OpenHiContext;\n /** Id of the User whose context is being reassigned by the admin. */\n targetUserId: 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 UserAdminSetContextResult {\n id: string;\n resource: Record<string, unknown>;\n meta: { lastUpdated: string; versionId: string };\n}\n\n/**\n * Admin reassignment of ANOTHER user's `currentTenant` / `currentWorkspace`.\n *\n * Mirrors {@link switchUserTenantWorkspaceOperation} but resolves the user\n * from `targetUserId` (an explicit path id supplied by an admin) instead of\n * the caller's Cognito `sub`. The membership pre-condition is asserted against\n * the TARGET user, reusing the same ADR-018 workspace sub-lane projection\n * Query (no GSI fan-out, no scan). The target user's next Cognito JWT reflects\n * the new context once they refresh their token.\n *\n * Authorization is the responsibility of the edge today — control routes do\n * not yet enforce per-role checks (see `routes/control/README.md` § \"JWT does\n * not yet carry role claims\", epic #932). This operation is callable by an\n * authenticated admin and gains role gating when RBAC lands.\n *\n * Throws:\n * - `ValidationError` when either reference is missing or malformed\n * - `NotFoundError` (from {@link getUserByIdOperation}) when no User matches\n * `targetUserId`\n * - `ForbiddenError` when the TARGET user has no Membership in the requested\n * `(tenantId, workspaceId)` pair on their user-partition projection\n *\n * @see https://github.com/codedrifters/openhi/issues/1348\n * @see ADR-018 § Access Pattern Coverage (pattern #4)\n */\nexport async function adminSetUserContextOperation(\n params: UserAdminSetContextParams,\n): Promise<UserAdminSetContextResult> {\n const {\n context,\n targetUserId,\n tenantReference,\n workspaceReference,\n tableName,\n } = 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 // Loads the TARGET user; throws NotFoundError when absent.\n const target = await getUserByIdOperation({\n context,\n id: targetUserId,\n tableName,\n });\n\n // ADR-018: single Query on the TARGET user's partition, narrowed to the\n // workspace sub-lane of the requested tenant. A row whose `workspaceId`\n // matches proves the target user may be assigned to that pair.\n const projection = await membershipListByUserOperation({\n userId: targetUserId,\n mode: \"workspaceInTenant\",\n tenantId,\n tableName,\n });\n const hasMembership = projection.items.some(\n (row) => row.workspaceId === workspaceId,\n );\n if (!hasMembership) {\n throw new ForbiddenError(\n `User is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`,\n );\n }\n\n const updatedResource: Record<string, unknown> = {\n ...target.resource,\n resourceType: \"User\",\n id: targetUserId,\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: targetUserId, sk: SK })\n .set({\n resource: JSON.stringify(updatedResource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return {\n id: targetUserId,\n resource: updatedResource,\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 { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { ulid } from \"ulid\";\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 ?? ulid();\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 { 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 { membershipListByUserOperation } from \"../membership/membership-list-by-user-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 * Membership pre-condition uses the ADR-018 adjacency-list user projection\n * (pattern #4, workspace sub-lane). A single base-table Query on\n * `PK = USER#ID#<userId>` with\n * `SK begins_with 'MEMBERSHIP#WORKSPACE#TID#<tenantId>#'`\n * (via {@link membershipListByUserOperation} with `mode: \"workspaceInTenant\"`)\n * confirms the caller has a workspace-level Membership in the requested\n * tenant + workspace pair. No GSI1 fan-out, no scan.\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 has no Membership in the requested\n * `(tenantId, workspaceId)` pair on their user-partition projection\n *\n * @see https://github.com/codedrifters/openhi/issues/769\n * @see https://github.com/codedrifters/openhi/issues/1020\n * @see ADR-018 § Access Pattern Coverage (pattern #4)\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 // ADR-018: single Query on the user partition, narrowed to the workspace\n // sub-lane of the requested tenant. The lane includes `workspaceId` on\n // every row; a row with `workspaceId === <requested>` is sufficient and\n // necessary proof that the caller may switch to that pair.\n const projection = await membershipListByUserOperation({\n userId: user.id,\n mode: \"workspaceInTenant\",\n tenantId,\n tableName,\n });\n const hasMembership = projection.items.some(\n (row) => row.workspaceId === workspaceId,\n );\n if (!hasMembership) {\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 { 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":";;;;;;;;;;;;;AAUO,SAAS,gBACd,WACA,QACoB;AACpB,MAAI,CAAC,aAAa,CAAC,UAAU,WAAW,MAAM,GAAG;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,KAAK,UAAU,MAAM,OAAO,MAAM;AACxC,SAAO,GAAG,SAAS,IAAI,KAAK;AAC9B;;;ACSA,eAAsB,uBACpB,QAC0C;AAC1C,QAAM,EAAE,YAAY,UAAU,IAAI;AAClC,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,cAAc,MAAM,QAAQ,SAAS,KAAK,MAC7C,KAAK,EAAE,WAAW,CAAC,EACnB,GAAG,EAAE,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC;AACtC,QAAM,YAAY,YAAY,OAAO,CAAC;AACtC,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,QAAQ,SAAS,KACtC,IAAI,EAAE,IAAI,UAAU,IAAI,IAAI,UAAU,CAAC,EACvC,GAAG;AACN,QAAM,MAAM,UAAU;AACtB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,YAAY,IAAI;AAAA,IAChB,UAAU,IAAI;AAAA,IACd,SAAS,IAAI;AAAA,IACb,KAAK,IAAI;AAAA,IACT,aAAa,IAAI;AAAA,EACnB;AACF;;;AC3CO,SAAS,kBAAkB,UAA4C;AAC5E,MAAI;AACF,WAAO,KAAK,MAAM,QAAQ;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACrBA,SAAS,sBAA6C;;;ACetD,eAAsB,qBACpB,QAC4B;AAC5B,QAAM,EAAE,IAAI,UAAU,IAAI;AAC1B,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,WAAW,MAAM,QAAQ,SAAS,KAAK,IAAI,EAAE,IAAI,IAAI,UAAU,CAAC,EAAE,GAAG;AAE3E,QAAM,OAAO,SAAS;AACtB,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,cAAc,mBAAmB,EAAE,EAAE;AAAA,EACjD;AAEA,QAAM,iBAAiB,KAAK,MAAM,KAAK,QAAQ;AAE/C,SAAO;AAAA,IACL;AAAA,IACA,UAAU,EAAE,cAAc,QAAQ,IAAI,GAAG,eAAe;AAAA,EAC1D;AACF;;;AD1BA,IAAM,KAAK;AA6CX,eAAsB,6BACpB,QACoC;AACpC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,WAAW,gBAAgB,iBAAiB,SAAS;AAC3D,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,cAAc,gBAAgB,oBAAoB,YAAY;AACpE,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,qBAAqB;AAAA,IACxC;AAAA,IACA,IAAI;AAAA,IACJ;AAAA,EACF,CAAC;AAKD,QAAM,aAAa,MAAM,8BAA8B;AAAA,IACrD,QAAQ;AAAA,IACR,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,gBAAgB,WAAW,MAAM;AAAA,IACrC,CAAC,QAAQ,IAAI,gBAAgB;AAAA,EAC/B;AACA,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI;AAAA,MACR,qCAAqC,WAAW,cAAc,QAAQ;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,kBAA2C;AAAA,IAC/C,GAAG,OAAO;AAAA,IACV,cAAc;AAAA,IACd,IAAI;AAAA,IACJ,eAAe,EAAE,WAAW,UAAU,QAAQ,GAAG;AAAA,IACjD,kBAAkB,EAAE,WAAW,aAAa,WAAW,GAAG;AAAA,EAC5D;AAEA,QAAM,eAAe,OAAO,MAAM,OAAO,IAAI,IAAI,oBAAI,KAAK,GAAG,YAAY;AACzE,QAAM,MAAM,GAAG,KAAK,IAAI,CAAC;AACzB,QAAM,UAAU,KAAK;AAAA,IACnB,eAAe,eAAmC;AAAA,EACpD;AAEA,QAAM,UAAU,wBAAwB,SAAS;AACjD,QAAM,QAAQ,SAAS,KACpB,MAAM,EAAE,IAAI,cAAc,IAAI,GAAG,CAAC,EAClC,IAAI;AAAA,IACH,UAAU,KAAK,UAAU,eAAe;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,EACtC;AACF;;;AEpIA,SAAS,kBAAAA,uBAA6C;AACtD,SAAS,YAAY;AAgBrB,eAAsB,oBACpB,QAC2B;AAC3B,QAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AACrC,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,KAAK,KAAK,MAAM,KAAK;AAC3B,QAAM,iBACJ,OAAO,KAAK,aAAa,WACpB,KAAK,MAAM,KAAK,QAAQ,IACxB,KAAK,YAAY,CAAC;AAEzB,QAAM,cAAc,QAAQ,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAC3D,QAAM,MAAM;AAEZ,QAAM,WAAW,EAAE,cAAc,QAAQ,IAAI,GAAG,eAAe;AAC/D,QAAM,UAAU,KAAK,UAAUC,gBAAe,QAA4B,CAAC;AAE3E,QAAM,QAAQ,SAAS,KACpB,IAAI;AAAA,IACH;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,EACtC;AACF;;;AClDA,SAAS,kBAAAC,uBAA6C;AAYtD,IAAMC,MAAK;AAwCX,eAAsB,mCACpB,QAC0C;AAC1C,QAAM,EAAE,YAAY,iBAAiB,oBAAoB,UAAU,IAAI;AAEvE,QAAM,WAAW,gBAAgB,iBAAiB,SAAS;AAC3D,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,cAAc,gBAAgB,oBAAoB,YAAY;AACpE,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,uBAAuB;AAAA;AAAA,IAExC,SAAS;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAMA,QAAM,aAAa,MAAM,8BAA8B;AAAA,IACrD,QAAQ,KAAK;AAAA,IACb,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,gBAAgB,WAAW,MAAM;AAAA,IACrC,CAAC,QAAQ,IAAI,gBAAgB;AAAA,EAC/B;AACA,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI;AAAA,MACR,qCAAqC,WAAW,cAAc,QAAQ;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,mBAAmB,kBAAkB,KAAK,QAAQ,KAAK,CAAC;AAC9D,QAAM,kBAA2C;AAAA,IAC/C,GAAG;AAAA,IACH,cAAc;AAAA,IACd,IAAI,KAAK;AAAA,IACT,eAAe,EAAE,WAAW,UAAU,QAAQ,GAAG;AAAA,IACjD,kBAAkB,EAAE,WAAW,aAAa,WAAW,GAAG;AAAA,EAC5D;AAEA,QAAM,eAAe,OAAO,MAAM,OAAO,IAAI,IAAI,oBAAI,KAAK,GAAG,YAAY;AACzE,QAAM,MAAM,GAAG,KAAK,IAAI,CAAC;AACzB,QAAM,UAAU,KAAK;AAAA,IACnBC,gBAAe,eAAmC;AAAA,EACpD;AAEA,QAAM,UAAU,wBAAwB,SAAS;AACjD,QAAM,QAAQ,SAAS,KACpB,MAAM,EAAE,IAAI,KAAK,IAAI,IAAID,IAAG,CAAC,EAC7B,IAAI;AAAA,IACH,UAAU,KAAK,UAAU,eAAe;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,UAAU;AAAA,IACV,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,EACtC;AACF;;;AC3IA,SAAS,kBAAAE,uBAA6C;AAkBtD,eAAsB,oBACpB,QAC2B;AAC3B,QAAM,EAAE,SAAS,IAAI,MAAM,UAAU,IAAI;AACzC,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,WAAW,MAAM,QAAQ,SAAS,KAAK,IAAI,EAAE,IAAI,IAAI,UAAU,CAAC,EAAE,GAAG;AAC3E,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,cAAc,mBAAmB,EAAE,EAAE;AAAA,EACjD;AAEA,QAAM,iBACJ,OAAO,KAAK,aAAa,WACpB,KAAK,MAAM,KAAK,QAAQ,IACxB,KAAK,YAAY,CAAC;AAEzB,QAAM,cAAc,QAAQ,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAC3D,QAAM,MAAM,GAAG,KAAK,IAAI,CAAC;AAEzB,QAAM,WAAW,EAAE,cAAc,QAAQ,IAAI,GAAG,eAAe;AAC/D,QAAM,UAAU,KAAK,UAAUC,gBAAe,QAA4B,CAAC;AAE3E,QAAM,QAAQ,SAAS,KACpB,IAAI;AAAA,IACH;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,EACtC;AACF;;;AC9CA,eAAsB,oBACpB,QACe;AACf,QAAM,EAAE,IAAI,UAAU,IAAI;AAC1B,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,QAAQ,SAAS,KAAK,OAAO,EAAE,IAAI,IAAI,UAAU,CAAC,EAAE,GAAG;AAC/D;","names":["extractSummary","extractSummary","extractSummary","SK","extractSummary","extractSummary","extractSummary"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
publishWorkspaceDeleted
|
|
3
|
+
} from "./chunk-BUAYVN3C.mjs";
|
|
4
|
+
import {
|
|
5
|
+
getDynamoDataService
|
|
6
|
+
} from "./chunk-6BB4CRSS.mjs";
|
|
7
|
+
import {
|
|
8
|
+
deleteDataEntityById
|
|
9
|
+
} from "./chunk-Q4KQD2NB.mjs";
|
|
10
|
+
import {
|
|
11
|
+
getDynamoControlService
|
|
12
|
+
} from "./chunk-EUIP2U5F.mjs";
|
|
13
|
+
|
|
14
|
+
// src/data/operations/control/tenant/tenant-delete-operation.ts
|
|
15
|
+
async function deleteTenantOperation(params) {
|
|
16
|
+
const { id, tableName } = params;
|
|
17
|
+
const service = getDynamoControlService(tableName);
|
|
18
|
+
await service.entities.tenant.delete({ tenantId: id, sk: "CURRENT" }).go();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/data/operations/data/organization/organization-delete-operation.ts
|
|
22
|
+
async function deleteOrganizationOperation(params) {
|
|
23
|
+
const { context, id, tableName } = params;
|
|
24
|
+
const { tenantId, workspaceId } = context;
|
|
25
|
+
const service = getDynamoDataService(tableName);
|
|
26
|
+
await deleteDataEntityById(
|
|
27
|
+
service.entities.organization,
|
|
28
|
+
tenantId,
|
|
29
|
+
workspaceId,
|
|
30
|
+
id
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/data/operations/control/workspace/workspace-delete-operation.ts
|
|
35
|
+
async function deleteWorkspaceOperation(params) {
|
|
36
|
+
const { context, id, tableName } = params;
|
|
37
|
+
const { tenantId } = context;
|
|
38
|
+
const service = getDynamoControlService(tableName);
|
|
39
|
+
const existing = await service.entities.workspace.get({ tenantId, id, sk: "CURRENT" }).go();
|
|
40
|
+
const workspaceExisted = existing.data !== null;
|
|
41
|
+
await service.entities.workspace.delete({ tenantId, id, sk: "CURRENT" }).go();
|
|
42
|
+
await deleteOrganizationOperation({
|
|
43
|
+
context: { ...context, workspaceId: id },
|
|
44
|
+
id,
|
|
45
|
+
tableName
|
|
46
|
+
});
|
|
47
|
+
if (workspaceExisted) {
|
|
48
|
+
await publishWorkspaceDeleted(context, {
|
|
49
|
+
workspaceId: id,
|
|
50
|
+
tenantId
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
deleteTenantOperation,
|
|
57
|
+
deleteOrganizationOperation,
|
|
58
|
+
deleteWorkspaceOperation
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=chunk-VQY57NOV.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/tenant/tenant-delete-operation.ts","../src/data/operations/data/organization/organization-delete-operation.ts","../src/data/operations/control/workspace/workspace-delete-operation.ts"],"sourcesContent":["import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\n\n/**\n * Delete a Tenant by id. Idempotent — does not throw if the item does not exist.\n */\nexport interface DeleteTenantParams {\n context: OpenHiContext;\n id: string;\n tableName?: string;\n}\n\nexport async function deleteTenantOperation(\n params: DeleteTenantParams,\n): Promise<void> {\n const { id, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n await service.entities.tenant.delete({ tenantId: id, sk: \"CURRENT\" }).go();\n}\n","import { getDynamoDataService } from \"../../../dynamo/dynamo-data-service\";\nimport {\n type BaseDataEntityParams,\n deleteDataEntityById,\n} from \"../../data-operations-common\";\n\n/**\n * Delete a Organization by id. No-op if the item does not exist (ElectroDB delete is idempotent).\n *\n * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md\n */\nexport interface DeleteOrganizationParams extends BaseDataEntityParams {\n id: string;\n}\n\n/**\n * Deletes the current version of a Organization. Does not throw when the resource does not exist.\n * Throws on service errors; adapters map to HTTP/GraphQL.\n */\nexport async function deleteOrganizationOperation(\n params: DeleteOrganizationParams,\n): Promise<void> {\n const { context, id, tableName } = params;\n const { tenantId, workspaceId } = context;\n const service = getDynamoDataService(tableName);\n await deleteDataEntityById(\n service.entities.organization as Parameters<typeof deleteDataEntityById>[0],\n tenantId,\n workspaceId,\n id,\n );\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\nimport { deleteOrganizationOperation } from \"../../data/organization/organization-delete-operation\";\nimport { publishWorkspaceDeleted } from \"../control-event-publisher\";\n\n/**\n * Delete a Workspace by id. Idempotent — does not throw if the item does not exist.\n */\nexport interface DeleteWorkspaceParams {\n context: OpenHiContext;\n id: string;\n tableName?: string;\n}\n\nexport async function deleteWorkspaceOperation(\n params: DeleteWorkspaceParams,\n): Promise<void> {\n const { context, id, tableName } = params;\n const { tenantId } = context;\n const service = getDynamoControlService(tableName);\n\n // Read existence first so the counter decrement event only fires when a\n // row was actually removed. The delete itself is idempotent (DynamoDB\n // DeleteItem on a missing key is a no-op); without this guard a repeated\n // delete call would publish a spurious decrement event that the\n // ADR-028 floor guard would have to absorb.\n const existing = await service.entities.workspace\n .get({ tenantId, id, sk: \"CURRENT\" })\n .go();\n const workspaceExisted = existing.data !== null;\n\n await service.entities.workspace.delete({ tenantId, id, sk: \"CURRENT\" }).go();\n\n await deleteOrganizationOperation({\n context: { ...context, workspaceId: id },\n id,\n tableName,\n });\n\n // ADR-028 (Open Item #1): publish the workspace-deleted control event so\n // the counter-maintenance consumer can decrement Tenant.workspacesInTenant.\n if (workspaceExisted) {\n await publishWorkspaceDeleted(context, {\n workspaceId: id,\n tenantId,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,eAAsB,sBACpB,QACe;AACf,QAAM,EAAE,IAAI,UAAU,IAAI;AAC1B,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,QAAQ,SAAS,OAAO,OAAO,EAAE,UAAU,IAAI,IAAI,UAAU,CAAC,EAAE,GAAG;AAC3E;;;ACAA,eAAsB,4BACpB,QACe;AACf,QAAM,EAAE,SAAS,IAAI,UAAU,IAAI;AACnC,QAAM,EAAE,UAAU,YAAY,IAAI;AAClC,QAAM,UAAU,qBAAqB,SAAS;AAC9C,QAAM;AAAA,IACJ,QAAQ,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACjBA,eAAsB,yBACpB,QACe;AACf,QAAM,EAAE,SAAS,IAAI,UAAU,IAAI;AACnC,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,UAAU,wBAAwB,SAAS;AAOjD,QAAM,WAAW,MAAM,QAAQ,SAAS,UACrC,IAAI,EAAE,UAAU,IAAI,IAAI,UAAU,CAAC,EACnC,GAAG;AACN,QAAM,mBAAmB,SAAS,SAAS;AAE3C,QAAM,QAAQ,SAAS,UAAU,OAAO,EAAE,UAAU,IAAI,IAAI,UAAU,CAAC,EAAE,GAAG;AAE5E,QAAM,4BAA4B;AAAA,IAChC,SAAS,EAAE,GAAG,SAAS,aAAa,GAAG;AAAA,IACvC;AAAA,IACA;AAAA,EACF,CAAC;AAID,MAAI,kBAAkB;AACpB,UAAM,wBAAwB,SAAS;AAAA,MACrC,aAAa;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COUNTER_MAINTENANCE_CONSUMER_NAME,
|
|
3
|
+
import_workflows
|
|
4
|
+
} from "./chunk-LKKLO66E.mjs";
|
|
1
5
|
import {
|
|
2
6
|
COUNTER_TARGET,
|
|
3
7
|
applyCounterDeltaOperation,
|
|
4
8
|
isAdminRoleAssignment
|
|
5
9
|
} from "./chunk-RQKJNMX5.mjs";
|
|
6
|
-
import {
|
|
7
|
-
COUNTER_MAINTENANCE_CONSUMER_NAME,
|
|
8
|
-
import_workflows
|
|
9
|
-
} from "./chunk-LKKLO66E.mjs";
|
|
10
10
|
import "./chunk-EUIP2U5F.mjs";
|
|
11
11
|
import "./chunk-TRY7JGWO.mjs";
|
|
12
12
|
import {
|
|
@@ -2744,7 +2744,7 @@ var import_node_zlib = require("zlib");
|
|
|
2744
2744
|
// src/data/operations/data-operations-common.ts
|
|
2745
2745
|
var BATCH_GET_MAX_ATTEMPTS = 3;
|
|
2746
2746
|
var BATCH_GET_BASE_BACKOFF_MS = 50;
|
|
2747
|
-
async function batchGetWithRetry(entity, keys) {
|
|
2747
|
+
async function batchGetWithRetry(entity, keys, options) {
|
|
2748
2748
|
if (keys.length === 0) return [];
|
|
2749
2749
|
const collected = [];
|
|
2750
2750
|
let pending = keys;
|
|
@@ -2756,7 +2756,7 @@ async function batchGetWithRetry(entity, keys) {
|
|
|
2756
2756
|
);
|
|
2757
2757
|
}
|
|
2758
2758
|
attempt++;
|
|
2759
|
-
const result = await entity.get(pending).go();
|
|
2759
|
+
const result = await entity.get(pending).go(options?.consistent ? { consistent: true } : void 0);
|
|
2760
2760
|
collected.push(...result.data);
|
|
2761
2761
|
const unprocessed = result.unprocessed ?? [];
|
|
2762
2762
|
if (unprocessed.length === 0) break;
|