@openhi/constructs 0.0.111 → 0.0.113
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 +2 -2
- 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 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/membership/membership-create-operation.ts","../src/data/operations/control/denormalized-display-names.ts","../src/data/operations/control/roleassignment/roleassignment-create-operation.ts","../src/data/operations/control/tenant/tenant-create-operation.ts","../src/data/operations/control/workspace/workspace-create-operation.ts","../src/data/operations/data/organization/organization-provision-for-workspace-operation.ts"],"sourcesContent":["import {\n assertLinkedDataIdentityCardinality,\n extractSummary,\n type Extension,\n type FhirResourceLike,\n LinkedDataIdentityCardinalityError,\n} from \"@openhi/types\";\nimport {\n buildMembershipUserProjectionItem,\n extractReferenceSlug,\n} from \"./membership-user-projection\";\nimport { buildMembershipWorkspaceProjectionItem } from \"./membership-workspace-projection\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { ValidationError } from \"../../../errors\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport { extractDenormalizedReferenceDisplay } from \"../denormalized-display-names\";\nimport {\n executeMultiWrite,\n type MultiWriteTriple,\n} from \"../multi-write-operation\";\n\nexport interface MembershipCreateParams {\n context: OpenHiContext;\n body: { id?: string; resource?: Record<string, unknown> | string };\n tableName?: string;\n}\n\nexport interface MembershipCreateResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n meta: { lastUpdated: string; versionId: string };\n}\n\nexport async function createMembershipOperation(\n params: MembershipCreateParams,\n): Promise<MembershipCreateResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `membership-${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: \"Membership\", id, ...parsedResource };\n\n let linkedDataIdentityRef: string | undefined;\n try {\n const ext = assertLinkedDataIdentityCardinality(\n resource as { extension?: Array<Extension> },\n );\n linkedDataIdentityRef = ext?.valueReference?.reference;\n } catch (e) {\n if (e instanceof LinkedDataIdentityCardinalityError) {\n throw new ValidationError(e.message, { cause: e });\n }\n throw e;\n }\n\n // TR-024 denormalized display-name attributes. The authoritative\n // write-time source per TR-024 rule 2 is the canonical Tenant / User\n // record's `displayName`. Until the carrier-record-lookup pass lands\n // (#1010 follow-up), the foundational fallback (#1009) reads the\n // FHIR Reference.display values supplied on the resource so the\n // adjacency-list projection writer here can compose SKs from\n // top-level attributes.\n const resourceRecord = resource as Record<string, unknown>;\n const denormalizedTenantName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"tenant\",\n );\n const denormalizedUserName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"user\",\n );\n const denormalizedWorkspaceName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"workspace\",\n );\n\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n // ADR-018 patterns #2 / #3 / #4 — user- and workspace-partition\n // projection rows. The projection's discriminating fields come from\n // the same FHIR Reference shape the canonical row reads, so the\n // canonical and projection writes always agree on the underlying\n // user / workspace identifiers. Missing identifiers (no user\n // reference at all) skip the user-projection; tenant-scoped\n // Memberships (no workspaceId) skip the workspace-projection — the\n // canonical row still lands either way.\n const userIdFromResource = extractReferenceSlug(resourceRecord, \"user\");\n const workspaceIdFromResource = extractReferenceSlug(\n resourceRecord,\n \"workspace\",\n );\n\n const userProjectionItem =\n userIdFromResource !== undefined\n ? buildMembershipUserProjectionItem({\n tenantId: context.tenantId,\n userId: userIdFromResource,\n workspaceId: workspaceIdFromResource,\n membershipId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedTenantName,\n denormalizedUserName,\n denormalizedWorkspaceName,\n })\n : undefined;\n\n const workspaceProjectionItem =\n userIdFromResource !== undefined && workspaceIdFromResource !== undefined\n ? buildMembershipWorkspaceProjectionItem({\n tenantId: context.tenantId,\n workspaceId: workspaceIdFromResource,\n userId: userIdFromResource,\n membershipId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedUserName,\n })\n : undefined;\n\n const canonicalItem = {\n tenantId: context.tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n linkedDataIdentityRef,\n denormalizedTenantName,\n denormalizedUserName,\n };\n\n const triples: Array<MultiWriteTriple> = [\n { entity: \"membership\", action: \"put\", item: canonicalItem },\n ];\n if (userProjectionItem) {\n triples.push({\n entity: \"membershipUserProjection\",\n action: \"put\",\n item: userProjectionItem as unknown as Record<string, unknown>,\n });\n }\n if (workspaceProjectionItem) {\n triples.push({\n entity: \"membershipWorkspaceProjection\",\n action: \"put\",\n item: workspaceProjectionItem as unknown as Record<string, unknown>,\n });\n }\n\n await executeMultiWrite({ service, triples });\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","/**\n * Helpers that capture denormalized display-name attributes from an\n * incoming FHIR Reference on Membership and RoleAssignment resources.\n *\n * **Foundational implementation for TR-024.** ADR-018 § Implementation\n * Notes and TR-024 § Recommendation pin the authoritative write-time\n * source as the carrier entity's canonical `displayName` — i.e. the\n * operations layer reads the canonical Tenant / User / Role record\n * by id and copies its display name into the relationship's\n * `denormalized<CarrierEntity>Name` field on the same\n * `TransactWriteItems`. That contract is owned by the operations-layer\n * multi-write helper filed as #1010.\n *\n * This module is the **foundational fallback** that issue #1009 uses\n * before #1010 lands: it reads the display string a client already\n * supplies on the resource's `Reference.display` field. The fallback\n * keeps Membership / RoleAssignment write paths populating the new\n * top-level attributes today (so adjacency-list projection writers\n * downstream can rely on them) while letting #1010 own the canonical-\n * row lookup without re-plumbing the write paths.\n *\n * @see TR-024 — Denormalized display-name attributes\n * @see ADR-018 § Implementation Notes\n */\n\n/**\n * Returns the trimmed display string from `resource[fieldName].display`\n * when present and non-empty; otherwise returns `undefined`. Used by the\n * Membership and RoleAssignment create / update operations to populate\n * top-level `denormalized<CarrierEntity>Name` attributes from incoming\n * FHIR Reference fields.\n *\n * Field name maps to the FHIR field on the resource:\n * - `Membership.tenant` → `\"tenant\"`\n * - `Membership.user` → `\"user\"`\n * - `RoleAssignment.tenant` → `\"tenant\"`\n * - `RoleAssignment.user` → `\"user\"`\n * - `RoleAssignment.role` → `\"role\"`\n *\n * Guards against malformed payloads (non-object `field`, non-string\n * `display`, empty strings after trim) so a single bad write never\n * blocks an entity put — matching the same defensive posture\n * `gsi1skAttribute` takes for the `resource` JSON parse.\n */\nexport function extractDenormalizedReferenceDisplay(\n resource: Record<string, unknown>,\n fieldName: string,\n): string | undefined {\n const field = resource[fieldName];\n if (!field || typeof field !== \"object\") {\n return undefined;\n }\n const display = (field as { display?: unknown }).display;\n if (typeof display !== \"string\") {\n return undefined;\n }\n const trimmed = display.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport {\n buildRoleAssignmentUserProjectionItem,\n extractReferenceSlug,\n} from \"./roleassignment-user-projection\";\nimport { buildRoleAssignmentWorkspaceProjectionItem } from \"./roleassignment-workspace-projection\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport { extractDenormalizedReferenceDisplay } from \"../denormalized-display-names\";\nimport {\n executeMultiWrite,\n type MultiWriteTriple,\n} from \"../multi-write-operation\";\n\nexport interface RoleAssignmentCreateParams {\n context: OpenHiContext;\n body: { id?: string; resource?: Record<string, unknown> | string };\n tableName?: string;\n}\n\nexport interface RoleAssignmentCreateResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n meta: { lastUpdated: string; versionId: string };\n}\n\nexport async function createRoleAssignmentOperation(\n params: RoleAssignmentCreateParams,\n): Promise<RoleAssignmentCreateResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `roleassignment-${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: \"RoleAssignment\", id, ...parsedResource };\n\n // TR-024 denormalized display-name attributes. The authoritative\n // write-time source per TR-024 rule 2 is the canonical Tenant / User /\n // Role record's `displayName`. Until the carrier-record-lookup pass\n // lands (#1010 follow-up), the foundational fallback (#1009) reads\n // the FHIR Reference.display values supplied on the resource so the\n // adjacency-list projection writer here can compose SKs from\n // top-level attributes.\n const resourceRecord = resource as Record<string, unknown>;\n const denormalizedTenantName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"tenant\",\n );\n const denormalizedUserName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"user\",\n );\n const denormalizedRoleName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"role\",\n );\n\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n // ADR-018 patterns #5 / #9 — user- and workspace-partition projection\n // rows. The projection's discriminating fields come from the same\n // FHIR Reference shape the canonical row reads, so the canonical and\n // projection writes always agree on the underlying user / role /\n // workspace identifiers. Missing user or role references skip the\n // user-projection; tenant-scoped RoleAssignments (no workspaceId)\n // skip the workspace-projection — the canonical row still lands\n // either way.\n const userIdFromResource = extractReferenceSlug(resourceRecord, \"user\");\n const roleIdFromResource = extractReferenceSlug(resourceRecord, \"role\");\n const workspaceIdFromResource = extractReferenceSlug(\n resourceRecord,\n \"workspace\",\n );\n\n const userProjectionItem =\n userIdFromResource !== undefined && roleIdFromResource !== undefined\n ? buildRoleAssignmentUserProjectionItem({\n tenantId: context.tenantId,\n userId: userIdFromResource,\n workspaceId: workspaceIdFromResource,\n roleId: roleIdFromResource,\n roleAssignmentId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedTenantName,\n denormalizedUserName,\n denormalizedRoleName,\n })\n : undefined;\n\n const workspaceProjectionItem =\n userIdFromResource !== undefined &&\n roleIdFromResource !== undefined &&\n workspaceIdFromResource !== undefined\n ? buildRoleAssignmentWorkspaceProjectionItem({\n tenantId: context.tenantId,\n workspaceId: workspaceIdFromResource,\n userId: userIdFromResource,\n roleId: roleIdFromResource,\n roleAssignmentId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedUserName,\n denormalizedRoleName,\n })\n : undefined;\n\n const canonicalItem = {\n tenantId: context.tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n denormalizedTenantName,\n denormalizedUserName,\n denormalizedRoleName,\n };\n\n const triples: Array<MultiWriteTriple> = [\n { entity: \"roleAssignment\", action: \"put\", item: canonicalItem },\n ];\n if (userProjectionItem) {\n triples.push({\n entity: \"roleAssignmentUserProjection\",\n action: \"put\",\n item: userProjectionItem as unknown as Record<string, unknown>,\n });\n }\n if (workspaceProjectionItem) {\n triples.push({\n entity: \"roleAssignmentWorkspaceProjection\",\n action: \"put\",\n item: workspaceProjectionItem as unknown as Record<string, unknown>,\n });\n }\n\n await executeMultiWrite({ service, triples });\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface CreateTenantParams {\n context: OpenHiContext;\n body: {\n id?: string;\n resource?: Record<string, unknown> | string;\n };\n tableName?: string;\n}\n\nexport interface CreateTenantResult {\n id: string;\n resource: Record<string, unknown>;\n meta: { lastUpdated: string; versionId: string };\n}\n\n/**\n * Creates a Tenant. Generates an id if not provided.\n */\nexport async function createTenantOperation(\n params: CreateTenantParams,\n): Promise<CreateTenantResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `tenant-${Date.now()}`;\n const lastUpdated = context.date;\n const vid =\n lastUpdated.replace(/[-:T.Z]/g, \"\").slice(0, 12) || Date.now().toString(36);\n\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const resource = { resourceType: \"Tenant\", id, ...parsedResource };\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.tenant\n .put({\n tenantId: id,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return { id, resource, meta: { lastUpdated, versionId: vid } };\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\nimport { provisionOrganizationForWorkspaceOperation } from \"../../data/organization/organization-provision-for-workspace-operation\";\n\nexport interface CreateWorkspaceParams {\n context: OpenHiContext;\n body: {\n id?: string;\n resource?: Record<string, unknown> | string;\n };\n tableName?: string;\n}\n\nexport interface CreateWorkspaceResult {\n id: string;\n resource: Record<string, unknown>;\n meta: { lastUpdated: string; versionId: string };\n}\n\n/**\n * Creates a Workspace scoped to the context tenant. Generates an id if not provided.\n */\nexport async function createWorkspaceOperation(\n params: CreateWorkspaceParams,\n): Promise<CreateWorkspaceResult> {\n const { context, body, tableName } = params;\n const { tenantId } = context;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `workspace-${Date.now()}`;\n const lastUpdated = context.date;\n const vid =\n lastUpdated.replace(/[-:T.Z]/g, \"\").slice(0, 12) || Date.now().toString(36);\n\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const resource = { resourceType: \"Workspace\", id, ...parsedResource };\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.workspace\n .put({\n tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n const workspaceName =\n typeof parsedResource.name === \"string\" ? parsedResource.name : undefined;\n await provisionOrganizationForWorkspaceOperation({\n context,\n workspaceId: id,\n workspaceName,\n tableName,\n });\n\n return { id, resource, meta: { lastUpdated, versionId: vid } };\n}\n","import type { Organization, Reference } from \"@openhi/types\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-data-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\nimport {\n createDataEntityRecord,\n DATA_ENTITY_SK,\n type SingleResourceResult,\n} from \"../../data-operations-common\";\n\n/**\n * Provision the slim, id-share Organization that represents a Workspace on the data plane.\n *\n * - `Organization.id === workspaceId` (id-share with the Workspace).\n * - `name` is populated from the Workspace's name when provided.\n * - `partOf` is populated when the Workspace's Tenant has an Organization (id-share at the\n * tenant level: `Tenant.id === TenantOrganization.id`). Tenant-level provisioning is out of\n * scope for #1001 — the lookup returns `undefined` until Tenant Organizations exist.\n *\n * Idempotent: re-running with the same params overwrites the same PK with the same payload.\n *\n * @see https://github.com/codedrifters/openhi/issues/1001\n */\nexport interface ProvisionOrganizationForWorkspaceParams {\n context: OpenHiContext;\n /** Workspace id; the provisioned Organization's id equals this value. */\n workspaceId: string;\n /** Name to record on the Organization. Derived from the Workspace's `name` by the caller. */\n workspaceName?: string;\n /** Optional table name override; resolved by data service from DYNAMO_TABLE_NAME when omitted. */\n tableName?: string;\n}\n\nexport type ProvisionOrganizationForWorkspaceResult =\n SingleResourceResult<Organization>;\n\nexport async function provisionOrganizationForWorkspaceOperation(\n params: ProvisionOrganizationForWorkspaceParams,\n): Promise<ProvisionOrganizationForWorkspaceResult> {\n const { context, workspaceId, workspaceName, tableName } = params;\n const { tenantId, date } = context;\n const service = getDynamoDataService(tableName);\n\n const partOf = await resolveTenantOrganizationReference(service, tenantId);\n\n const resource: Organization = {\n resourceType: \"Organization\",\n id: workspaceId,\n ...(workspaceName !== undefined && workspaceName !== \"\"\n ? { name: workspaceName }\n : {}),\n ...(partOf !== undefined ? { partOf } : {}),\n meta: {\n lastUpdated: date,\n versionId: \"1\",\n },\n };\n\n return createDataEntityRecord<Organization>(\n service.entities.organization as Parameters<\n typeof createDataEntityRecord\n >[0],\n tenantId,\n workspaceId,\n workspaceId,\n resource,\n date,\n );\n}\n\n/**\n * Resolve the Tenant's Organization reference for `Organization.partOf`. Returns `undefined`\n * when the Tenant has no Organization yet (the default until Tenant Organization provisioning\n * is wired up in a follow-up).\n *\n * Lookup convention: the Tenant's Organization is stored at\n * `(tenantId, workspaceId=tenantId, id=tenantId)` — a self-scope key that mirrors the\n * Workspace id-share pattern one level up. This scoping convention will be ratified by the\n * Tenant Organization provisioning issue; the OrganizationEntity comment notes strict\n * isolation requires both `tenantId` and `workspaceId`, so the self-scope is the\n * simplest deterministic location.\n */\nasync function resolveTenantOrganizationReference(\n service: ReturnType<typeof getDynamoDataService>,\n tenantId: string,\n): Promise<Reference | undefined> {\n const result = await (\n service.entities.organization as {\n get(params: {\n tenantId: string;\n workspaceId: string;\n id: string;\n sk: string;\n }): { go(): Promise<{ data: unknown }> };\n }\n )\n .get({\n tenantId,\n workspaceId: tenantId,\n id: tenantId,\n sk: DATA_ENTITY_SK,\n })\n .go();\n\n if (result.data === null || result.data === undefined) {\n return undefined;\n }\n\n return { reference: `Organization/${tenantId}` };\n}\n"],"mappings":"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","names":["extractSummary","extractSummary","extractReferenceSlug","extractSummary","extractSummary","extractSummary","extractSummary"]}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// src/data/operations/control/membership/membership-user-projection.ts
|
|
2
|
+
import { normalizeLabel } from "@openhi/types";
|
|
3
|
+
var MISSING_NAME_SENTINEL = "-";
|
|
4
|
+
function buildMembershipUserProjectionSkTenantLane(params) {
|
|
5
|
+
const normalizedTenantName = typeof params.denormalizedTenantName === "string" && params.denormalizedTenantName.length > 0 ? normalizeLabel(params.denormalizedTenantName) : MISSING_NAME_SENTINEL;
|
|
6
|
+
return `MEMBERSHIP#TENANT#${normalizedTenantName}#TID#${params.tenantId}#${params.membershipId}`;
|
|
7
|
+
}
|
|
8
|
+
function buildMembershipUserProjectionSkWorkspaceLane(params) {
|
|
9
|
+
const normalizedWorkspaceName = typeof params.denormalizedWorkspaceName === "string" && params.denormalizedWorkspaceName.length > 0 ? normalizeLabel(params.denormalizedWorkspaceName) : MISSING_NAME_SENTINEL;
|
|
10
|
+
return `MEMBERSHIP#WORKSPACE#TID#${params.tenantId}#${normalizedWorkspaceName}#WID#${params.workspaceId}#${params.membershipId}`;
|
|
11
|
+
}
|
|
12
|
+
function buildMembershipUserProjectionItem(input) {
|
|
13
|
+
if (!input.userId || input.userId.length === 0) {
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
const hasWorkspace = typeof input.workspaceId === "string" && input.workspaceId.length > 0;
|
|
17
|
+
const sk = hasWorkspace ? buildMembershipUserProjectionSkWorkspaceLane({
|
|
18
|
+
tenantId: input.tenantId,
|
|
19
|
+
workspaceId: input.workspaceId,
|
|
20
|
+
membershipId: input.membershipId,
|
|
21
|
+
denormalizedWorkspaceName: input.denormalizedWorkspaceName
|
|
22
|
+
}) : buildMembershipUserProjectionSkTenantLane({
|
|
23
|
+
tenantId: input.tenantId,
|
|
24
|
+
membershipId: input.membershipId,
|
|
25
|
+
denormalizedTenantName: input.denormalizedTenantName
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
userId: input.userId,
|
|
29
|
+
sk,
|
|
30
|
+
tenantId: input.tenantId,
|
|
31
|
+
workspaceId: hasWorkspace ? input.workspaceId : void 0,
|
|
32
|
+
membershipId: input.membershipId,
|
|
33
|
+
summary: input.summary,
|
|
34
|
+
vid: input.vid,
|
|
35
|
+
lastUpdated: input.lastUpdated,
|
|
36
|
+
denormalizedTenantName: input.denormalizedTenantName,
|
|
37
|
+
denormalizedUserName: input.denormalizedUserName,
|
|
38
|
+
denormalizedWorkspaceName: hasWorkspace ? input.denormalizedWorkspaceName : void 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function extractReferenceSlug(resource, fieldName) {
|
|
42
|
+
const field = resource[fieldName];
|
|
43
|
+
if (!field || typeof field !== "object") {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
const reference = field.reference;
|
|
47
|
+
if (typeof reference !== "string" || reference.length === 0) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
const slash = reference.lastIndexOf("/");
|
|
51
|
+
const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
|
|
52
|
+
return tail.length > 0 ? tail : void 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/data/operations/control/membership/membership-workspace-projection.ts
|
|
56
|
+
import { normalizeLabel as normalizeLabel2 } from "@openhi/types";
|
|
57
|
+
var MISSING_NAME_SENTINEL2 = "-";
|
|
58
|
+
function buildMembershipWorkspaceProjectionSk(params) {
|
|
59
|
+
const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? normalizeLabel2(params.denormalizedUserName) : MISSING_NAME_SENTINEL2;
|
|
60
|
+
return `MEMBERSHIP#${normalizedUserName}#USER#${params.userId}#${params.membershipId}`;
|
|
61
|
+
}
|
|
62
|
+
function buildMembershipWorkspaceProjectionItem(input) {
|
|
63
|
+
if (!input.workspaceId || input.workspaceId.length === 0) {
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
if (!input.userId || input.userId.length === 0) {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
const sk = buildMembershipWorkspaceProjectionSk({
|
|
70
|
+
userId: input.userId,
|
|
71
|
+
membershipId: input.membershipId,
|
|
72
|
+
denormalizedUserName: input.denormalizedUserName
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
tenantId: input.tenantId,
|
|
76
|
+
workspaceId: input.workspaceId,
|
|
77
|
+
sk,
|
|
78
|
+
userId: input.userId,
|
|
79
|
+
membershipId: input.membershipId,
|
|
80
|
+
summary: input.summary,
|
|
81
|
+
vid: input.vid,
|
|
82
|
+
lastUpdated: input.lastUpdated,
|
|
83
|
+
denormalizedUserName: input.denormalizedUserName
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/data/operations/control/roleassignment/roleassignment-user-projection.ts
|
|
88
|
+
import { normalizeLabel as normalizeLabel3 } from "@openhi/types";
|
|
89
|
+
var MISSING_NAME_SENTINEL3 = "-";
|
|
90
|
+
function buildRoleAssignmentUserProjectionSkTenantLane(params) {
|
|
91
|
+
const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? normalizeLabel3(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
|
|
92
|
+
return `ROLEASSIGNMENT#TENANT#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#${params.roleAssignmentId}`;
|
|
93
|
+
}
|
|
94
|
+
function buildRoleAssignmentUserProjectionSkWorkspaceLane(params) {
|
|
95
|
+
const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? normalizeLabel3(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
|
|
96
|
+
return `ROLEASSIGNMENT#WORKSPACE#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#WID#${params.workspaceId}#${params.roleAssignmentId}`;
|
|
97
|
+
}
|
|
98
|
+
function buildRoleAssignmentUserProjectionItem(input) {
|
|
99
|
+
if (!input.userId || input.userId.length === 0) {
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
if (!input.roleId || input.roleId.length === 0) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
const hasWorkspace = typeof input.workspaceId === "string" && input.workspaceId.length > 0;
|
|
106
|
+
const sk = hasWorkspace ? buildRoleAssignmentUserProjectionSkWorkspaceLane({
|
|
107
|
+
tenantId: input.tenantId,
|
|
108
|
+
workspaceId: input.workspaceId,
|
|
109
|
+
roleId: input.roleId,
|
|
110
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
111
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
112
|
+
}) : buildRoleAssignmentUserProjectionSkTenantLane({
|
|
113
|
+
tenantId: input.tenantId,
|
|
114
|
+
roleId: input.roleId,
|
|
115
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
116
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
userId: input.userId,
|
|
120
|
+
sk,
|
|
121
|
+
tenantId: input.tenantId,
|
|
122
|
+
workspaceId: hasWorkspace ? input.workspaceId : void 0,
|
|
123
|
+
roleId: input.roleId,
|
|
124
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
125
|
+
summary: input.summary,
|
|
126
|
+
vid: input.vid,
|
|
127
|
+
lastUpdated: input.lastUpdated,
|
|
128
|
+
denormalizedTenantName: input.denormalizedTenantName,
|
|
129
|
+
denormalizedUserName: input.denormalizedUserName,
|
|
130
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function extractReferenceSlug2(resource, fieldName) {
|
|
134
|
+
const field = resource[fieldName];
|
|
135
|
+
if (!field || typeof field !== "object") {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
const reference = field.reference;
|
|
139
|
+
if (typeof reference !== "string" || reference.length === 0) {
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
const slash = reference.lastIndexOf("/");
|
|
143
|
+
const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
|
|
144
|
+
return tail.length > 0 ? tail : void 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/data/operations/control/roleassignment/roleassignment-workspace-projection.ts
|
|
148
|
+
import { normalizeLabel as normalizeLabel4 } from "@openhi/types";
|
|
149
|
+
var MISSING_NAME_SENTINEL4 = "-";
|
|
150
|
+
function buildRoleAssignmentWorkspaceProjectionSk(params) {
|
|
151
|
+
const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? normalizeLabel4(params.denormalizedUserName) : MISSING_NAME_SENTINEL4;
|
|
152
|
+
return `ROLEASSIGNMENT#${params.roleId}#${normalizedUserName}#USER#${params.userId}#${params.roleAssignmentId}`;
|
|
153
|
+
}
|
|
154
|
+
function buildRoleAssignmentWorkspaceProjectionItem(input) {
|
|
155
|
+
if (!input.workspaceId || input.workspaceId.length === 0) {
|
|
156
|
+
return void 0;
|
|
157
|
+
}
|
|
158
|
+
if (!input.userId || input.userId.length === 0) {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
if (!input.roleId || input.roleId.length === 0) {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
const sk = buildRoleAssignmentWorkspaceProjectionSk({
|
|
165
|
+
roleId: input.roleId,
|
|
166
|
+
userId: input.userId,
|
|
167
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
168
|
+
denormalizedUserName: input.denormalizedUserName
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
tenantId: input.tenantId,
|
|
172
|
+
workspaceId: input.workspaceId,
|
|
173
|
+
sk,
|
|
174
|
+
userId: input.userId,
|
|
175
|
+
roleId: input.roleId,
|
|
176
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
177
|
+
summary: input.summary,
|
|
178
|
+
vid: input.vid,
|
|
179
|
+
lastUpdated: input.lastUpdated,
|
|
180
|
+
denormalizedUserName: input.denormalizedUserName,
|
|
181
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
buildMembershipUserProjectionSkTenantLane,
|
|
187
|
+
buildMembershipUserProjectionSkWorkspaceLane,
|
|
188
|
+
buildMembershipUserProjectionItem,
|
|
189
|
+
extractReferenceSlug,
|
|
190
|
+
buildMembershipWorkspaceProjectionSk,
|
|
191
|
+
buildMembershipWorkspaceProjectionItem,
|
|
192
|
+
buildRoleAssignmentUserProjectionSkTenantLane,
|
|
193
|
+
buildRoleAssignmentUserProjectionSkWorkspaceLane,
|
|
194
|
+
buildRoleAssignmentUserProjectionItem,
|
|
195
|
+
extractReferenceSlug2,
|
|
196
|
+
buildRoleAssignmentWorkspaceProjectionSk,
|
|
197
|
+
buildRoleAssignmentWorkspaceProjectionItem
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=chunk-HQ67J7BP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/membership/membership-user-projection.ts","../src/data/operations/control/membership/membership-workspace-projection.ts","../src/data/operations/control/roleassignment/roleassignment-user-projection.ts","../src/data/operations/control/roleassignment/roleassignment-workspace-projection.ts"],"sourcesContent":["/**\n * Membership user-projection composer.\n *\n * Owns the SK grammar for ADR-018 patterns #3 and #4 and assembles the\n * projection-row payloads consumed by the membership create / update /\n * delete operations. The {@link MembershipUserProjectionEntity} stores\n * the SK verbatim — the grammar lives here so the operations layer is\n * the single source of truth for projection-row shape (per\n * `.claude/rules/data-layer-layout.md`).\n *\n * SK grammar:\n *\n * - **Pattern #3** (tenant sub-lane, `workspaceId` absent):\n * `MEMBERSHIP#TENANT#<normalizedTenantName>#TID#<tenantId>#<membershipId>`\n * - **Pattern #4** (workspace sub-lane, `workspaceId` set):\n * `MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#WID#<workspaceId>#<membershipId>`\n *\n * Both patterns share the user partition `PK = USER#ID#<userId>` so\n * `Query(PK = USER#ID#<userId>, SK begins_with 'MEMBERSHIP#')` returns\n * both lanes interleaved.\n *\n * @see ADR-018 § Access Pattern Coverage (patterns #3 and #4)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n */\n\nimport { normalizeLabel } from \"@openhi/types\";\n\n/**\n * Sentinel rendered into the SK when the source display name is missing\n * or empty. Keeps the SK shape stable so a `begins_with` prefix query\n * still matches the row; the rename-cascade pipeline (TR-023) will\n * rewrite the SK once the carrier display name lands. Matches the\n * `gsi1skAttribute` defensive posture — a missing source field never\n * produces a malformed key.\n */\nconst MISSING_NAME_SENTINEL = \"-\";\n\n/** Inputs to compose a Membership user-projection row. */\nexport interface MembershipUserProjectionInput {\n readonly tenantId: string;\n readonly userId: string;\n readonly workspaceId?: string;\n readonly membershipId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedTenantName?: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedWorkspaceName?: string;\n}\n\n/** A projection-row payload ready for `multi-write` consumption. */\nexport interface MembershipUserProjectionItem {\n readonly userId: string;\n readonly sk: string;\n readonly tenantId: string;\n readonly workspaceId?: string;\n readonly membershipId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedTenantName?: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedWorkspaceName?: string;\n}\n\n/**\n * Compose the SK for ADR-018 pattern #3 (tenant sub-lane). The\n * `<normalizedTenantName>` segment sorts memberships alphabetically by\n * tenant name within the user's partition. Missing `denormalizedTenantName`\n * falls back to {@link MISSING_NAME_SENTINEL} so the SK shape stays valid\n * pre-rename-cascade.\n */\nexport function buildMembershipUserProjectionSkTenantLane(params: {\n readonly tenantId: string;\n readonly membershipId: string;\n readonly denormalizedTenantName?: string;\n}): string {\n const normalizedTenantName =\n typeof params.denormalizedTenantName === \"string\" &&\n params.denormalizedTenantName.length > 0\n ? normalizeLabel(params.denormalizedTenantName)\n : MISSING_NAME_SENTINEL;\n return `MEMBERSHIP#TENANT#${normalizedTenantName}#TID#${params.tenantId}#${params.membershipId}`;\n}\n\n/**\n * Compose the SK for ADR-018 pattern #4 (workspace sub-lane). `tenantId`\n * appears before `<normalizedWorkspaceName>` so a\n * `begins_with('MEMBERSHIP#WORKSPACE#TID#<tenantId>#')` query filters\n * by one tenant. Missing `denormalizedWorkspaceName` falls back to\n * {@link MISSING_NAME_SENTINEL}.\n */\nexport function buildMembershipUserProjectionSkWorkspaceLane(params: {\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly membershipId: string;\n readonly denormalizedWorkspaceName?: string;\n}): string {\n const normalizedWorkspaceName =\n typeof params.denormalizedWorkspaceName === \"string\" &&\n params.denormalizedWorkspaceName.length > 0\n ? normalizeLabel(params.denormalizedWorkspaceName)\n : MISSING_NAME_SENTINEL;\n return `MEMBERSHIP#WORKSPACE#TID#${params.tenantId}#${normalizedWorkspaceName}#WID#${params.workspaceId}#${params.membershipId}`;\n}\n\n/**\n * Builds the projection item for the access lane implied by the input.\n * Pattern #3 when `workspaceId` is absent or empty; pattern #4 otherwise.\n * Returns `undefined` when `userId` is missing — a Membership without a\n * linked user cannot project onto the user partition.\n */\nexport function buildMembershipUserProjectionItem(\n input: MembershipUserProjectionInput,\n): MembershipUserProjectionItem | undefined {\n if (!input.userId || input.userId.length === 0) {\n return undefined;\n }\n const hasWorkspace =\n typeof input.workspaceId === \"string\" && input.workspaceId.length > 0;\n const sk = hasWorkspace\n ? buildMembershipUserProjectionSkWorkspaceLane({\n tenantId: input.tenantId,\n workspaceId: input.workspaceId as string,\n membershipId: input.membershipId,\n denormalizedWorkspaceName: input.denormalizedWorkspaceName,\n })\n : buildMembershipUserProjectionSkTenantLane({\n tenantId: input.tenantId,\n membershipId: input.membershipId,\n denormalizedTenantName: input.denormalizedTenantName,\n });\n return {\n userId: input.userId,\n sk,\n tenantId: input.tenantId,\n workspaceId: hasWorkspace ? input.workspaceId : undefined,\n membershipId: input.membershipId,\n summary: input.summary,\n vid: input.vid,\n lastUpdated: input.lastUpdated,\n denormalizedTenantName: input.denormalizedTenantName,\n denormalizedUserName: input.denormalizedUserName,\n denormalizedWorkspaceName: hasWorkspace\n ? input.denormalizedWorkspaceName\n : undefined,\n };\n}\n\n/**\n * Extracts a FHIR `Reference` slug — the segment after the final `/`.\n * Returns `undefined` when the reference is missing or malformed so\n * callers fall back gracefully (matches the defensive posture in\n * `extractRoleId` / `extractDenormalizedReferenceDisplay`).\n */\nexport function extractReferenceSlug(\n resource: Record<string, unknown>,\n fieldName: string,\n): string | undefined {\n const field = resource[fieldName];\n if (!field || typeof field !== \"object\") {\n return undefined;\n }\n const reference = (field as { reference?: unknown }).reference;\n if (typeof reference !== \"string\" || reference.length === 0) {\n return undefined;\n }\n const slash = reference.lastIndexOf(\"/\");\n const tail = slash >= 0 ? reference.slice(slash + 1) : reference;\n return tail.length > 0 ? tail : undefined;\n}\n","/**\n * Membership workspace-projection composer.\n *\n * Owns the SK grammar for ADR-018 pattern #2 and assembles the\n * projection-row payload consumed by the membership create / update /\n * delete operations. The {@link MembershipWorkspaceProjectionEntity}\n * stores the SK verbatim — the grammar lives here so the operations\n * layer is the single source of truth for projection-row shape (per\n * `.claude/rules/data-layer-layout.md`).\n *\n * SK grammar:\n *\n * - **Pattern #2** (users in a workspace, sorted by user name —\n * workspace-scoped Memberships only):\n * `MEMBERSHIP#<normalizedUserName>#USER#<userId>#<membershipId>`\n *\n * The projection co-locates with the canonical Workspace record under\n * `PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>` so\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`\n * returns workspace metadata + every member projection in one round\n * trip. Tenant-scoped Memberships (no `workspaceId`) skip this\n * projection entirely.\n *\n * @see ADR-018 § Access Pattern Coverage (pattern #2)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n */\n\nimport { normalizeLabel } from \"@openhi/types\";\n\n/**\n * Sentinel rendered into the SK when the source display name is missing\n * or empty. Keeps the SK shape stable so a `begins_with` prefix query\n * still matches the row; the rename-cascade pipeline (TR-023) will\n * rewrite the SK once the carrier display name lands. Mirrors the\n * sibling user-projection composer's defensive posture — a missing\n * source field never produces a malformed key.\n */\nconst MISSING_NAME_SENTINEL = \"-\";\n\n/** Inputs to compose a Membership workspace-projection row. */\nexport interface MembershipWorkspaceProjectionInput {\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly userId: string;\n readonly membershipId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedUserName?: string;\n}\n\n/** A projection-row payload ready for `multi-write` consumption. */\nexport interface MembershipWorkspaceProjectionItem {\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly sk: string;\n readonly userId: string;\n readonly membershipId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedUserName?: string;\n}\n\n/**\n * Compose the SK for ADR-018 pattern #2. The `<normalizedUserName>`\n * segment sorts memberships alphabetically by user name within the\n * workspace partition. The trailing `USER#<userId>#<membershipId>`\n * disambiguates rows when two memberships share a normalized user name\n * (homonyms) and supports a per-user lookup via\n * `begins_with('MEMBERSHIP#<normalizedUserName>#USER#<userId>#')`.\n * Missing `denormalizedUserName` falls back to\n * {@link MISSING_NAME_SENTINEL} so the SK shape stays valid\n * pre-rename-cascade.\n */\nexport function buildMembershipWorkspaceProjectionSk(params: {\n readonly userId: string;\n readonly membershipId: string;\n readonly denormalizedUserName?: string;\n}): string {\n const normalizedUserName =\n typeof params.denormalizedUserName === \"string\" &&\n params.denormalizedUserName.length > 0\n ? normalizeLabel(params.denormalizedUserName)\n : MISSING_NAME_SENTINEL;\n return `MEMBERSHIP#${normalizedUserName}#USER#${params.userId}#${params.membershipId}`;\n}\n\n/**\n * Builds the projection item for a workspace-scoped Membership.\n * Returns `undefined` when `workspaceId` or `userId` is missing —\n * tenant-scoped Memberships (no workspaceId) skip the workspace\n * projection entirely, and a Membership without a linked user cannot\n * project onto the workspace partition.\n */\nexport function buildMembershipWorkspaceProjectionItem(\n input: MembershipWorkspaceProjectionInput,\n): MembershipWorkspaceProjectionItem | undefined {\n if (!input.workspaceId || input.workspaceId.length === 0) {\n return undefined;\n }\n if (!input.userId || input.userId.length === 0) {\n return undefined;\n }\n const sk = buildMembershipWorkspaceProjectionSk({\n userId: input.userId,\n membershipId: input.membershipId,\n denormalizedUserName: input.denormalizedUserName,\n });\n return {\n tenantId: input.tenantId,\n workspaceId: input.workspaceId,\n sk,\n userId: input.userId,\n membershipId: input.membershipId,\n summary: input.summary,\n vid: input.vid,\n lastUpdated: input.lastUpdated,\n denormalizedUserName: input.denormalizedUserName,\n };\n}\n","/**\n * RoleAssignment user-projection composer.\n *\n * Owns the SK grammar for ADR-018 pattern #5 and assembles the\n * projection-row payload consumed by the role-assignment create /\n * update / delete operations. The\n * {@link RoleAssignmentUserProjectionEntity} stores the SK verbatim —\n * the grammar lives here so the operations layer is the single source\n * of truth for projection-row shape (per\n * `.claude/rules/data-layer-layout.md`).\n *\n * SK grammar:\n *\n * - **tenant-level sub-lane** (`workspaceId` absent):\n * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<roleAssignmentId>`\n * - **workspace-level sub-lane** (`workspaceId` set):\n * `ROLEASSIGNMENT#WORKSPACE#<normalizedRoleName>#<roleId>#TID#<tenantId>#WID#<workspaceId>#<roleAssignmentId>`\n *\n * Both sub-lanes share the user partition `PK = USER#ID#<userId>` so\n * `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#')`\n * returns both sub-lanes interleaved with TENANT preceding WORKSPACE\n * lexicographically.\n *\n * @see ADR-018 § Access Pattern Coverage (pattern #5)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n */\n\nimport { normalizeLabel } from \"@openhi/types\";\n\n/**\n * Sentinel rendered into the SK when the source display name is missing\n * or empty. Keeps the SK shape stable so a `begins_with` prefix query\n * still matches the row; the rename-cascade pipeline (TR-023) will\n * rewrite the SK once the carrier display name lands. Matches the\n * defensive posture in `membership-user-projection` — a missing source\n * field never produces a malformed key.\n */\nconst MISSING_NAME_SENTINEL = \"-\";\n\n/** Inputs to compose a RoleAssignment user-projection row. */\nexport interface RoleAssignmentUserProjectionInput {\n readonly tenantId: string;\n readonly userId: string;\n readonly workspaceId?: string;\n readonly roleId: string;\n readonly roleAssignmentId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedTenantName?: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedRoleName?: string;\n}\n\n/** A projection-row payload ready for `multi-write` consumption. */\nexport interface RoleAssignmentUserProjectionItem {\n readonly userId: string;\n readonly sk: string;\n readonly tenantId: string;\n readonly workspaceId?: string;\n readonly roleId: string;\n readonly roleAssignmentId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedTenantName?: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedRoleName?: string;\n}\n\n/**\n * Compose the SK for ADR-018 pattern #5 — tenant-level sub-lane. The\n * `<normalizedRoleName>` segment sorts assignments alphabetically by\n * role name within the user's partition; `<roleId>` discriminates\n * rename-stable. Missing `denormalizedRoleName` falls back to\n * {@link MISSING_NAME_SENTINEL} so the SK shape stays valid\n * pre-rename-cascade.\n */\nexport function buildRoleAssignmentUserProjectionSkTenantLane(params: {\n readonly tenantId: string;\n readonly roleId: string;\n readonly roleAssignmentId: string;\n readonly denormalizedRoleName?: string;\n}): string {\n const normalizedRoleName =\n typeof params.denormalizedRoleName === \"string\" &&\n params.denormalizedRoleName.length > 0\n ? normalizeLabel(params.denormalizedRoleName)\n : MISSING_NAME_SENTINEL;\n return `ROLEASSIGNMENT#TENANT#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#${params.roleAssignmentId}`;\n}\n\n/**\n * Compose the SK for ADR-018 pattern #5 — workspace-level sub-lane.\n * Same `<normalizedRoleName>#<roleId>` sort discriminator as the tenant\n * sub-lane; the trailing segments narrow the partition to a single\n * tenant + workspace. Missing `denormalizedRoleName` falls back to\n * {@link MISSING_NAME_SENTINEL}.\n */\nexport function buildRoleAssignmentUserProjectionSkWorkspaceLane(params: {\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly roleId: string;\n readonly roleAssignmentId: string;\n readonly denormalizedRoleName?: string;\n}): string {\n const normalizedRoleName =\n typeof params.denormalizedRoleName === \"string\" &&\n params.denormalizedRoleName.length > 0\n ? normalizeLabel(params.denormalizedRoleName)\n : MISSING_NAME_SENTINEL;\n return `ROLEASSIGNMENT#WORKSPACE#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#WID#${params.workspaceId}#${params.roleAssignmentId}`;\n}\n\n/**\n * Builds the projection item for the access lane implied by the input.\n * Tenant-level sub-lane when `workspaceId` is absent or empty;\n * workspace-level sub-lane otherwise. Returns `undefined` when `userId`\n * or `roleId` is missing — without either the projection cannot land\n * under a user partition or be sorted by role name.\n */\nexport function buildRoleAssignmentUserProjectionItem(\n input: RoleAssignmentUserProjectionInput,\n): RoleAssignmentUserProjectionItem | undefined {\n if (!input.userId || input.userId.length === 0) {\n return undefined;\n }\n if (!input.roleId || input.roleId.length === 0) {\n return undefined;\n }\n const hasWorkspace =\n typeof input.workspaceId === \"string\" && input.workspaceId.length > 0;\n const sk = hasWorkspace\n ? buildRoleAssignmentUserProjectionSkWorkspaceLane({\n tenantId: input.tenantId,\n workspaceId: input.workspaceId as string,\n roleId: input.roleId,\n roleAssignmentId: input.roleAssignmentId,\n denormalizedRoleName: input.denormalizedRoleName,\n })\n : buildRoleAssignmentUserProjectionSkTenantLane({\n tenantId: input.tenantId,\n roleId: input.roleId,\n roleAssignmentId: input.roleAssignmentId,\n denormalizedRoleName: input.denormalizedRoleName,\n });\n return {\n userId: input.userId,\n sk,\n tenantId: input.tenantId,\n workspaceId: hasWorkspace ? input.workspaceId : undefined,\n roleId: input.roleId,\n roleAssignmentId: input.roleAssignmentId,\n summary: input.summary,\n vid: input.vid,\n lastUpdated: input.lastUpdated,\n denormalizedTenantName: input.denormalizedTenantName,\n denormalizedUserName: input.denormalizedUserName,\n denormalizedRoleName: input.denormalizedRoleName,\n };\n}\n\n/**\n * Extracts a FHIR `Reference` slug — the segment after the final `/`.\n * Returns `undefined` when the reference is missing or malformed so\n * callers fall back gracefully (matches the defensive posture in\n * `extractRoleId` / `extractDenormalizedReferenceDisplay`).\n */\nexport function extractReferenceSlug(\n resource: Record<string, unknown>,\n fieldName: string,\n): string | undefined {\n const field = resource[fieldName];\n if (!field || typeof field !== \"object\") {\n return undefined;\n }\n const reference = (field as { reference?: unknown }).reference;\n if (typeof reference !== \"string\" || reference.length === 0) {\n return undefined;\n }\n const slash = reference.lastIndexOf(\"/\");\n const tail = slash >= 0 ? reference.slice(slash + 1) : reference;\n return tail.length > 0 ? tail : undefined;\n}\n","/**\n * RoleAssignment workspace-projection composer.\n *\n * Owns the SK grammar for ADR-018 pattern #9 and assembles the\n * projection-row payload consumed by the role-assignment create /\n * update / delete operations. The\n * {@link RoleAssignmentWorkspaceProjectionEntity} stores the SK\n * verbatim — the grammar lives here so the operations layer is the\n * single source of truth for projection-row shape (per\n * `.claude/rules/data-layer-layout.md`).\n *\n * SK grammar:\n *\n * - **Pattern #9** (users with a specific role in a workspace, sorted\n * by user name — workspace-scoped RoleAssignments only):\n * `ROLEASSIGNMENT#<roleId>#<normalizedUserName>#USER#<userId>#<roleAssignmentId>`\n *\n * The SK is **discriminator-first** on the raw `<roleId>` (mirroring\n * the canonical GSI1SK from pattern #8). Role id discriminates first so\n * a `begins_with('ROLEASSIGNMENT#<roleId>#')` filter returns every user\n * assigned to that role in the workspace, sorted alphabetically by\n * normalized user name. The trailing `USER#<userId>#<roleAssignmentId>`\n * disambiguates rows when two assignments share a normalized user name\n * (homonyms) and supports a per-user lookup via\n * `begins_with('ROLEASSIGNMENT#<roleId>#<normalizedUserName>#USER#<userId>#')`.\n *\n * The projection co-locates with the canonical Workspace record (and\n * the Membership workspace-projection rows from pattern #2) under\n * `PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>` so\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>)` returns\n * workspace metadata + every member projection + every role-assignment\n * projection in one round trip. Tenant-scoped RoleAssignments (no\n * `workspaceId`) skip this projection entirely.\n *\n * **Rename-cascade interaction (TR-023, Phase 6).** The SK uses the\n * raw `<roleId>` (rename-stable) for the discriminator and\n * `<normalizedUserName>` for the secondary sort. A Role rename does\n * NOT rewrite this SK; a User rename DOES (cascaded by the rename\n * pipeline).\n *\n * @see ADR-018 § Access Pattern Coverage (pattern #9)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n */\n\nimport { normalizeLabel } from \"@openhi/types\";\n\n/**\n * Sentinel rendered into the SK when the source user display name is\n * missing or empty. Keeps the SK shape stable so a `begins_with` prefix\n * query still matches the row; the rename-cascade pipeline (TR-023)\n * will rewrite the SK once the carrier display name lands. Mirrors the\n * sibling projection composers' defensive posture — a missing source\n * field never produces a malformed key.\n */\nconst MISSING_NAME_SENTINEL = \"-\";\n\n/** Inputs to compose a RoleAssignment workspace-projection row. */\nexport interface RoleAssignmentWorkspaceProjectionInput {\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly userId: string;\n readonly roleId: string;\n readonly roleAssignmentId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedRoleName?: string;\n}\n\n/** A projection-row payload ready for `multi-write` consumption. */\nexport interface RoleAssignmentWorkspaceProjectionItem {\n readonly tenantId: string;\n readonly workspaceId: string;\n readonly sk: string;\n readonly userId: string;\n readonly roleId: string;\n readonly roleAssignmentId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedRoleName?: string;\n}\n\n/**\n * Compose the SK for ADR-018 pattern #9. The discriminator-first\n * `<roleId>` segment (raw, NOT normalized — matches the canonical\n * GSI1SK from pattern #8) lets `begins_with('ROLEASSIGNMENT#<roleId>#')`\n * filter one role. The `<normalizedUserName>` segment sorts assignments\n * alphabetically by user name within that role. The trailing\n * `USER#<userId>#<roleAssignmentId>` disambiguates homonyms and\n * supports a per-user lookup via\n * `begins_with('ROLEASSIGNMENT#<roleId>#<normalizedUserName>#USER#<userId>#')`.\n * Missing `denormalizedUserName` falls back to\n * {@link MISSING_NAME_SENTINEL} so the SK shape stays valid\n * pre-rename-cascade.\n */\nexport function buildRoleAssignmentWorkspaceProjectionSk(params: {\n readonly roleId: string;\n readonly userId: string;\n readonly roleAssignmentId: string;\n readonly denormalizedUserName?: string;\n}): string {\n const normalizedUserName =\n typeof params.denormalizedUserName === \"string\" &&\n params.denormalizedUserName.length > 0\n ? normalizeLabel(params.denormalizedUserName)\n : MISSING_NAME_SENTINEL;\n return `ROLEASSIGNMENT#${params.roleId}#${normalizedUserName}#USER#${params.userId}#${params.roleAssignmentId}`;\n}\n\n/**\n * Builds the projection item for a workspace-scoped RoleAssignment.\n * Returns `undefined` when `workspaceId`, `userId`, or `roleId` is\n * missing — tenant-scoped RoleAssignments (no workspaceId) skip the\n * workspace projection entirely; a RoleAssignment without a linked\n * user or role cannot project onto the workspace partition under the\n * pattern-#9 SK shape.\n */\nexport function buildRoleAssignmentWorkspaceProjectionItem(\n input: RoleAssignmentWorkspaceProjectionInput,\n): RoleAssignmentWorkspaceProjectionItem | undefined {\n if (!input.workspaceId || input.workspaceId.length === 0) {\n return undefined;\n }\n if (!input.userId || input.userId.length === 0) {\n return undefined;\n }\n if (!input.roleId || input.roleId.length === 0) {\n return undefined;\n }\n const sk = buildRoleAssignmentWorkspaceProjectionSk({\n roleId: input.roleId,\n userId: input.userId,\n roleAssignmentId: input.roleAssignmentId,\n denormalizedUserName: input.denormalizedUserName,\n });\n return {\n tenantId: input.tenantId,\n workspaceId: input.workspaceId,\n sk,\n userId: input.userId,\n roleId: input.roleId,\n roleAssignmentId: input.roleAssignmentId,\n summary: input.summary,\n vid: input.vid,\n lastUpdated: input.lastUpdated,\n denormalizedUserName: input.denormalizedUserName,\n denormalizedRoleName: input.denormalizedRoleName,\n };\n}\n"],"mappings":"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","names":["normalizeLabel","MISSING_NAME_SENTINEL","normalizeLabel","MISSING_NAME_SENTINEL","extractReferenceSlug","normalizeLabel","MISSING_NAME_SENTINEL"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDynamoControlService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6NBGYGFL.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-KO64HPWQ.mjs.map
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
dispatchListMode,
|
|
4
4
|
getDynamoDataService,
|
|
5
5
|
listDataEntitiesByWorkspace
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QMBJ4VHC.mjs";
|
|
7
7
|
import {
|
|
8
8
|
SHARD_COUNT,
|
|
9
9
|
getDynamoControlService
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-6NBGYGFL.mjs";
|
|
11
11
|
|
|
12
12
|
// src/data/operations/data/practitionerrole/practitionerrole-list-operation.ts
|
|
13
13
|
async function listPractitionerRolesOperation(params) {
|
|
@@ -93,4 +93,4 @@ export {
|
|
|
93
93
|
listMembershipsOperation,
|
|
94
94
|
listRoleAssignmentsOperation
|
|
95
95
|
};
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
96
|
+
//# sourceMappingURL=chunk-KSFC72TT.mjs.map
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ForbiddenError,
|
|
3
|
-
NotFoundError,
|
|
4
|
-
ValidationError,
|
|
5
2
|
batchGetWithRetry,
|
|
6
3
|
dispatchListMode
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QMBJ4VHC.mjs";
|
|
5
|
+
import {
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
ValidationError
|
|
9
|
+
} from "./chunk-FYHBHHWK.mjs";
|
|
8
10
|
import {
|
|
9
11
|
SHARD_COUNT,
|
|
10
12
|
getDynamoControlService
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6NBGYGFL.mjs";
|
|
12
14
|
|
|
13
15
|
// src/data/operations/control/user/user-find-by-sub-operation.ts
|
|
14
16
|
async function findUserBySubOperation(params) {
|
|
@@ -121,53 +123,67 @@ async function listUsersOperation(params) {
|
|
|
121
123
|
// src/data/operations/control/user/user-switch-tenant-workspace-operation.ts
|
|
122
124
|
import { extractSummary as extractSummary2 } from "@openhi/types";
|
|
123
125
|
|
|
124
|
-
// src/data/operations/control/membership/membership-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
}
|
|
126
|
+
// src/data/operations/control/membership/membership-list-by-user-operation.ts
|
|
127
|
+
function buildSkPrefix(mode, tenantId) {
|
|
128
|
+
switch (mode) {
|
|
129
|
+
case "tenant":
|
|
130
|
+
return "MEMBERSHIP#TENANT#";
|
|
131
|
+
case "workspace":
|
|
132
|
+
return "MEMBERSHIP#WORKSPACE#";
|
|
133
|
+
case "workspaceInTenant":
|
|
134
|
+
return `MEMBERSHIP#WORKSPACE#TID#${tenantId}#`;
|
|
135
|
+
case "all":
|
|
136
|
+
default:
|
|
137
|
+
return "MEMBERSHIP#";
|
|
154
138
|
}
|
|
155
|
-
return { memberships };
|
|
156
139
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
140
|
+
async function membershipListByUserOperation(params) {
|
|
141
|
+
const {
|
|
142
|
+
userId,
|
|
143
|
+
mode = "all",
|
|
144
|
+
tenantId,
|
|
145
|
+
cursor = null,
|
|
146
|
+
limit,
|
|
147
|
+
order,
|
|
148
|
+
tableName
|
|
149
|
+
} = params;
|
|
150
|
+
if (mode === "workspaceInTenant" && !tenantId) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
'membershipListByUserOperation: tenantId is required when mode === "workspaceInTenant"'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const service = getDynamoControlService(tableName);
|
|
156
|
+
const skPrefix = buildSkPrefix(mode, tenantId);
|
|
157
|
+
const goOptions = {
|
|
158
|
+
cursor
|
|
159
|
+
};
|
|
160
|
+
if (limit !== void 0) {
|
|
161
|
+
goOptions.limit = limit;
|
|
162
|
+
}
|
|
163
|
+
if (order !== void 0) {
|
|
164
|
+
goOptions.order = order;
|
|
165
|
+
}
|
|
166
|
+
const result = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: skPrefix }).go(goOptions);
|
|
167
|
+
const items = (result.data ?? []).map(
|
|
168
|
+
(row) => ({
|
|
169
|
+
userId: row.userId,
|
|
170
|
+
sk: row.sk,
|
|
171
|
+
tenantId: row.tenantId,
|
|
172
|
+
workspaceId: row.workspaceId,
|
|
173
|
+
membershipId: row.membershipId,
|
|
174
|
+
summary: row.summary,
|
|
175
|
+
vid: row.vid,
|
|
176
|
+
lastUpdated: row.lastUpdated,
|
|
177
|
+
denormalizedTenantName: row.denormalizedTenantName,
|
|
178
|
+
denormalizedUserName: row.denormalizedUserName,
|
|
179
|
+
denormalizedWorkspaceName: row.denormalizedWorkspaceName
|
|
180
|
+
})
|
|
166
181
|
);
|
|
182
|
+
return { items, cursor: result.cursor ?? null };
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
// src/data/operations/control/user/user-switch-tenant-workspace-operation.ts
|
|
170
|
-
var
|
|
186
|
+
var SK2 = "CURRENT";
|
|
171
187
|
async function switchUserTenantWorkspaceOperation(params) {
|
|
172
188
|
const { cognitoSub, tenantReference, workspaceReference, tableName } = params;
|
|
173
189
|
const tenantId = idFromReference(tenantReference, "Tenant/");
|
|
@@ -200,15 +216,16 @@ async function switchUserTenantWorkspaceOperation(params) {
|
|
|
200
216
|
"User not yet provisioned for the authenticated Cognito subject."
|
|
201
217
|
);
|
|
202
218
|
}
|
|
203
|
-
const
|
|
204
|
-
tenantId,
|
|
219
|
+
const projection = await membershipListByUserOperation({
|
|
205
220
|
userId: user.id,
|
|
221
|
+
mode: "workspaceInTenant",
|
|
222
|
+
tenantId,
|
|
206
223
|
tableName
|
|
207
224
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!
|
|
225
|
+
const hasMembership = projection.items.some(
|
|
226
|
+
(row) => row.workspaceId === workspaceId
|
|
227
|
+
);
|
|
228
|
+
if (!hasMembership) {
|
|
212
229
|
throw new ForbiddenError(
|
|
213
230
|
`User is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`
|
|
214
231
|
);
|
|
@@ -227,7 +244,7 @@ async function switchUserTenantWorkspaceOperation(params) {
|
|
|
227
244
|
extractSummary2(updatedResource)
|
|
228
245
|
);
|
|
229
246
|
const service = getDynamoControlService(tableName);
|
|
230
|
-
await service.entities.user.patch({ id: user.id, sk:
|
|
247
|
+
await service.entities.user.patch({ id: user.id, sk: SK2 }).set({
|
|
231
248
|
resource: JSON.stringify(updatedResource),
|
|
232
249
|
summary,
|
|
233
250
|
vid,
|
|
@@ -279,6 +296,7 @@ export {
|
|
|
279
296
|
createUserOperation,
|
|
280
297
|
deleteUserOperation,
|
|
281
298
|
getUserByIdOperation,
|
|
299
|
+
membershipListByUserOperation,
|
|
282
300
|
listUsersOperation,
|
|
283
301
|
updateUserOperation,
|
|
284
302
|
findUserBySubOperation,
|
|
@@ -286,4 +304,4 @@ export {
|
|
|
286
304
|
idFromReference,
|
|
287
305
|
switchUserTenantWorkspaceOperation
|
|
288
306
|
};
|
|
289
|
-
//# sourceMappingURL=chunk-
|
|
307
|
+
//# sourceMappingURL=chunk-NZRW7ROK.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-list-by-user-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 { 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 `SK begins_with\n * 'MEMBERSHIP#WORKSPACE#TID#<tenantId>#'` (via\n * {@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 { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\n\n/**\n * Filter modes for {@link membershipListByUserOperation}.\n *\n * Maps directly to the ADR-018 sub-lane discriminator in the user-projection\n * SK (`MEMBERSHIP#TENANT#…` vs `MEMBERSHIP#WORKSPACE#…`):\n *\n * - `\"all\"` — `Query(PK = USER#ID#<userId>, SK begins_with 'MEMBERSHIP#')`.\n * Returns both lanes interleaved in raw SK order.\n * - `\"tenant\"` — `SK begins_with 'MEMBERSHIP#TENANT#'`. Pattern #3 only.\n * - `\"workspace\"` — `SK begins_with 'MEMBERSHIP#WORKSPACE#'`. Pattern #4\n * across every tenant.\n * - `\"workspaceInTenant\"` — `SK begins_with 'MEMBERSHIP#WORKSPACE#TID#<tenantId>#'`.\n * Pattern #4 narrowed to one tenant. Requires `tenantId`.\n */\nexport type MembershipListByUserMode =\n | \"all\"\n | \"tenant\"\n | \"workspace\"\n | \"workspaceInTenant\";\n\n/** Inputs accepted by {@link membershipListByUserOperation}. */\nexport interface MembershipListByUserParams {\n readonly userId: string;\n /** Filter mode — see {@link MembershipListByUserMode}. Defaults to `\"all\"`. */\n readonly mode?: MembershipListByUserMode;\n /** Required only when `mode === \"workspaceInTenant\"`. */\n readonly tenantId?: string;\n /** ElectroDB cursor from a prior page. Forwarded to `.go({ cursor })`. */\n readonly cursor?: string | null;\n /** Per-page item limit forwarded to `.go({ limit })`. */\n readonly limit?: number;\n /** Sort order forwarded to `.go({ order })`. Defaults to ElectroDB's `\"asc\"`. */\n readonly order?: \"asc\" | \"desc\";\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n}\n\n/** One projection-row payload as returned to a consumer. */\nexport interface MembershipUserProjectionEntry {\n readonly userId: string;\n readonly sk: string;\n readonly tenantId: string;\n readonly workspaceId?: string;\n readonly membershipId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedTenantName?: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedWorkspaceName?: string;\n}\n\n/** Page returned by {@link membershipListByUserOperation}. */\nexport interface MembershipListByUserResult {\n readonly items: Array<MembershipUserProjectionEntry>;\n /** ElectroDB cursor for the next page, or `null` when exhausted. */\n readonly cursor: string | null;\n}\n\n/**\n * Compose the SK prefix for a given filter mode. Centralizing the\n * prefix string here keeps the SK grammar (owned by\n * `membership-user-projection.ts`) the single source of truth for the\n * lane discriminators — this function reads them, it does not invent them.\n */\nfunction buildSkPrefix(\n mode: MembershipListByUserMode,\n tenantId: string | undefined,\n): string {\n switch (mode) {\n case \"tenant\":\n return \"MEMBERSHIP#TENANT#\";\n case \"workspace\":\n return \"MEMBERSHIP#WORKSPACE#\";\n case \"workspaceInTenant\":\n // Pattern-#4 SK places `<tenantId>` directly after the\n // `MEMBERSHIP#WORKSPACE#TID#` segment so a `begins_with` filter\n // narrows the workspace lane to a single tenant.\n return `MEMBERSHIP#WORKSPACE#TID#${tenantId}#`;\n case \"all\":\n default:\n return \"MEMBERSHIP#\";\n }\n}\n\n/**\n * List Memberships for a user via the ADR-018 user-partition projection\n * (no GSI hop).\n *\n * Reads `MembershipUserProjectionEntity` rows under `PK = USER#ID#<userId>`\n * with an `SK begins_with` filter selected by `mode`:\n *\n * | Mode | SK begins_with | Covers |\n * |---|---|---|\n * | `all` (default) | `MEMBERSHIP#` | patterns #3 + #4 interleaved |\n * | `tenant` | `MEMBERSHIP#TENANT#` | pattern #3 only |\n * | `workspace` | `MEMBERSHIP#WORKSPACE#` | pattern #4 only, across tenants |\n * | `workspaceInTenant` | `MEMBERSHIP#WORKSPACE#TID#<tenantId>#` | pattern #4 in one tenant |\n *\n * Returns the projection rows verbatim (`summary`, `vid`, `lastUpdated`\n * plus the projection-discriminating fields) — full canonical-resource\n * hydration is opt-in for callers via\n * `MembershipEntity.get({ tenantId, id: membershipId })`. Pagination\n * mirrors ElectroDB's native `.go({ cursor })` shape; the returned\n * `cursor` is opaque to callers.\n *\n * @see ADR-018 § Access Pattern Coverage (patterns #3 and #4)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar)\n */\nexport async function membershipListByUserOperation(\n params: MembershipListByUserParams,\n): Promise<MembershipListByUserResult> {\n const {\n userId,\n mode = \"all\",\n tenantId,\n cursor = null,\n limit,\n order,\n tableName,\n } = params;\n\n if (mode === \"workspaceInTenant\" && !tenantId) {\n throw new Error(\n 'membershipListByUserOperation: tenantId is required when mode === \"workspaceInTenant\"',\n );\n }\n\n const service = getDynamoControlService(tableName);\n const skPrefix = buildSkPrefix(mode, tenantId);\n\n const goOptions: {\n cursor?: string | null;\n limit?: number;\n order?: \"asc\" | \"desc\";\n } = {\n cursor,\n };\n if (limit !== undefined) {\n goOptions.limit = limit;\n }\n if (order !== undefined) {\n goOptions.order = order;\n }\n\n const result = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: skPrefix })\n .go(goOptions);\n\n const items: Array<MembershipUserProjectionEntry> = (result.data ?? []).map(\n (row) => ({\n userId: row.userId,\n sk: row.sk,\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n membershipId: row.membershipId,\n summary: row.summary,\n vid: row.vid,\n lastUpdated: row.lastUpdated,\n denormalizedTenantName: row.denormalizedTenantName,\n denormalizedUserName: row.denormalizedUserName,\n denormalizedWorkspaceName: row.denormalizedWorkspaceName,\n }),\n );\n\n return { items, cursor: result.cursor ?? null };\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","extractSummary","extractSummary","extractSummary"]}
|