@openhi/constructs 0.0.90 → 0.0.92

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/cognito/post-confirmation.handler.ts","../src/data/dynamo/dynamo-control-service.ts","../src/data/dynamo/dynamo-client.ts","../src/data/dynamo/entities/control/configuration-entity.ts","../src/data/dynamo/shard.ts","../src/data/dynamo/entities/control/control-entity-common.ts","../src/data/dynamo/entities/control/membership-entity.ts","../src/data/dynamo/entities/control/role-entity.ts","../src/data/dynamo/entities/control/roleassignment-entity.ts","../src/data/dynamo/entities/control/tenant-entity.ts","../src/data/dynamo/entities/control/user-entity.ts","../src/data/dynamo/entities/control/workspace-entity.ts"],"sourcesContent":["import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport type {\n Context,\n PostConfirmationTriggerEvent,\n PostConfirmationTriggerHandler,\n} from \"aws-lambda\";\nimport { ulid } from \"ulid\";\nimport { getDynamoControlService } from \"../../data/dynamo/dynamo-control-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/post-confirmation-lambda.md\n *\n * Cognito Post Confirmation trigger.\n *\n * Implements the ADR 2026-03-17-01 invariant that every confirmed user\n * starts with their own default Tenant, default Workspace, Memberships,\n * and a `tenant-user` RoleAssignment, plus a User record carrying the\n * Cognito `sub` (drives the GSI2 sub-lookup added in epic #892) and\n * `currentTenant` / `currentWorkspace` references the Pre Token Generation\n * Lambda (#767) and Tenant Switcher (#769) read at runtime.\n *\n * Sign-up must not fail because of a DynamoDB error: any failure is logged\n * and the event is returned unchanged so Cognito completes confirmation.\n */\nfunction summaryFor(resource: Record<string, unknown>): string {\n return JSON.stringify(extractSummary(resource as FhirResourceLike));\n}\n\nexport const handler: PostConfirmationTriggerHandler = async (\n event: PostConfirmationTriggerEvent,\n _context: Context,\n): Promise<PostConfirmationTriggerEvent> => {\n try {\n const { sub, email } = event.request.userAttributes;\n const displayName = email ?? event.userName ?? sub;\n\n const userId = ulid();\n const tenantId = ulid();\n const workspaceId = ulid();\n const userTenantMembershipId = ulid();\n const userWorkspaceMembershipId = ulid();\n const roleAssignmentId = ulid();\n\n const lastUpdated = new Date().toISOString();\n const vid = \"1\";\n\n const service = getDynamoControlService();\n\n const tenantResource = {\n resourceType: \"Tenant\",\n id: tenantId,\n displayName: `${displayName}'s Practice`,\n status: \"active\",\n };\n const workspaceResource = {\n resourceType: \"Workspace\",\n id: workspaceId,\n displayName: \"Default Workspace\",\n status: \"active\",\n tenant: { reference: `Tenant/${tenantId}` },\n };\n const userResource = {\n resourceType: \"User\",\n id: userId,\n displayName,\n status: \"active\",\n currentTenant: { reference: `Tenant/${tenantId}` },\n currentWorkspace: { reference: `Workspace/${workspaceId}` },\n };\n const userTenantMembershipResource = {\n resourceType: \"Membership\",\n id: userTenantMembershipId,\n status: \"active\",\n user: { reference: `User/${userId}` },\n tenant: { reference: `Tenant/${tenantId}` },\n };\n const userWorkspaceMembershipResource = {\n resourceType: \"Membership\",\n id: userWorkspaceMembershipId,\n status: \"active\",\n user: { reference: `User/${userId}` },\n tenant: { reference: `Tenant/${tenantId}` },\n workspace: { reference: `Workspace/${workspaceId}` },\n };\n const roleAssignmentResource = {\n resourceType: \"RoleAssignment\",\n id: roleAssignmentId,\n status: \"active\",\n user: { reference: `User/${userId}` },\n tenant: { reference: `Tenant/${tenantId}` },\n role: \"tenant-user\",\n };\n\n await service.entities.tenant\n .put({\n tenantId,\n id: tenantId,\n resource: JSON.stringify(tenantResource),\n summary: summaryFor(tenantResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.workspace\n .put({\n tenantId,\n id: workspaceId,\n resource: JSON.stringify(workspaceResource),\n summary: summaryFor(workspaceResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.user\n .put({\n id: userId,\n cognitoSub: sub,\n resource: JSON.stringify(userResource),\n summary: summaryFor(userResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.membership\n .put({\n tenantId,\n id: userTenantMembershipId,\n resource: JSON.stringify(userTenantMembershipResource),\n summary: summaryFor(userTenantMembershipResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.membership\n .put({\n tenantId,\n id: userWorkspaceMembershipId,\n resource: JSON.stringify(userWorkspaceMembershipResource),\n summary: summaryFor(userWorkspaceMembershipResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.roleAssignment\n .put({\n tenantId,\n id: roleAssignmentId,\n resource: JSON.stringify(roleAssignmentResource),\n summary: summaryFor(roleAssignmentResource),\n vid,\n lastUpdated,\n })\n .go();\n } catch (err) {\n console.warn(\n \"PostConfirmation onboarding failed; returning event unchanged\",\n err,\n );\n }\n return event;\n};\n","import { Service } from \"electrodb\";\nimport { defaultTableName, dynamoClient } from \"./dynamo-client\";\nimport { ConfigurationEntity } from \"./entities/control/configuration-entity\";\nimport { MembershipEntity } from \"./entities/control/membership-entity\";\nimport { RoleEntity } from \"./entities/control/role-entity\";\nimport { RoleAssignmentEntity } from \"./entities/control/roleassignment-entity\";\nimport { TenantEntity } from \"./entities/control/tenant-entity\";\nimport { UserEntity } from \"./entities/control/user-entity\";\nimport { WorkspaceEntity } from \"./entities/control/workspace-entity\";\n\n/**\n * Control-plane entities only (service \"control\"). Same table as data plane; use\n * DynamoDataService for data-plane entities.\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n */\n\nconst controlPlaneEntities = {\n configuration: ConfigurationEntity,\n membership: MembershipEntity,\n role: RoleEntity,\n roleAssignment: RoleAssignmentEntity,\n tenant: TenantEntity,\n user: UserEntity,\n workspace: WorkspaceEntity,\n};\n\nconst controlPlaneService = new Service(controlPlaneEntities, {\n table: defaultTableName,\n client: dynamoClient,\n});\n\n/**\n * Control-plane service: entities for configuration and control. Use with the\n * data store table (PK, SK, GSI1; UserEntity also uses GSI2).\n */\nexport const DynamoControlService = {\n entities: controlPlaneService.entities,\n};\n\nexport type DynamoControlServiceType = typeof DynamoControlService;\n\n/**\n * Returns the control-plane service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoControlService(\n tableName?: string,\n): DynamoControlServiceType {\n const resolved = tableName ?? defaultTableName;\n const service = new Service(controlPlaneEntities, {\n table: resolved,\n client: dynamoClient,\n });\n return {\n entities: service.entities,\n };\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\n\n/**\n * DynamoDB table name for the data store. Set via DYNAMO_TABLE_NAME at runtime\n * (e.g. from Lambda env); defaults for local/test.\n */\nexport const defaultTableName =\n process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * DynamoDB client. When MOCK_DYNAMODB_ENDPOINT is set (e.g. local DynamoDB or\n * jest-dynalite), uses that endpoint with no SSL and region \"local\".\n */\nexport const dynamoClient = new DynamoDBClient({\n ...(process.env.MOCK_DYNAMODB_ENDPOINT && {\n endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,\n sslEnabled: false,\n region: \"local\",\n }),\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Configuration data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Partially tenant-isolated, control plane. Cascade of scope\n * levels: resolution order user → workspace → tenant → baseline. Sentinels: tenantId \"BASELINE\" for\n * baseline tier; workspaceId/userId/roleId \"-\" for absent dimension.\n *\n * Key structure: PK = CONFIG#TID#<tenantId>#WID#<workspaceId>#UID#<userId>#RID#<roleId>,\n * SK = KEY#<key>#SK#<sk>. Uniqueness: one Configuration per (tenantId, workspaceId, userId, roleId, key).\n * Standard attributes and key-building conventions align with single-table design.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Configuration entries in a tenant/workspace\n * across the four shards.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/entities/configuration.md\n * @see sites/www-docs/content/architecture/control-plane/configuration.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/entity-standards.md — Key-building conventions (keys built inside entity)\n */\nexport const ConfigurationEntity = new Entity({\n model: {\n entity: \"configuration\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key. \"CURRENT\" for current version; version history in S3. */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant scope. Use \"BASELINE\" when the config is baseline default (no tenant). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n default: \"BASELINE\",\n },\n /** Workspace scope. Use \"-\" when absent. */\n workspaceId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** User scope. Use \"-\" when absent. */\n userId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** Role scope. Use \"-\" when absent. */\n roleId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** Config type (category), e.g. endpoints, branding, display. */\n key: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL and for the Configuration resource. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, key, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). Tracks current version; S3 history key. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\", \"userId\", \"roleId\"],\n template:\n \"CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"key\", \"sk\"],\n template: \"KEY#${key}#SK#${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a\n * (tenant, workspace) across the four shards. Use for \"list configs scoped to this tenant\"\n * (workspaceId = \"-\") or \"list configs scoped to this workspace\". Does not support\n * hierarchical resolution in one query; use base table GetItem in fallback order\n * (user → workspace → tenant → baseline) for that.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"workspaceId\", \"gsi1Shard\"],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","/**\n * Shard selection for the data-plane single-table GSI1 partitioning per ADR-011.\n *\n * GSI1's partition key embeds a `SHARD#<n>` segment with `n = computeShard(id)`.\n * The hash is deterministic so updates to the same resource id always land on\n * the same shard (no cross-shard migration on update); reads fan out to all\n * shards in parallel and merge by SK.\n *\n * @see sites/www-docs/content/architecture/adr/ — ADR-011 (single-table DynamoDB)\n */\n\n/** Number of shards in the GSI1 partition key. Fixed at 4 in v1; raising it later is a backfill, not a schema migration. */\nexport const SHARD_COUNT = 4;\n\n/**\n * Returns a deterministic shard index in [0, SHARD_COUNT) for the given resource id.\n *\n * Implementation: 32-bit FNV-1a over the UTF-16 code units of the id, modulo SHARD_COUNT.\n * The function is pure and stable; the same id always returns the same shard.\n *\n * ESLint's `no-bitwise` rule is disabled inside this function because FNV-1a is\n * defined in terms of XOR and unsigned-right-shift — the bitwise ops are the\n * algorithm, not an accidental logical-operator confusion.\n */\nexport function computeShard(id: string): number {\n /* eslint-disable no-bitwise */\n let hash = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n hash ^= id.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) % SHARD_COUNT;\n /* eslint-enable no-bitwise */\n}\n","import { computeShard } from \"../../shard\";\n\n/**\n * Shared GSI1 shard attribute for control-plane entities.\n *\n * Control-plane entities (User, Tenant, Workspace, Membership, Role, RoleAssignment,\n * Configuration) use the same `TID#/WID#/RT#/SHARD#` PK shape on GSI1 as data-plane\n * FHIR resources per ADR-011. The shard index is derived deterministically from `id`\n * via `computeShard` so updates always land on the same shard. Stored as a string\n * because it appears as a literal segment in the GSI1 PK template; the underlying\n * value is 0..3.\n *\n * Not `required` because the value is derived via `watch`/`set`; ElectroDB's\n * required-field check runs before watch propagation, so callers must not fail\n * validation on a derived field.\n */\nexport const gsi1ShardAttribute = {\n type: \"string\" as const,\n watch: [\"id\"] as const,\n set: (_val?: string, item?: { id?: string }) => {\n if (typeof item?.id !== \"string\" || item.id.length === 0) {\n return undefined;\n }\n return String(computeShard(item.id));\n },\n};\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Membership data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Membership links a User\n * to a Tenant (and optionally a Workspace). One record per (tenantId, id).\n *\n * Key structure: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT.\n * Uniqueness: one Membership per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Memberships in a tenant across the four\n * shards. Membership is tenant-scoped (not workspace-scoped), so the GSI1 PK uses `WID#-` as a\n * sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const MembershipEntity = new Entity({\n model: {\n entity: \"membership\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant in which the user has membership (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; membership id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Membership resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#MEMBERSHIP#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the\n * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Role data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Non-tenant-isolated, control plane. Role is a\n * platform-wide role catalog (e.g. tenant-admin, tenant-user, system-admin); not scoped by tenant.\n * RoleAssignment references Role to assign a role to a User in a Tenant/Workspace context.\n *\n * Key structure: PK = ROLE#ID#<id>, SK = CURRENT.\n * The ROLE# prefix prevents key collisions with other non-tenant-isolated entities (User, etc.)\n * sharing the same table (ADR 2026-03-11-01 — preferred pattern for all control plane entities).\n * Uniqueness: one Role per id.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Roles across the four shards. Non-tenant-\n * isolated, so the PK uses `TID#-#WID#-` sentinels.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const RoleEntity = new Entity({\n model: {\n entity: \"role\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** FHIR Resource.id; role id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Role resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"id\"],\n template: \"ROLE#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.\n * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * RoleAssignment data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. RoleAssignment assigns\n * a Role to a User in a Tenant (and optionally Workspace) context.\n *\n * Key structure: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT.\n * Uniqueness: one RoleAssignment per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all RoleAssignments in a tenant across the four\n * shards. Tenant-scoped only (workspace context lives inside the resource), so the GSI1 PK uses\n * `WID#-` as a sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const RoleAssignmentEntity = new Entity({\n model: {\n entity: \"roleassignment\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant in which the role assignment applies (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; role assignment id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full RoleAssignment resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#ROLEASSIGNMENT#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the\n * four shards. Tenant-scoped only, so `WID#-` is a sentinel.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Tenant data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Tenant IS the top scope;\n * the workspace dimension is not applicable and uses the sentinel `TENANT`. The tenant's own `id`\n * is stored as `tenantId` to drive the partition key.\n *\n * Key structure: PK = TENANT#ID#<tenantId>, SK = CURRENT.\n * Uniqueness: one Tenant per tenantId (id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Tenants across the four shards. Tenant has\n * no parent tenant or workspace, so the PK uses `TID#-#WID#-` sentinels.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-01-tenant-and-workspace-fhir-types-control-plane.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const TenantEntity = new Entity({\n model: {\n entity: \"tenant\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** The tenant's own id (= resource id). Drives the partition key. */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL. Equals tenantId. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Tenant resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\"],\n template: \"TENANT#ID#${tenantId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.\n * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`\n * sentinels precede `RT#Tenant#SHARD#<n>`. SK is `<ISO-8601 lastUpdated>#<id>` (control-plane\n * unlabeled per DR-004). `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * User data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Non-tenant-isolated, control plane. User is a\n * platform-wide identity; association with tenants and workspaces is through Membership and\n * RoleAssignment, not the User entity's own key.\n *\n * Key structure: PK = USER#ID#<id>, SK = CURRENT.\n * The USER# prefix prevents key collisions with other non-tenant-isolated entities (Role, etc.)\n * sharing the same table (ADR 2026-03-11-01 — preferred pattern for all control plane entities).\n * Uniqueness: one User per id.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Users across the four shards. Non-tenant-\n * isolated, so the PK uses `TID#-#WID#-` sentinels.\n * GSI2 — Cognito sub-lookup per ADR-011: resolves a UserEntity from a Cognito `sub` claim\n * (`USER#SUB#<cognitoSub>` PK, `CURRENT` SK). The `cognitoSub` attribute is populated by the\n * Post Confirmation Lambda (Epic #765 / #770); kept optional here until that write path lands.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-11-01-user-type-definition-fhir-and-data-layer.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const UserEntity = new Entity({\n model: {\n entity: \"user\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** FHIR Resource.id; platform user id (ohi_uid). */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full User resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the\n * Post Confirmation Lambda (#770) lands; required thereafter.\n */\n cognitoSub: {\n type: \"string\" as const,\n required: false,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"id\"],\n template: \"USER#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.\n * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z` characters.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#User#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n\n /**\n * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.\n * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are\n * not indexed.\n */\n gsi2: {\n index: \"GSI2\",\n condition: (attrs: { cognitoSub?: string }) =>\n typeof attrs.cognitoSub === \"string\" && attrs.cognitoSub.length > 0,\n pk: {\n field: \"GSI2PK\",\n casing: \"none\" as const,\n composite: [\"cognitoSub\"],\n template: \"USER#SUB#${cognitoSub}\",\n },\n sk: {\n field: \"GSI2SK\",\n casing: \"none\" as const,\n composite: [],\n template: \"CURRENT\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Workspace data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Each workspace belongs\n * to exactly one tenant; both tenantId and workspace id are in the partition key.\n *\n * Key structure: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT.\n * Uniqueness: one Workspace per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Workspaces in a tenant across the four\n * shards. Workspace is itself the workspace identity, so the GSI1 PK uses `WID#-` as a sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-01-tenant-and-workspace-fhir-types-control-plane.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const WorkspaceEntity = new Entity({\n model: {\n entity: \"workspace\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant that contains this workspace (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Workspace resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#WORKSPACE#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the\n * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n"],"mappings":"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","names":["import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb"]}
@@ -0,0 +1,128 @@
1
+ import {
2
+ getDynamoControlService
3
+ } from "./chunk-MLTYFMSE.mjs";
4
+ import "./chunk-3QS3WKRC.mjs";
5
+
6
+ // src/components/cognito/post-confirmation.handler.ts
7
+ import { extractSummary } from "@openhi/types";
8
+ import { ulid } from "ulid";
9
+ function summaryFor(resource) {
10
+ return JSON.stringify(extractSummary(resource));
11
+ }
12
+ var handler = async (event, _context) => {
13
+ try {
14
+ const { sub, email } = event.request.userAttributes;
15
+ const displayName = email ?? event.userName ?? sub;
16
+ const userId = ulid();
17
+ const tenantId = ulid();
18
+ const workspaceId = ulid();
19
+ const userTenantMembershipId = ulid();
20
+ const userWorkspaceMembershipId = ulid();
21
+ const roleAssignmentId = ulid();
22
+ const lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
23
+ const vid = "1";
24
+ const service = getDynamoControlService();
25
+ const tenantResource = {
26
+ resourceType: "Tenant",
27
+ id: tenantId,
28
+ displayName: `${displayName}'s Practice`,
29
+ status: "active"
30
+ };
31
+ const workspaceResource = {
32
+ resourceType: "Workspace",
33
+ id: workspaceId,
34
+ displayName: "Default Workspace",
35
+ status: "active",
36
+ tenant: { reference: `Tenant/${tenantId}` }
37
+ };
38
+ const userResource = {
39
+ resourceType: "User",
40
+ id: userId,
41
+ displayName,
42
+ status: "active",
43
+ currentTenant: { reference: `Tenant/${tenantId}` },
44
+ currentWorkspace: { reference: `Workspace/${workspaceId}` }
45
+ };
46
+ const userTenantMembershipResource = {
47
+ resourceType: "Membership",
48
+ id: userTenantMembershipId,
49
+ status: "active",
50
+ user: { reference: `User/${userId}` },
51
+ tenant: { reference: `Tenant/${tenantId}` }
52
+ };
53
+ const userWorkspaceMembershipResource = {
54
+ resourceType: "Membership",
55
+ id: userWorkspaceMembershipId,
56
+ status: "active",
57
+ user: { reference: `User/${userId}` },
58
+ tenant: { reference: `Tenant/${tenantId}` },
59
+ workspace: { reference: `Workspace/${workspaceId}` }
60
+ };
61
+ const roleAssignmentResource = {
62
+ resourceType: "RoleAssignment",
63
+ id: roleAssignmentId,
64
+ status: "active",
65
+ user: { reference: `User/${userId}` },
66
+ tenant: { reference: `Tenant/${tenantId}` },
67
+ role: "tenant-user"
68
+ };
69
+ await service.entities.tenant.put({
70
+ tenantId,
71
+ id: tenantId,
72
+ resource: JSON.stringify(tenantResource),
73
+ summary: summaryFor(tenantResource),
74
+ vid,
75
+ lastUpdated
76
+ }).go();
77
+ await service.entities.workspace.put({
78
+ tenantId,
79
+ id: workspaceId,
80
+ resource: JSON.stringify(workspaceResource),
81
+ summary: summaryFor(workspaceResource),
82
+ vid,
83
+ lastUpdated
84
+ }).go();
85
+ await service.entities.user.put({
86
+ id: userId,
87
+ cognitoSub: sub,
88
+ resource: JSON.stringify(userResource),
89
+ summary: summaryFor(userResource),
90
+ vid,
91
+ lastUpdated
92
+ }).go();
93
+ await service.entities.membership.put({
94
+ tenantId,
95
+ id: userTenantMembershipId,
96
+ resource: JSON.stringify(userTenantMembershipResource),
97
+ summary: summaryFor(userTenantMembershipResource),
98
+ vid,
99
+ lastUpdated
100
+ }).go();
101
+ await service.entities.membership.put({
102
+ tenantId,
103
+ id: userWorkspaceMembershipId,
104
+ resource: JSON.stringify(userWorkspaceMembershipResource),
105
+ summary: summaryFor(userWorkspaceMembershipResource),
106
+ vid,
107
+ lastUpdated
108
+ }).go();
109
+ await service.entities.roleAssignment.put({
110
+ tenantId,
111
+ id: roleAssignmentId,
112
+ resource: JSON.stringify(roleAssignmentResource),
113
+ summary: summaryFor(roleAssignmentResource),
114
+ vid,
115
+ lastUpdated
116
+ }).go();
117
+ } catch (err) {
118
+ console.warn(
119
+ "PostConfirmation onboarding failed; returning event unchanged",
120
+ err
121
+ );
122
+ }
123
+ return event;
124
+ };
125
+ export {
126
+ handler
127
+ };
128
+ //# sourceMappingURL=post-confirmation.handler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/cognito/post-confirmation.handler.ts"],"sourcesContent":["import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport type {\n Context,\n PostConfirmationTriggerEvent,\n PostConfirmationTriggerHandler,\n} from \"aws-lambda\";\nimport { ulid } from \"ulid\";\nimport { getDynamoControlService } from \"../../data/dynamo/dynamo-control-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/post-confirmation-lambda.md\n *\n * Cognito Post Confirmation trigger.\n *\n * Implements the ADR 2026-03-17-01 invariant that every confirmed user\n * starts with their own default Tenant, default Workspace, Memberships,\n * and a `tenant-user` RoleAssignment, plus a User record carrying the\n * Cognito `sub` (drives the GSI2 sub-lookup added in epic #892) and\n * `currentTenant` / `currentWorkspace` references the Pre Token Generation\n * Lambda (#767) and Tenant Switcher (#769) read at runtime.\n *\n * Sign-up must not fail because of a DynamoDB error: any failure is logged\n * and the event is returned unchanged so Cognito completes confirmation.\n */\nfunction summaryFor(resource: Record<string, unknown>): string {\n return JSON.stringify(extractSummary(resource as FhirResourceLike));\n}\n\nexport const handler: PostConfirmationTriggerHandler = async (\n event: PostConfirmationTriggerEvent,\n _context: Context,\n): Promise<PostConfirmationTriggerEvent> => {\n try {\n const { sub, email } = event.request.userAttributes;\n const displayName = email ?? event.userName ?? sub;\n\n const userId = ulid();\n const tenantId = ulid();\n const workspaceId = ulid();\n const userTenantMembershipId = ulid();\n const userWorkspaceMembershipId = ulid();\n const roleAssignmentId = ulid();\n\n const lastUpdated = new Date().toISOString();\n const vid = \"1\";\n\n const service = getDynamoControlService();\n\n const tenantResource = {\n resourceType: \"Tenant\",\n id: tenantId,\n displayName: `${displayName}'s Practice`,\n status: \"active\",\n };\n const workspaceResource = {\n resourceType: \"Workspace\",\n id: workspaceId,\n displayName: \"Default Workspace\",\n status: \"active\",\n tenant: { reference: `Tenant/${tenantId}` },\n };\n const userResource = {\n resourceType: \"User\",\n id: userId,\n displayName,\n status: \"active\",\n currentTenant: { reference: `Tenant/${tenantId}` },\n currentWorkspace: { reference: `Workspace/${workspaceId}` },\n };\n const userTenantMembershipResource = {\n resourceType: \"Membership\",\n id: userTenantMembershipId,\n status: \"active\",\n user: { reference: `User/${userId}` },\n tenant: { reference: `Tenant/${tenantId}` },\n };\n const userWorkspaceMembershipResource = {\n resourceType: \"Membership\",\n id: userWorkspaceMembershipId,\n status: \"active\",\n user: { reference: `User/${userId}` },\n tenant: { reference: `Tenant/${tenantId}` },\n workspace: { reference: `Workspace/${workspaceId}` },\n };\n const roleAssignmentResource = {\n resourceType: \"RoleAssignment\",\n id: roleAssignmentId,\n status: \"active\",\n user: { reference: `User/${userId}` },\n tenant: { reference: `Tenant/${tenantId}` },\n role: \"tenant-user\",\n };\n\n await service.entities.tenant\n .put({\n tenantId,\n id: tenantId,\n resource: JSON.stringify(tenantResource),\n summary: summaryFor(tenantResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.workspace\n .put({\n tenantId,\n id: workspaceId,\n resource: JSON.stringify(workspaceResource),\n summary: summaryFor(workspaceResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.user\n .put({\n id: userId,\n cognitoSub: sub,\n resource: JSON.stringify(userResource),\n summary: summaryFor(userResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.membership\n .put({\n tenantId,\n id: userTenantMembershipId,\n resource: JSON.stringify(userTenantMembershipResource),\n summary: summaryFor(userTenantMembershipResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.membership\n .put({\n tenantId,\n id: userWorkspaceMembershipId,\n resource: JSON.stringify(userWorkspaceMembershipResource),\n summary: summaryFor(userWorkspaceMembershipResource),\n vid,\n lastUpdated,\n })\n .go();\n\n await service.entities.roleAssignment\n .put({\n tenantId,\n id: roleAssignmentId,\n resource: JSON.stringify(roleAssignmentResource),\n summary: summaryFor(roleAssignmentResource),\n vid,\n lastUpdated,\n })\n .go();\n } catch (err) {\n console.warn(\n \"PostConfirmation onboarding failed; returning event unchanged\",\n err,\n );\n }\n return event;\n};\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import "./chunk-LZOMFHX3.mjs";
1
+ import "./chunk-3QS3WKRC.mjs";
2
2
 
