@openhi/constructs 0.0.150 → 0.0.152

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.
Files changed (39) hide show
  1. package/lib/{chunk-AWYZJFPL.mjs → chunk-CFJDATDK.mjs} +8 -1
  2. package/lib/chunk-CFJDATDK.mjs.map +1 -0
  3. package/lib/{chunk-I7IIPV5X.mjs → chunk-KR2Y2CVQ.mjs} +10 -4
  4. package/lib/chunk-KR2Y2CVQ.mjs.map +1 -0
  5. package/lib/{chunk-WGA43MMY.mjs → chunk-SXYY5WHG.mjs} +194 -71
  6. package/lib/chunk-SXYY5WHG.mjs.map +1 -0
  7. package/lib/{chunk-CEOAGPYY.mjs → chunk-ZXPA6W3G.mjs} +1 -3
  8. package/lib/chunk-ZXPA6W3G.mjs.map +1 -0
  9. package/lib/data-store-postgres-replication.handler.js +687 -0
  10. package/lib/data-store-postgres-replication.handler.js.map +1 -1
  11. package/lib/data-store-postgres-replication.handler.mjs +3 -2
  12. package/lib/data-store-postgres-replication.handler.mjs.map +1 -1
  13. package/lib/{events-CMG8xanm.d.ts → events-DTgo2dcW.d.mts} +2 -14
  14. package/lib/{events-CMG8xanm.d.mts → events-DTgo2dcW.d.ts} +2 -14
  15. package/lib/firehose-archive-transform.handler.js +688 -2
  16. package/lib/firehose-archive-transform.handler.js.map +1 -1
  17. package/lib/firehose-archive-transform.handler.mjs +3 -2
  18. package/lib/index.d.mts +62 -8
  19. package/lib/index.d.ts +62 -20
  20. package/lib/index.js +53 -22
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +36 -7
  23. package/lib/index.mjs.map +1 -1
  24. package/lib/provision-default-workspace.handler.js +6 -0
  25. package/lib/provision-default-workspace.handler.js.map +1 -1
  26. package/lib/provision-default-workspace.handler.mjs +1 -1
  27. package/lib/rest-api-lambda.handler.js +6 -0
  28. package/lib/rest-api-lambda.handler.js.map +1 -1
  29. package/lib/rest-api-lambda.handler.mjs +1 -1
  30. package/lib/seed-demo-data.handler.d.mts +14 -1
  31. package/lib/seed-demo-data.handler.d.ts +14 -1
  32. package/lib/seed-demo-data.handler.js +199 -68
  33. package/lib/seed-demo-data.handler.js.map +1 -1
  34. package/lib/seed-demo-data.handler.mjs +2 -2
  35. package/package.json +5 -5
  36. package/lib/chunk-AWYZJFPL.mjs.map +0 -1
  37. package/lib/chunk-CEOAGPYY.mjs.map +0 -1
  38. package/lib/chunk-I7IIPV5X.mjs.map +0 -1
  39. package/lib/chunk-WGA43MMY.mjs.map +0 -1