3
3
  // src/components/cognito/pre-token-generation.handler.ts
4
4
  var OPENHI_CLAIMS = {
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(rest_api_lambda_handler_exports);
36
36
  var import_serverless_express2 = __toESM(require("@codegenie/serverless-express"));
37
37
 
38
38
  // src/data/rest-api/rest-api.ts
39
- var import_node_path2 = __toESM(require("path"));
39
+ var import_node_path = __toESM(require("path"));
40
40
  var import_express150 = __toESM(require("express"));
41
41
 
42
42
  // src/data/middleware/normalize-json-body.ts
@@ -1488,7 +1488,7 @@ async function batchGetWithRetry(entity, keys) {
1488
1488
  while (pending.length > 0) {
1489
1489
  if (attempt > 0) {
1490
1490
  await new Promise(
1491
- (resolve2) => setTimeout(resolve2, BATCH_GET_BASE_BACKOFF_MS * 2 ** (attempt - 1))
1491
+ (resolve) => setTimeout(resolve, BATCH_GET_BASE_BACKOFF_MS * 2 ** (attempt - 1))
1492
1492
  );
1493
1493
  }
1494
1494
  attempt++;
@@ -24928,133 +24928,8 @@ var import_express106 = __toESM(require("express"));
24928
24928
 
24929
24929
  // src/data/operations/data/patient/patient-create-operation.ts
24930
24930
  var import_ulid99 = require("ulid");
24931
-
24932
- // src/data/import-patient.ts
24933
- var import_node_fs = require("fs");
24934
- var import_node_path = require("path");
24935
- var import_types14 = require("@openhi/types");
24936
- function extractPatient(parsed) {
24937
- if (parsed && typeof parsed === "object" && "resourceType" in parsed) {
24938
- const root = parsed;
24939
- if (root.resourceType === "Patient" && root.id) {
24940
- return root;
24941
- }
24942
- if (root.resourceType === "Bundle" && "entry" in parsed) {
24943
- const entries = parsed.entry;
24944
- if (Array.isArray(entries)) {
24945
- const patientEntry = entries.find(
24946
- (e) => e?.resource?.resourceType === "Patient" && e.resource.id
24947
- );
24948
- if (patientEntry?.resource) {
24949
- return patientEntry.resource;
24950
- }
24951
- }
24952
- }
24953
- }
24954
- throw new Error(
24955
- "File must be a FHIR Patient resource or a Bundle containing at least one Patient entry"
24956
- );
24957
- }
24958
- var SK12 = "CURRENT";
24959
- var defaultAudit = {
24960
- createdDate: (/* @__PURE__ */ new Date()).toISOString(),
24961
- createdById: "import",
24962
- createdByName: "Bulk import",
24963
- modifiedDate: (/* @__PURE__ */ new Date()).toISOString(),
24964
- modifiedById: "import",
24965
- modifiedByName: "Bulk import"
24966
- };
24967
- function patientToPutAttrs(patient, options) {
24968
- const {
24969
- tenantId,
24970
- workspaceId,
24971
- createdDate,
24972
- createdById,
24973
- createdByName,
24974
- modifiedDate,
24975
- modifiedById,
24976
- modifiedByName
24977
- } = options;
24978
- const lastUpdated = patient.meta?.lastUpdated ?? modifiedDate ?? defaultAudit.modifiedDate ?? (/* @__PURE__ */ new Date()).toISOString();
24979
- const auditMerged = {
24980
- ...defaultAudit,
24981
- ...createdDate != null && { createdDate },
24982
- ...createdById != null && { createdById },
24983
- ...createdByName != null && { createdByName },
24984
- ...modifiedDate != null && { modifiedDate },
24985
- ...modifiedById != null && { modifiedById },
24986
- ...modifiedByName != null && { modifiedByName }
24987
- };
24988
- const patientWithMeta = {
24989
- ...patient,
24990
- meta: mergeAuditIntoMeta(patient.meta, auditMerged)
24991
- };
24992
- if (lastUpdated && !patientWithMeta.meta.lastUpdated) {
24993
- patientWithMeta.meta.lastUpdated = lastUpdated;
24994
- }
24995
- return {
24996
- sk: SK12,
24997
- tenantId,
24998
- workspaceId,
24999
- id: patient.id,
25000
- resource: compressResource(JSON.stringify(patientWithMeta)),
25001
- summary: JSON.stringify(
25002
- (0, import_types14.extractSummary)(patientWithMeta)
25003
- ),
25004
- vid: lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36),
25005
- lastUpdated,
25006
- identifierSystem: "",
25007
- identifierValue: "",
25008
- facilityId: "",
25009
- normalizedName: ""
25010
- };
25011
- }
25012
- async function importPatientFromFile(filePath, options) {
25013
- const resolved = (0, import_node_path.resolve)(filePath);
25014
- const raw = (0, import_node_fs.readFileSync)(resolved, "utf-8");
25015
- const parsed = JSON.parse(raw);
25016
- const patient = extractPatient(parsed);
25017
- const service = getDynamoDataService(options.tableName);
25018
- const attrs = patientToPutAttrs(patient, options);
25019
- const result = await service.entities.patient.put(attrs).go();
25020
- const data = result.data;
25021
- if (!data) {
25022
- throw new Error(`Put failed for Patient ${patient.id}`);
25023
- }
25024
- return {
25025
- id: data.id,
25026
- tenantId: data.tenantId,
25027
- workspaceId: data.workspaceId
25028
- };
25029
- }
25030
- async function main() {
25031
- const [, , fileArg, tenantId = "tenant-1", workspaceId = "ws-1"] = process.argv;
25032
- if (!fileArg) {
25033
- console.error(
25034
- "Usage: import-patient.ts <path-to-patient.json> [tenantId] [workspaceId]"
25035
- );
25036
- process.exit(1);
25037
- }
25038
- try {
25039
- const result = await importPatientFromFile(fileArg, {
25040
- tenantId,
25041
- workspaceId
25042
- });
25043
- console.log(
25044
- `Imported Patient ${result.id} (tenant=${result.tenantId}, workspace=${result.workspaceId})`
25045
- );
25046
- } catch (err) {
25047
- console.error(err);
25048
- process.exit(1);
25049
- }
25050
- }
25051
- if (require.main === module) {
25052
- void main();
25053
- }
25054
-
25055
- // src/data/operations/data/patient/patient-create-operation.ts
25056
24931
  async function createPatientOperation(params) {
25057
- const { context, body } = params;
24932
+ const { context, body, tableName } = params;
25058
24933
  const { tenantId, workspaceId, date, actorId, actorName } = context;
25059
24934
  const id = body.id ?? (0, import_ulid99.ulid)();
25060
24935
  const meta = {
@@ -25062,29 +24937,28 @@ async function createPatientOperation(params) {
25062
24937
  lastUpdated: date,
25063
24938
  versionId: "1"
25064
24939
  };
25065
- const patient = {
24940
+ const patientWithAudit = {
25066
24941
  ...body,
25067
24942
  resourceType: "Patient",
25068
24943
  id,
25069
- meta
24944
+ meta: mergeAuditIntoMeta(meta, {
24945
+ createdDate: date,
24946
+ createdById: actorId,
24947
+ createdByName: actorName,
24948
+ modifiedDate: date,
24949
+ modifiedById: actorId,
24950
+ modifiedByName: actorName
24951
+ })
25070
24952
  };
25071
- const options = {
24953
+ const service = getDynamoDataService(tableName);
24954
+ return createDataEntityRecord(
24955
+ service.entities.patient,
25072
24956
  tenantId,
25073
24957
  workspaceId,
25074
- createdDate: date,
25075
- createdById: actorId,
25076
- createdByName: actorName,
25077
- modifiedDate: date,
25078
- modifiedById: actorId,
25079
- modifiedByName: actorName
25080
- };
25081
- const service = getDynamoDataService(params.tableName);
25082
- const attrs = patientToPutAttrs(patient, options);
25083
- await service.entities.patient.put(attrs).go();
25084
- return {
25085
24958
  id,
25086
- resource: patient
25087
- };
24959
+ patientWithAudit,
24960
+ date
24961
+ );
25088
24962
  }
25089
24963
 
25090
24964
  // src/data/rest-api/routes/data/patient/patient-create-route.ts
@@ -34055,7 +33929,7 @@ router149.delete("/:id", deleteVisionPrescriptionRoute);
34055
33929
  // src/data/rest-api/rest-api.ts
34056
33930
  var app = (0, import_express150.default)();
34057
33931
  app.set("view engine", "ejs");
34058
- app.set("views", import_node_path2.default.join(__dirname, "views"));
33932
+ app.set("views", import_node_path.default.join(__dirname, "views"));
34059
33933
  app.use(import_express150.default.json());
34060
33934
  app.use(import_express150.default.urlencoded({ extended: true }));
34061
33935
  app.use(normalizeJsonBodyMiddleware);