@@ -23,7 +23,7 @@ import {
23
23
  createTenantOperation,
24
24
  createWorkspaceOperation,
25
25
  extractDenormalizedReferenceDisplay
26
- } from "./chunk-AWYZJFPL.mjs";
26
+ } from "./chunk-CFJDATDK.mjs";
27
27
  import {
28
28
  buildMembershipUserProjectionItem,
29
29
  buildMembershipWorkspaceProjectionItem,
@@ -1,6 +1,6 @@
1
1
  import { WorkflowDedupClient } from '@openhi/workflows';
2
2
  import { EventBridgeEvent } from 'aws-lambda';
3
- import { d as DemoDevUser } from './events-CMG8xanm.mjs';
3
+ import { d as DemoDevUser } from './events-DTgo2dcW.mjs';
4
4
  import { O as OpenHiContext } from './openhi-context-CaBH8SFo.mjs';
5
5
  import '@openhi/types';
6
6
 
@@ -68,6 +68,19 @@ interface SeedDemoDataDependencies {
68
68
  * put is keyed by a deterministic stable id so re-runs after
69
69
  * dedup-TTL expiry upsert the same records.
70
70
  *
71
+ * Self-healing-on-every-deploy contract: every individual upsert
72
+ * across all three phases is wrapped via {@link tryRun}. A single
73
+ * item's failure (transient AWS error, write-time constraint
74
+ * violation, etc.) is collected into a per-call accumulator and
75
+ * does not skip the remaining items in its phase. After all phases
76
+ * run, collected failures are aggregate-thrown as a single error so
77
+ * EventBridge still routes the workflow into its failure-detection
78
+ * path (DLQ + CloudWatch alarm) and the outer `runSeedDemoData`
79
+ * records `markFailed` on the dedup row. Because every put is keyed
80
+ * by a deterministic stable id, the next deploy re-attempts every
81
+ * item — failed items eventually heal, successful items overwrite
82
+ * themselves with the same body.
83
+ *
71
84
  * Exported so the seeder test file can exercise it directly against
72
85
  * a mocked DynamoControlService; the production handler reaches it
73
86
  * through {@link SeedDemoDataDependencies.seedDemoGraph}.
@@ -1,6 +1,6 @@
1
1
  import { WorkflowDedupClient } from '@openhi/workflows';
2
2
  import { EventBridgeEvent } from 'aws-lambda';
3
- import { d as DemoDevUser } from './events-CMG8xanm.js';
3
+ import { d as DemoDevUser } from './events-DTgo2dcW.js';
4
4
  import { O as OpenHiContext } from './openhi-context-CaBH8SFo.js';
5
5
  import '@openhi/types';
6
6
 
@@ -68,6 +68,19 @@ interface SeedDemoDataDependencies {
68
68
  * put is keyed by a deterministic stable id so re-runs after
69
69
  * dedup-TTL expiry upsert the same records.
70
70
  *
71
+ * Self-healing-on-every-deploy contract: every individual upsert
72
+ * across all three phases is wrapped via {@link tryRun}. A single
73
+ * item's failure (transient AWS error, write-time constraint
74
+ * violation, etc.) is collected into a per-call accumulator and
75
+ * does not skip the remaining items in its phase. After all phases
76
+ * run, collected failures are aggregate-thrown as a single error so
77
+ * EventBridge still routes the workflow into its failure-detection
78
+ * path (DLQ + CloudWatch alarm) and the outer `runSeedDemoData`
79
+ * records `markFailed` on the dedup row. Because every put is keyed
80
+ * by a deterministic stable id, the next deploy re-attempts every
81
+ * item — failed items eventually heal, successful items overwrite
82
+ * themselves with the same body.
83
+ *
71
84
  * Exported so the seeder test file can exercise it directly against
72
85
  * a mocked DynamoControlService; the production handler reaches it
73
86
  * through {@link SeedDemoDataDependencies.seedDemoGraph}.
@@ -723,11 +723,15 @@ var import_workflows2 = __toESM(require_lib());
723
723
  // src/workflows/control-plane/seed-demo-data/events.ts
724
724
  var import_types = require("@openhi/types");
725
725
  var import_workflows = __toESM(require_lib());
726
+
727
+ // src/data/operations/control/membership-constraints/platform-scope-tenant-id.ts
728
+ var PLATFORM_SCOPE_TENANT_ID = "platform";
729
+
730
+ // src/workflows/control-plane/seed-demo-data/events.ts
726
731
  var SEED_DEMO_DATA_CONSUMER_NAME = "seed-demo-data";
727
732
  var DEMO_URN_SYSTEM = "urn:openhi:demo";
728
733
  var OPENHI_RESOURCE_URN_SYSTEM = "http://openhi.org/";
729
734
  var DEMO_PERIOD = { start: "2026-01-01T00:00:00Z" };
730
- var PLATFORM_SCOPE_TENANT_ID = "platform";
731
735
  var PLACEHOLDER_TENANT_ID = "placeholder-tenant-id";
732
736
  var PLACEHOLDER_WORKSPACE_ID = "placeholder-workspace-id";
733
737
  var DEV_USERS = [
@@ -3510,6 +3514,9 @@ function buildRoleAssignmentWorkspaceProjectionItem(input) {
3510
3514
  var TENANT_LANE_SK_PREFIX = "MEMBERSHIP#TENANT#";
3511
3515
  async function assertUserHasTenantMembershipOperation(params) {
3512
3516
  const { userId, tenantId, tableName } = params;
3517
+ if (tenantId === PLATFORM_SCOPE_TENANT_ID) {
3518
+ return;
3519
+ }
3513
3520
  const service = getDynamoControlService(tableName);
3514
3521
  const result = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: TENANT_LANE_SK_PREFIX }).go();
3515
3522
  const matched = (result.data ?? []).some((row) => row.tenantId === tenantId);
@@ -5017,6 +5024,25 @@ var errorMessage = (err) => {
5017
5024
  }
5018
5025
  return String(err);
5019
5026
  };
5027
+ var tryRun = async (failures, phase, scope, resourceType, resourceId, fn) => {
5028
+ try {
5029
+ await fn();
5030
+ return true;
5031
+ } catch (err) {
5032
+ failures.push({ phase, scope, resourceType, resourceId, error: err });
5033
+ return false;
5034
+ }
5035
+ };
5036
+ var aggregateFailureError = (failures) => {
5037
+ const summary = failures.map(
5038
+ (f) => `${f.phase} ${f.scope}/${f.resourceType}/${f.resourceId}: ${errorMessage(
5039
+ f.error
5040
+ )}`
5041
+ ).join("; ");
5042
+ return new Error(
5043
+ `seed-demo-data: ${failures.length} item(s) failed across phases: ${summary}`
5044
+ );
5045
+ };
5020
5046
  var idForRoleCode = (code) => {
5021
5047
  for (const key of Object.keys(import_types12.PLATFORM_ROLE_IDS)) {
5022
5048
  if (import_types12.PLATFORM_ROLE_CONCEPTS[key].code === code) {
@@ -5126,95 +5152,180 @@ var upsertUser = async (context, user, cognitoSub) => {
5126
5152
  lastUpdated: context.date ?? (/* @__PURE__ */ new Date()).toISOString()
5127
5153
  }).go();
5128
5154
  };
5129
- var seedWorkspaceDataPlane = async (baseContext, group) => {
5155
+ var seedWorkspaceDataPlane = async (baseContext, group, failures) => {
5130
5156
  const workspaceContext = {
5131
5157
  ...baseContext,
5132
5158
  tenantId: group.tenantId,
5133
5159
  workspaceId: group.workspaceId
5134
5160
  };
5161
+ const scope = `${group.tenantId}/${group.workspaceId}`;
5135
5162
  for (const patient of group.patients) {
5136
- await createPatientOperation({
5137
- context: workspaceContext,
5138
- body: patient
5139
- });
5163
+ await tryRun(
5164
+ failures,
5165
+ "phase-3",
5166
+ scope,
5167
+ "Patient",
5168
+ patient.id ?? "",
5169
+ () => createPatientOperation({
5170
+ context: workspaceContext,
5171
+ body: patient
5172
+ })
5173
+ );
5140
5174
  }
5141
5175
  for (const practitioner of group.practitioners) {
5142
- await createPractitionerOperation({
5143
- context: workspaceContext,
5144
- body: practitioner
5145
- });
5176
+ await tryRun(
5177
+ failures,
5178
+ "phase-3",
5179
+ scope,
5180
+ "Practitioner",
5181
+ practitioner.id ?? "",
5182
+ () => createPractitionerOperation({
5183
+ context: workspaceContext,
5184
+ body: practitioner
5185
+ })
5186
+ );
5146
5187
  }
5147
5188
  for (const observation of group.observations) {
5148
- await createObservationOperation({
5149
- context: workspaceContext,
5150
- body: observation
5151
- });
5189
+ await tryRun(
5190
+ failures,
5191
+ "phase-3",
5192
+ scope,
5193
+ "Observation",
5194
+ observation.id ?? "",
5195
+ () => createObservationOperation({
5196
+ context: workspaceContext,
5197
+ body: observation
5198
+ })
5199
+ );
5152
5200
  }
5153
5201
  for (const encounter of group.encounters) {
5154
- await createEncounterOperation({
5155
- context: workspaceContext,
5156
- body: encounter
5157
- });
5202
+ await tryRun(
5203
+ failures,
5204
+ "phase-3",
5205
+ scope,
5206
+ "Encounter",
5207
+ encounter.id ?? "",
5208
+ () => createEncounterOperation({
5209
+ context: workspaceContext,
5210
+ body: encounter
5211
+ })
5212
+ );
5158
5213
  }
5159
5214
  for (const account of group.accounts) {
5160
- await createAccountOperation({
5161
- context: workspaceContext,
5162
- body: account
5163
- });
5215
+ await tryRun(
5216
+ failures,
5217
+ "phase-3",
5218
+ scope,
5219
+ "Account",
5220
+ account.id ?? "",
5221
+ () => createAccountOperation({
5222
+ context: workspaceContext,
5223
+ body: account
5224
+ })
5225
+ );
5164
5226
  }
5165
5227
  };
5166
5228
  var seedDemoGraph = async (params) => {
5167
5229
  const { baseContext, devUsers, cognito } = params;
5230
+ const failures = [];
5168
5231
  for (const spec of DEMO_TENANT_SPECS) {
5169
5232
  const tenantContext = {
5170
5233
  ...baseContext,
5171
5234
  tenantId: spec.tenantId
5172
5235
  };
5173
- await createTenantOperation({
5174
- context: tenantContext,
5175
- body: { id: spec.tenantId, resource: tenantResourceBody(spec) }
5176
- });
5177
- for (const workspace of spec.workspaces) {
5178
- await createWorkspaceOperation({
5236
+ await tryRun(
5237
+ failures,
5238
+ "phase-1",
5239
+ spec.tenantId,
5240
+ "Tenant",
5241
+ spec.tenantId,
5242
+ () => createTenantOperation({
5179
5243
  context: tenantContext,
5180
- body: {
5181
- id: workspace.id,
5182
- resource: workspaceResourceBody(spec, workspace)
5183
- }
5184
- });
5244
+ body: { id: spec.tenantId, resource: tenantResourceBody(spec) }
5245
+ })
5246
+ );
5247
+ for (const workspace of spec.workspaces) {
5248
+ await tryRun(
5249
+ failures,
5250
+ "phase-1",
5251
+ spec.tenantId,
5252
+ "Workspace",
5253
+ workspace.id,
5254
+ () => createWorkspaceOperation({
5255
+ context: tenantContext,
5256
+ body: {
5257
+ id: workspace.id,
5258
+ resource: workspaceResourceBody(spec, workspace)
5259
+ }
5260
+ })
5261
+ );
5185
5262
  }
5186
5263
  }
5187
5264
  for (const user of devUsers) {
5188
- const cognitoSub = await cognito.ensureUser(user.email);
5189
- await upsertUser(baseContext, user, cognitoSub);
5265
+ let cognitoSub;
5266
+ try {
5267
+ cognitoSub = await cognito.ensureUser(user.email);
5268
+ } catch (err) {
5269
+ failures.push({
5270
+ phase: "phase-2",
5271
+ scope: user.id,
5272
+ resourceType: "CognitoUser",
5273
+ resourceId: user.email,
5274
+ error: err
5275
+ });
5276
+ continue;
5277
+ }
5278
+ await tryRun(
5279
+ failures,
5280
+ "phase-2",
5281
+ user.id,
5282
+ "User",
5283
+ user.id,
5284
+ () => upsertUser(baseContext, user, cognitoSub)
5285
+ );
5190
5286
  for (const spec of DEMO_TENANT_SPECS) {
5191
5287
  const tenantContext = {
5192
5288
  ...baseContext,
5193
5289
  tenantId: spec.tenantId
5194
5290
  };
5291
+ const userScope = `${user.id}@${spec.tenantId}`;
5195
5292
  const membershipId = demoMembershipId(user.id, spec.tenantId);
5196
- await createMembershipOperation({
5197
- context: tenantContext,
5198
- body: {
5199
- id: membershipId,
5200
- resource: membershipResourceBody(spec, user, membershipId)
5201
- }
5202
- });
5203
- for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {
5204
- const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);
5205
- await createRoleAssignmentOperation({
5293
+ await tryRun(
5294
+ failures,
5295
+ "phase-2",
5296
+ userScope,
5297
+ "Membership",
5298
+ membershipId,
5299
+ () => createMembershipOperation({
5206
5300
  context: tenantContext,
5207
5301
  body: {
5208
- id: raId,
5209
- resource: roleAssignmentResourceBody(
5210
- spec.scenario,
5211
- spec.tenantId,
5212
- user,
5213
- roleCode,
5214
- raId
5215
- )
5302
+ id: membershipId,
5303
+ resource: membershipResourceBody(spec, user, membershipId)
5216
5304
  }
5217
- });
5305
+ })
5306
+ );
5307
+ for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {
5308
+ const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);
5309
+ await tryRun(
5310
+ failures,
5311
+ "phase-2",
5312
+ userScope,
5313
+ "RoleAssignment",
5314
+ raId,
5315
+ () => createRoleAssignmentOperation({
5316
+ context: tenantContext,
5317
+ body: {
5318
+ id: raId,
5319
+ resource: roleAssignmentResourceBody(
5320
+ spec.scenario,
5321
+ spec.tenantId,
5322
+ user,
5323
+ roleCode,
5324
+ raId
5325
+ )
5326
+ }
5327
+ })
5328
+ );
5218
5329
  }
5219
5330
  }
5220
5331
  const platformContext = {
@@ -5227,22 +5338,42 @@ var seedDemoGraph = async (params) => {
5227
5338
  PLATFORM_SCOPE_TENANT_ID,
5228
5339
  platformRoleCode
5229
5340
  );
5230
- await createRoleAssignmentOperation({
5231
- context: platformContext,
5232
- body: {
5233
- id: platformRaId,
5234
- resource: roleAssignmentResourceBody(
5235
- "platform",
5236
- PLATFORM_SCOPE_TENANT_ID,
5237
- user,
5238
- platformRoleCode,
5239
- platformRaId
5240
- )
5241
- }
5242
- });
5341
+ await tryRun(
5342
+ failures,
5343
+ "phase-2",
5344
+ `${user.id}@${PLATFORM_SCOPE_TENANT_ID}`,
5345
+ "RoleAssignment",
5346
+ platformRaId,
5347
+ () => createRoleAssignmentOperation({
5348
+ context: platformContext,
5349
+ body: {
5350
+ id: platformRaId,
5351
+ resource: roleAssignmentResourceBody(
5352
+ "platform",
5353
+ PLATFORM_SCOPE_TENANT_ID,
5354
+ user,
5355
+ platformRoleCode,
5356
+ platformRaId
5357
+ )
5358
+ }
5359
+ })
5360
+ );
5243
5361
  }
5244
5362
  for (const group of DEMO_DATA_PLANE_FIXTURES) {
5245
- await seedWorkspaceDataPlane(baseContext, group);
5363
+ try {
5364
+ await seedWorkspaceDataPlane(baseContext, group, failures);
5365
+ } catch (err) {
5366
+ failures.push({
5367
+ phase: "phase-3",
5368
+ scope: `${group.tenantId}/${group.workspaceId}`,
5369
+ resourceType: "Workspace",
5370
+ resourceId: group.workspaceId,
5371
+ error: err
5372
+ });
5373
+ }
5374
+ }
5375
+ if (failures.length > 0) {
5376
+ throw aggregateFailureError(failures);
5246
5377
  }
5247
5378
  };
5248
5379
  var runSeedDemoData = async (event, deps, devUsers) => {