@openhi/constructs 0.0.106 → 0.0.108

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 (54) hide show
  1. package/lib/{chunk-CHPEQRXU.mjs → chunk-6J7NQ6A4.mjs} +2 -2
  2. package/lib/{chunk-YU2HRNUP.mjs → chunk-AJ3G3THO.mjs} +2 -2
  3. package/lib/{chunk-L6UAP4KP.mjs → chunk-EST32BJ2.mjs} +3 -3
  4. package/lib/chunk-GT7SFZLP.mjs +126 -0
  5. package/lib/chunk-GT7SFZLP.mjs.map +1 -0
  6. package/lib/{chunk-QMIOLLAS.mjs → chunk-L3QHWDHB.mjs} +16 -18
  7. package/lib/chunk-L3QHWDHB.mjs.map +1 -0
  8. package/lib/chunk-LKG3I536.mjs +1383 -0
  9. package/lib/chunk-LKG3I536.mjs.map +1 -0
  10. package/lib/{chunk-VYDIGFIX.mjs → chunk-QR5JVSCF.mjs} +11 -1
  11. package/lib/chunk-QR5JVSCF.mjs.map +1 -0
  12. package/lib/{chunk-AO3E22CS.mjs → chunk-WWGJZNXJ.mjs} +74 -4
  13. package/lib/chunk-WWGJZNXJ.mjs.map +1 -0
  14. package/lib/chunk-ZGOHB4RA.mjs +96 -0
  15. package/lib/chunk-ZGOHB4RA.mjs.map +1 -0
  16. package/lib/{events-DGep6C7w.d.mts → events-DPodvl07.d.mts} +3 -3
  17. package/lib/{events-DGep6C7w.d.ts → events-DPodvl07.d.ts} +3 -3
  18. package/lib/index.d.mts +2 -2
  19. package/lib/index.d.ts +4 -4
  20. package/lib/index.js +1045 -9
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +9 -11
  23. package/lib/index.mjs.map +1 -1
  24. package/lib/pre-token-generation.handler.js +1356 -10
  25. package/lib/pre-token-generation.handler.js.map +1 -1
  26. package/lib/pre-token-generation.handler.mjs +103 -5
  27. package/lib/pre-token-generation.handler.mjs.map +1 -1
  28. package/lib/provision-default-workspace.handler.js +1172 -1
  29. package/lib/provision-default-workspace.handler.js.map +1 -1
  30. package/lib/provision-default-workspace.handler.mjs +5 -5
  31. package/lib/rest-api-lambda.handler.js +2769 -2434
  32. package/lib/rest-api-lambda.handler.js.map +1 -1
  33. package/lib/rest-api-lambda.handler.mjs +630 -1464
  34. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  35. package/lib/seed-demo-data.handler.d.mts +2 -2
  36. package/lib/seed-demo-data.handler.d.ts +2 -2
  37. package/lib/seed-demo-data.handler.js +1174 -10
  38. package/lib/seed-demo-data.handler.js.map +1 -1
  39. package/lib/seed-demo-data.handler.mjs +5 -5
  40. package/lib/seed-system-data.handler.js +15 -5
  41. package/lib/seed-system-data.handler.js.map +1 -1
  42. package/lib/seed-system-data.handler.mjs +9 -9
  43. package/lib/seed-system-data.handler.mjs.map +1 -1
  44. package/package.json +1 -1
  45. package/lib/chunk-AO3E22CS.mjs.map +0 -1
  46. package/lib/chunk-JUNL76HF.mjs +0 -428
  47. package/lib/chunk-JUNL76HF.mjs.map +0 -1
  48. package/lib/chunk-QMIOLLAS.mjs.map +0 -1
  49. package/lib/chunk-VYDIGFIX.mjs.map +0 -1
  50. package/lib/chunk-YZZDUJHI.mjs +0 -37
  51. package/lib/chunk-YZZDUJHI.mjs.map +0 -1
  52. /package/lib/{chunk-CHPEQRXU.mjs.map → chunk-6J7NQ6A4.mjs.map} +0 -0
  53. /package/lib/{chunk-YU2HRNUP.mjs.map → chunk-AJ3G3THO.mjs.map} +0 -0
  54. /package/lib/{chunk-L6UAP4KP.mjs.map → chunk-EST32BJ2.mjs.map} +0 -0
@@ -6,11 +6,11 @@ import {
6
6
  } from "./chunk-AGF3RAAZ.mjs";
7
7
  import {
8
8
  createRoleOperation
9
- } from "./chunk-YU2HRNUP.mjs";
9
+ } from "./chunk-AJ3G3THO.mjs";
10
10
  import {
11
11
  require_lib
12
12
  } from "./chunk-SYBADQXI.mjs";
13
- import "./chunk-VYDIGFIX.mjs";
13
+ import "./chunk-QR5JVSCF.mjs";
14
14
  import {
15
15
  __toESM
16
16
  } from "./chunk-LZOMFHX3.mjs";
@@ -20,8 +20,8 @@ var import_workflows2 = __toESM(require_lib());
20
20
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
21
21
  import { EventBridgeClient } from "@aws-sdk/client-eventbridge";
22
22
  import {
23
- CONTROL_PLANE_ROLE_CONCEPTS,
24
- CONTROL_PLANE_ROLE_IDS
23
+ PLATFORM_ROLE_CONCEPTS,
24
+ PLATFORM_ROLE_IDS
25
25
  } from "@openhi/types";
26
26
  var errorMessage = (err) => {
27
27
  if (err instanceof Error) {
@@ -30,15 +30,15 @@ var errorMessage = (err) => {
30
30
  return String(err);
31
31
  };
32
32
  var idForRoleCode = (code) => {
33
- for (const key of Object.keys(CONTROL_PLANE_ROLE_IDS)) {
34
- if (CONTROL_PLANE_ROLE_CONCEPTS[key].code === code) {
35
- return CONTROL_PLANE_ROLE_IDS[key];
33
+ for (const key of Object.keys(PLATFORM_ROLE_IDS)) {
34
+ if (PLATFORM_ROLE_CONCEPTS[key].code === code) {
35
+ return PLATFORM_ROLE_IDS[key];
36
36
  }
37
37
  }
38
38
  throw new Error(`No id mapping for role code "${code}".`);
39
39
  };
40
40
  var seedSystemRoles = async (context) => {
41
- for (const concept of Object.values(CONTROL_PLANE_ROLE_CONCEPTS)) {
41
+ for (const concept of Object.values(PLATFORM_ROLE_CONCEPTS)) {
42
42
  const id = idForRoleCode(concept.code);
43
43
  await createRoleOperation({
44
44
  context,
@@ -90,7 +90,7 @@ var runSeedSystemData = async (event, deps) => {
90
90
  payload: {
91
91
  sourceEventId: parsed.envelope.eventId,
92
92
  sourceStackId: sourcePayload.stackId,
93
- seededRecordCount: Object.keys(CONTROL_PLANE_ROLE_IDS).length
93
+ seededRecordCount: Object.keys(PLATFORM_ROLE_IDS).length
94
94
  },
95
95
  envelope: {
96
96
  correlationId: parsed.envelope.correlationId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/workflows/control-plane/seed-system-data/seed-system-data.handler.ts"],"sourcesContent":["import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { EventBridgeClient } from \"@aws-sdk/client-eventbridge\";\nimport {\n CONTROL_PLANE_ROLE_CONCEPTS,\n CONTROL_PLANE_ROLE_IDS,\n type ControlPlaneRoleCode,\n} from \"@openhi/types\";\nimport {\n OPENHI_CONTROL_SOURCE,\n parseWorkflowEvent,\n publishWorkflowEvent,\n workflowDedupClient,\n type PlatformDeploymentCompletedV1Detail,\n type PublishResult,\n type WorkflowDedupClient,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n PlatformDeploymentCompletedV1,\n PlatformSystemDataSeededV1,\n SEED_SYSTEM_DATA_ACTOR_SYSTEM,\n SEED_SYSTEM_DATA_CONSUMER_NAME,\n SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR,\n} from \"./events\";\nimport type { OpenHiContext } from \"../../../data/openhi-context\";\nimport { createRoleOperation } from \"../../../data/operations/control/role/role-create-operation\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-system-data/seed-system-data-handler.md\n *\n * EventBridge workflow handler invoked once per platform-deploy event on\n * the control event bus. Idempotently re-asserts the platform-singleton\n * control-plane records (today: the three canonical Roles; future:\n * additional system data slotted in as sibling steps under the same\n * dedup record).\n */\n\ntype SeedSystemDataEvent = EventBridgeEvent<\n \"platform.deployment-completed.v1\",\n unknown\n>;\n\n/**\n * Input shape the handler passes to the completion publisher. Mirrors\n * the wire shape of `PlatformSystemDataSeededV1Detail` plus the\n * envelope-correlation fields the publisher needs to chain the new\n * event onto the originating deploy event.\n */\nexport interface PublishSeededInput {\n /** Per-workflow payload — re-asserted record count + source event/stack ids. */\n readonly payload: {\n readonly sourceEventId: string;\n readonly sourceStackId: string;\n readonly seededRecordCount: number;\n };\n /**\n * Envelope context propagated from the originating\n * `platform.deployment-completed.v1` event so the chain is\n * observable in logs.\n */\n readonly envelope: {\n readonly correlationId: string;\n readonly causationId: string;\n };\n}\n\n/**\n * Dependency seam for tests. The factory mirrors the production\n * arrangement: a real DynamoDB client + the real `workflowDedupClient`\n * factory bound to the `OPENHI_WORKFLOW_DEDUP_TABLE_NAME` env var the\n * construct injects via `grantConsumer`.\n */\nexport interface SeedSystemDataDependencies {\n readonly dedupClient: WorkflowDedupClient;\n readonly seedRoles: (context: OpenHiContext) => Promise<void>;\n /**\n * Publishes the `platform.system-data-seeded.v1` completion event\n * onto the control event bus. Called after a successful seed and\n * before the handler returns — downstream consumers\n * (`seed-demo-data`) subscribe to this event instead of the raw\n * deploy-completion event so the dependency on system data is\n * enforced by a happens-before edge.\n */\n readonly publishSeeded: (input: PublishSeededInput) => Promise<PublishResult>;\n}\n\nconst errorMessage = (err: unknown): string => {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n};\n\n/**\n * Lookup the stable record id for a given role code by walking the two\n * parallel generator-emitted constants. The `_IDS` / `_CONCEPTS` consts\n * are keyed by the same SCREAMING_SNAKE keys (the generator builds them\n * in lockstep), so a single key resolves both projections.\n */\nconst idForRoleCode = (code: ControlPlaneRoleCode): string => {\n for (const key of Object.keys(CONTROL_PLANE_ROLE_IDS) as Array<\n keyof typeof CONTROL_PLANE_ROLE_IDS\n >) {\n if (CONTROL_PLANE_ROLE_CONCEPTS[key].code === code) {\n return CONTROL_PLANE_ROLE_IDS[key];\n }\n }\n // Unreachable while `_IDS` and `_CONCEPTS` are emitted from the same\n // CodeSystem: every code in `_CONCEPTS` has a matching id in `_IDS`.\n throw new Error(`No id mapping for role code \"${code}\".`);\n};\n\n/**\n * Re-assert the three canonical control-plane Role records. Stable ids\n * from `CONTROL_PLANE_ROLE_IDS` make re-runs after dedup-TTL expiry\n * idempotent: every put with the same id+sk lands on the same primary\n * key and produces the same end state.\n */\nconst seedSystemRoles = async (context: OpenHiContext): Promise<void> => {\n for (const concept of Object.values(CONTROL_PLANE_ROLE_CONCEPTS)) {\n const id = idForRoleCode(concept.code);\n await createRoleOperation({\n context,\n body: {\n id,\n resource: {\n resourceType: \"Role\",\n code: concept.code,\n display: concept.display,\n active: true,\n },\n },\n });\n }\n};\n\n/**\n * Test-visible orchestrator. The production `handler` calls this with\n * real dependencies; unit tests inject fakes.\n */\nexport const runSeedSystemData = async (\n event: SeedSystemDataEvent,\n deps: SeedSystemDataDependencies,\n): Promise<void> => {\n const parsed = parseWorkflowEvent(event, PlatformDeploymentCompletedV1);\n\n // Dedup is the single circuit-breaker between EventBridge's\n // at-least-once redelivery and the DynamoDB writes below. If\n // `recordIfAbsent` returns `recorded: false`, a prior invocation of\n // this (eventId, attempt) tuple already ran to completion — there is\n // nothing left to do, so we exit without re-iterating the catalog.\n const recordResult = await deps.dedupClient.recordIfAbsent({\n consumerName: SEED_SYSTEM_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n });\n if (!recordResult.recorded) {\n return;\n }\n\n // Role records are platform-wide singletons (per ADR 2026-03-13-02 §4)\n // and live under the sentinel tenant/workspace coordinates the\n // `role-entity` declares (`TID#-#WID#-`). We pass an internal-system\n // context whose tenant/workspace fields are empty strings; the role\n // create operation reads only `date` and audit metadata.\n const lastUpdated = parsed.envelope.occurredAt;\n const context: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: lastUpdated,\n actorId: \"platform-deploy-bridge\",\n actorName: \"Platform Deploy Bridge\",\n actorType: \"internal-system\",\n source: \"step-function\",\n };\n\n try {\n await deps.seedRoles(context);\n } catch (err) {\n // Mark the dedup row failed so the replay tooling (TR-016 follow-up)\n // can re-publish the originating event with a fresh attempt. The\n // re-throw keeps EventBridge's failure-detection path intact — the\n // event ends up in the DLQ + the workflow Lambda's failure\n // CloudWatch alarm fires.\n await deps.dedupClient.markFailed({\n consumerName: SEED_SYSTEM_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n reason: errorMessage(err),\n });\n throw err;\n }\n\n // Seeding succeeded — publish `platform.system-data-seeded.v1` so\n // downstream consumers that depend on the platform-singleton\n // records existing (today: `seed-demo-data`) are triggered by a\n // happens-before edge rather than by EventBridge retry timing.\n //\n // A publish failure here does NOT roll back the seed — the records\n // are already in the data store and idempotent on re-fire. We log\n // and re-throw so EventBridge retries the whole event; on retry,\n // dedup wins (no-op) and the publish path re-runs.\n const sourcePayload = parsed.envelope\n .payload as PlatformDeploymentCompletedV1Detail;\n await deps.publishSeeded({\n payload: {\n sourceEventId: parsed.envelope.eventId,\n sourceStackId: sourcePayload.stackId,\n seededRecordCount: Object.keys(CONTROL_PLANE_ROLE_IDS).length,\n },\n envelope: {\n correlationId: parsed.envelope.correlationId,\n causationId: parsed.envelope.eventId,\n },\n });\n};\n\nconst productionDependencies = (): SeedSystemDataDependencies => {\n const dynamodb = new DynamoDBClient({});\n const eventBridge = new EventBridgeClient({});\n return {\n dedupClient: workflowDedupClient(dynamodb),\n seedRoles: seedSystemRoles,\n publishSeeded: async (input) => {\n const controlBusName = process.env[SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR];\n if (!controlBusName) {\n throw new Error(\n `seed-system-data: ${SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR} env var is required to publish system-data-seeded events.`,\n );\n }\n return publishWorkflowEvent(\n eventBridge,\n PlatformSystemDataSeededV1,\n input.payload,\n {\n actor: { system: SEED_SYSTEM_DATA_ACTOR_SYSTEM },\n correlationId: input.envelope.correlationId,\n causationId: input.envelope.causationId,\n },\n {\n busNameByPlane: { [OPENHI_CONTROL_SOURCE]: controlBusName },\n },\n );\n },\n };\n};\n\nexport const handler = async (event: SeedSystemDataEvent): Promise<void> =>\n runSeedSystemData(event, productionDependencies());\n"],"mappings":"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","names":["import_workflows"]}
1
+ {"version":3,"sources":["../src/workflows/control-plane/seed-system-data/seed-system-data.handler.ts"],"sourcesContent":["import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { EventBridgeClient } from \"@aws-sdk/client-eventbridge\";\nimport {\n PLATFORM_ROLE_CONCEPTS,\n PLATFORM_ROLE_IDS,\n type PlatformRoleCode,\n} from \"@openhi/types\";\nimport {\n OPENHI_CONTROL_SOURCE,\n parseWorkflowEvent,\n publishWorkflowEvent,\n workflowDedupClient,\n type PlatformDeploymentCompletedV1Detail,\n type PublishResult,\n type WorkflowDedupClient,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n PlatformDeploymentCompletedV1,\n PlatformSystemDataSeededV1,\n SEED_SYSTEM_DATA_ACTOR_SYSTEM,\n SEED_SYSTEM_DATA_CONSUMER_NAME,\n SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR,\n} from \"./events\";\nimport type { OpenHiContext } from \"../../../data/openhi-context\";\nimport { createRoleOperation } from \"../../../data/operations/control/role/role-create-operation\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-system-data/seed-system-data-handler.md\n *\n * EventBridge workflow handler invoked once per platform-deploy event on\n * the control event bus. Idempotently re-asserts the platform-singleton\n * control-plane records (today: the three canonical Roles; future:\n * additional system data slotted in as sibling steps under the same\n * dedup record).\n */\n\ntype SeedSystemDataEvent = EventBridgeEvent<\n \"platform.deployment-completed.v1\",\n unknown\n>;\n\n/**\n * Input shape the handler passes to the completion publisher. Mirrors\n * the wire shape of `PlatformSystemDataSeededV1Detail` plus the\n * envelope-correlation fields the publisher needs to chain the new\n * event onto the originating deploy event.\n */\nexport interface PublishSeededInput {\n /** Per-workflow payload — re-asserted record count + source event/stack ids. */\n readonly payload: {\n readonly sourceEventId: string;\n readonly sourceStackId: string;\n readonly seededRecordCount: number;\n };\n /**\n * Envelope context propagated from the originating\n * `platform.deployment-completed.v1` event so the chain is\n * observable in logs.\n */\n readonly envelope: {\n readonly correlationId: string;\n readonly causationId: string;\n };\n}\n\n/**\n * Dependency seam for tests. The factory mirrors the production\n * arrangement: a real DynamoDB client + the real `workflowDedupClient`\n * factory bound to the `OPENHI_WORKFLOW_DEDUP_TABLE_NAME` env var the\n * construct injects via `grantConsumer`.\n */\nexport interface SeedSystemDataDependencies {\n readonly dedupClient: WorkflowDedupClient;\n readonly seedRoles: (context: OpenHiContext) => Promise<void>;\n /**\n * Publishes the `platform.system-data-seeded.v1` completion event\n * onto the control event bus. Called after a successful seed and\n * before the handler returns — downstream consumers\n * (`seed-demo-data`) subscribe to this event instead of the raw\n * deploy-completion event so the dependency on system data is\n * enforced by a happens-before edge.\n */\n readonly publishSeeded: (input: PublishSeededInput) => Promise<PublishResult>;\n}\n\nconst errorMessage = (err: unknown): string => {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n};\n\n/**\n * Lookup the stable record id for a given role code by walking the two\n * parallel generator-emitted constants. The `_IDS` / `_CONCEPTS` consts\n * are keyed by the same SCREAMING_SNAKE keys (the generator builds them\n * in lockstep), so a single key resolves both projections.\n */\nconst idForRoleCode = (code: PlatformRoleCode): string => {\n for (const key of Object.keys(PLATFORM_ROLE_IDS) as Array<\n keyof typeof PLATFORM_ROLE_IDS\n >) {\n if (PLATFORM_ROLE_CONCEPTS[key].code === code) {\n return PLATFORM_ROLE_IDS[key];\n }\n }\n // Unreachable while `_IDS` and `_CONCEPTS` are emitted from the same\n // CodeSystem: every code in `_CONCEPTS` has a matching id in `_IDS`.\n throw new Error(`No id mapping for role code \"${code}\".`);\n};\n\n/**\n * Re-assert the three canonical control-plane Role records. Stable ids\n * from `PLATFORM_ROLE_IDS` make re-runs after dedup-TTL expiry\n * idempotent: every put with the same id+sk lands on the same primary\n * key and produces the same end state.\n */\nconst seedSystemRoles = async (context: OpenHiContext): Promise<void> => {\n for (const concept of Object.values(PLATFORM_ROLE_CONCEPTS)) {\n const id = idForRoleCode(concept.code);\n await createRoleOperation({\n context,\n body: {\n id,\n resource: {\n resourceType: \"Role\",\n code: concept.code,\n display: concept.display,\n active: true,\n },\n },\n });\n }\n};\n\n/**\n * Test-visible orchestrator. The production `handler` calls this with\n * real dependencies; unit tests inject fakes.\n */\nexport const runSeedSystemData = async (\n event: SeedSystemDataEvent,\n deps: SeedSystemDataDependencies,\n): Promise<void> => {\n const parsed = parseWorkflowEvent(event, PlatformDeploymentCompletedV1);\n\n // Dedup is the single circuit-breaker between EventBridge's\n // at-least-once redelivery and the DynamoDB writes below. If\n // `recordIfAbsent` returns `recorded: false`, a prior invocation of\n // this (eventId, attempt) tuple already ran to completion — there is\n // nothing left to do, so we exit without re-iterating the catalog.\n const recordResult = await deps.dedupClient.recordIfAbsent({\n consumerName: SEED_SYSTEM_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n });\n if (!recordResult.recorded) {\n return;\n }\n\n // Role records are platform-wide singletons (per ADR 2026-03-13-02 §4)\n // and live under the sentinel tenant/workspace coordinates the\n // `role-entity` declares (`TID#-#WID#-`). We pass an internal-system\n // context whose tenant/workspace fields are empty strings; the role\n // create operation reads only `date` and audit metadata.\n const lastUpdated = parsed.envelope.occurredAt;\n const context: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: lastUpdated,\n actorId: \"platform-deploy-bridge\",\n actorName: \"Platform Deploy Bridge\",\n actorType: \"internal-system\",\n source: \"step-function\",\n };\n\n try {\n await deps.seedRoles(context);\n } catch (err) {\n // Mark the dedup row failed so the replay tooling (TR-016 follow-up)\n // can re-publish the originating event with a fresh attempt. The\n // re-throw keeps EventBridge's failure-detection path intact — the\n // event ends up in the DLQ + the workflow Lambda's failure\n // CloudWatch alarm fires.\n await deps.dedupClient.markFailed({\n consumerName: SEED_SYSTEM_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n reason: errorMessage(err),\n });\n throw err;\n }\n\n // Seeding succeeded — publish `platform.system-data-seeded.v1` so\n // downstream consumers that depend on the platform-singleton\n // records existing (today: `seed-demo-data`) are triggered by a\n // happens-before edge rather than by EventBridge retry timing.\n //\n // A publish failure here does NOT roll back the seed — the records\n // are already in the data store and idempotent on re-fire. We log\n // and re-throw so EventBridge retries the whole event; on retry,\n // dedup wins (no-op) and the publish path re-runs.\n const sourcePayload = parsed.envelope\n .payload as PlatformDeploymentCompletedV1Detail;\n await deps.publishSeeded({\n payload: {\n sourceEventId: parsed.envelope.eventId,\n sourceStackId: sourcePayload.stackId,\n seededRecordCount: Object.keys(PLATFORM_ROLE_IDS).length,\n },\n envelope: {\n correlationId: parsed.envelope.correlationId,\n causationId: parsed.envelope.eventId,\n },\n });\n};\n\nconst productionDependencies = (): SeedSystemDataDependencies => {\n const dynamodb = new DynamoDBClient({});\n const eventBridge = new EventBridgeClient({});\n return {\n dedupClient: workflowDedupClient(dynamodb),\n seedRoles: seedSystemRoles,\n publishSeeded: async (input) => {\n const controlBusName = process.env[SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR];\n if (!controlBusName) {\n throw new Error(\n `seed-system-data: ${SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR} env var is required to publish system-data-seeded events.`,\n );\n }\n return publishWorkflowEvent(\n eventBridge,\n PlatformSystemDataSeededV1,\n input.payload,\n {\n actor: { system: SEED_SYSTEM_DATA_ACTOR_SYSTEM },\n correlationId: input.envelope.correlationId,\n causationId: input.envelope.causationId,\n },\n {\n busNameByPlane: { [OPENHI_CONTROL_SOURCE]: controlBusName },\n },\n );\n },\n };\n};\n\nexport const handler = async (event: SeedSystemDataEvent): Promise<void> =>\n runSeedSystemData(event, productionDependencies());\n"],"mappings":"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","names":["import_workflows"]}
package/package.json CHANGED
@@ -71,7 +71,7 @@
71
71
  "publishConfig": {
72
72
  "access": "public"
73
73
  },
74
- "version": "0.0.106",
74
+ "version": "0.0.108",
75
75
  "types": "lib/index.d.ts",
76
76
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\".",
77
77
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/data/operations/control/membership/membership-create-operation.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"],"sourcesContent":["import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\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 const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.membership\n .put({\n tenantId: context.tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\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 const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.roleAssignment\n .put({\n tenantId: context.tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { 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\";\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 return { id, resource, meta: { lastUpdated, versionId: vid } };\n}\n"],"mappings":";;;;;AAAA,SAAS,sBAA6C;AAgBtD,eAAsB,0BACpB,QACiC;AACjC,QAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AACrC,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,KAAK,KAAK,MAAM,cAAc,KAAK,IAAI,CAAC;AAC9C,QAAM,iBACJ,OAAO,KAAK,aAAa,WACpB,KAAK,MAAM,KAAK,QAAQ,IACxB,KAAK,YAAY,CAAC;AAEzB,QAAM,cAAc,QAAQ,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAC3D,QAAM,MAAM;AAEZ,QAAM,WAAW,EAAE,cAAc,cAAc,IAAI,GAAG,eAAe;AACrE,QAAM,UAAU,KAAK,UAAU,eAAe,QAA4B,CAAC;AAE3E,QAAM,QAAQ,SAAS,WACpB,IAAI;AAAA,IACH,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,EACtC;AACF;;;AClDA,SAAS,kBAAAA,uBAA6C;AAgBtD,eAAsB,8BACpB,QACqC;AACrC,QAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AACrC,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,KAAK,KAAK,MAAM,kBAAkB,KAAK,IAAI,CAAC;AAClD,QAAM,iBACJ,OAAO,KAAK,aAAa,WACpB,KAAK,MAAM,KAAK,QAAQ,IACxB,KAAK,YAAY,CAAC;AAEzB,QAAM,cAAc,QAAQ,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAC3D,QAAM,MAAM;AAEZ,QAAM,WAAW,EAAE,cAAc,kBAAkB,IAAI,GAAG,eAAe;AACzE,QAAM,UAAU,KAAK,UAAUC,gBAAe,QAA4B,CAAC;AAE3E,QAAM,QAAQ,SAAS,eACpB,IAAI;AAAA,IACH,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,EACtC;AACF;;;AClDA,SAAS,kBAAAC,uBAA6C;AAsBtD,eAAsB,sBACpB,QAC6B;AAC7B,QAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AACrC,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,KAAK,KAAK,MAAM,UAAU,KAAK,IAAI,CAAC;AAC1C,QAAM,cAAc,QAAQ;AAC5B,QAAM,MACJ,YAAY,QAAQ,YAAY,EAAE,EAAE,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AAE5E,QAAM,iBACJ,OAAO,KAAK,aAAa,WACpB,KAAK,MAAM,KAAK,QAAQ,IACxB,KAAK,YAAY,CAAC;AAEzB,QAAM,WAAW,EAAE,cAAc,UAAU,IAAI,GAAG,eAAe;AACjE,QAAM,UAAU,KAAK,UAAUC,gBAAe,QAA4B,CAAC;AAE3E,QAAM,QAAQ,SAAS,OACpB,IAAI;AAAA,IACH,UAAU;AAAA,IACV;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO,EAAE,IAAI,UAAU,MAAM,EAAE,aAAa,WAAW,IAAI,EAAE;AAC/D;;;ACrDA,SAAS,kBAAAC,uBAA6C;AAsBtD,eAAsB,yBACpB,QACgC;AAChC,QAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AACrC,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,UAAU,wBAAwB,SAAS;AAEjD,QAAM,KAAK,KAAK,MAAM,aAAa,KAAK,IAAI,CAAC;AAC7C,QAAM,cAAc,QAAQ;AAC5B,QAAM,MACJ,YAAY,QAAQ,YAAY,EAAE,EAAE,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AAE5E,QAAM,iBACJ,OAAO,KAAK,aAAa,WACpB,KAAK,MAAM,KAAK,QAAQ,IACxB,KAAK,YAAY,CAAC;AAEzB,QAAM,WAAW,EAAE,cAAc,aAAa,IAAI,GAAG,eAAe;AACpE,QAAM,UAAU,KAAK,UAAUC,gBAAe,QAA4B,CAAC;AAE3E,QAAM,QAAQ,SAAS,UACpB,IAAI;AAAA,IACH;AAAA,IACA;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EACA,GAAG;AAEN,SAAO,EAAE,IAAI,UAAU,MAAM,EAAE,aAAa,WAAW,IAAI,EAAE;AAC/D;","names":["extractSummary","extractSummary","extractSummary","extractSummary","extractSummary","extractSummary"]}
@@ -1,428 +0,0 @@
1
- import {
2
- NotFoundError
3
- } from "./chunk-YZZDUJHI.mjs";
4
- import {
5
- SHARD_COUNT,
6
- getDynamoControlService
7
- } from "./chunk-VYDIGFIX.mjs";
8
-
9
- // src/data/operations/control/user/user-create-operation.ts
10
- import { extractSummary } from "@openhi/types";
11
- async function createUserOperation(params) {
12
- const { context, body, tableName } = params;
13
- const service = getDynamoControlService(tableName);
14
- const id = body.id ?? `user-${Date.now()}`;
15
- const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
16
- const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
17
- const vid = `1`;
18
- const resource = { resourceType: "User", id, ...parsedResource };
19
- const summary = JSON.stringify(extractSummary(resource));
20
- await service.entities.user.put({
21
- id,
22
- resource: JSON.stringify(resource),
23
- summary,
24
- vid,
25
- lastUpdated
26
- }).go();
27
- return {
28
- id,
29
- resource,
30
- meta: { lastUpdated, versionId: vid }
31
- };
32
- }
33
-
34
- // src/data/operations/control/user/user-delete-operation.ts
35
- async function deleteUserOperation(params) {
36
- const { id, tableName } = params;
37
- const service = getDynamoControlService(tableName);
38
- await service.entities.user.delete({ id, sk: "CURRENT" }).go();
39
- }
40
-
41
- // src/data/operations/control/user/user-get-by-id-operation.ts
42
- async function getUserByIdOperation(params) {
43
- const { id, tableName } = params;
44
- const service = getDynamoControlService(tableName);
45
- const response = await service.entities.user.get({ id, sk: "CURRENT" }).go();
46
- const item = response.data;
47
- if (!item) {
48
- throw new NotFoundError(`User not found: ${id}`);
49
- }
50
- const parsedResource = JSON.parse(item.resource);
51
- return {
52
- id,
53
- resource: { resourceType: "User", id, ...parsedResource }
54
- };
55
- }
56
-
57
- // src/data/operations/data-operations-common.ts
58
- import { extractSortKey, extractSummary as extractSummary2 } from "@openhi/types";
59
-
60
- // src/lib/compression.ts
61
- import { gzipSync, gunzipSync } from "zlib";
62
- var ENVELOPE_VERSION = 1;
63
- var COMPRESSION_ALGOS = {
64
- NONE: "none",
65
- GZIP: "gzip",
66
- BROTLI: "brotli",
67
- DEFLATE: "deflate"
68
- };
69
- function isEnvelope(obj) {
70
- return typeof obj === "object" && obj !== null && "v" in obj && "algo" in obj && "payload" in obj && typeof obj.payload === "string";
71
- }
72
- function compressResource(jsonString, options) {
73
- const algo = options?.algo ?? COMPRESSION_ALGOS.GZIP;
74
- if (algo === COMPRESSION_ALGOS.NONE) {
75
- const envelope2 = {
76
- v: ENVELOPE_VERSION,
77
- algo: COMPRESSION_ALGOS.NONE,
78
- payload: jsonString
79
- };
80
- return JSON.stringify(envelope2);
81
- }
82
- const buf = Buffer.from(jsonString, "utf-8");
83
- const payload = gzipSync(buf).toString("base64");
84
- const envelope = {
85
- v: ENVELOPE_VERSION,
86
- algo: COMPRESSION_ALGOS.GZIP,
87
- payload
88
- };
89
- return JSON.stringify(envelope);
90
- }
91
- function decompressResource(compressedOrRaw) {
92
- try {
93
- const parsed = JSON.parse(compressedOrRaw);
94
- if (isEnvelope(parsed)) {
95
- if (parsed.algo === COMPRESSION_ALGOS.GZIP) {
96
- const buf = Buffer.from(parsed.payload, "base64");
97
- return gunzipSync(buf).toString("utf-8");
98
- }
99
- if (parsed.algo === COMPRESSION_ALGOS.NONE) {
100
- return parsed.payload;
101
- }
102
- return parsed.payload;
103
- }
104
- } catch {
105
- }
106
- try {
107
- const buf = Buffer.from(compressedOrRaw, "base64");
108
- if (buf.length >= 2 && buf[0] === 31 && buf[1] === 139) {
109
- return gunzipSync(buf).toString("utf-8");
110
- }
111
- } catch {
112
- }
113
- return compressedOrRaw;
114
- }
115
-
116
- // src/data/audit-meta.ts
117
- var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
118
- function mergeAuditIntoMeta(meta, audit) {
119
- const existing = meta ?? {};
120
- const ext = [
121
- ...Array.isArray(existing.extension) ? existing.extension : []
122
- ];
123
- const byUrl = new Map(ext.map((e) => [e.url, e]));
124
- function set(url, value, type) {
125
- if (value == null) return;
126
- byUrl.set(url, { url, [type]: value });
127
- }
128
- set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
129
- set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
130
- set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
131
- set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
132
- set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
133
- set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
134
- set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
135
- set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
136
- set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
137
- return { ...existing, extension: Array.from(byUrl.values()) };
138
- }
139
-
140
- // src/data/operations/data-operations-common.ts
141
- var DATA_ENTITY_SK = "CURRENT";
142
- async function getDataEntityById(entity, tenantId, workspaceId, id, resourceLabel) {
143
- const result = await entity.get({
144
- tenantId,
145
- workspaceId,
146
- id,
147
- sk: DATA_ENTITY_SK
148
- }).go();
149
- if (!result.data) {
150
- throw new NotFoundError(`${resourceLabel} ${id} not found`, {
151
- details: { id }
152
- });
153
- }
154
- const parsed = JSON.parse(decompressResource(result.data.resource));
155
- return {
156
- id: result.data.id,
157
- resource: { ...parsed, id: result.data.id }
158
- };
159
- }
160
- async function deleteDataEntityById(entity, tenantId, workspaceId, id) {
161
- await entity.delete({
162
- tenantId,
163
- workspaceId,
164
- id,
165
- sk: DATA_ENTITY_SK
166
- }).go();
167
- }
168
- var BATCH_GET_MAX_ATTEMPTS = 3;
169
- var BATCH_GET_BASE_BACKOFF_MS = 50;
170
- async function batchGetWithRetry(entity, keys) {
171
- if (keys.length === 0) return [];
172
- const collected = [];
173
- let pending = keys;
174
- let attempt = 0;
175
- while (pending.length > 0) {
176
- if (attempt > 0) {
177
- await new Promise(
178
- (resolve) => setTimeout(resolve, BATCH_GET_BASE_BACKOFF_MS * 2 ** (attempt - 1))
179
- );
180
- }
181
- attempt++;
182
- const result = await entity.get(pending).go();
183
- collected.push(...result.data);
184
- const unprocessed = result.unprocessed ?? [];
185
- if (unprocessed.length === 0) break;
186
- if (attempt >= BATCH_GET_MAX_ATTEMPTS) {
187
- throw new Error(
188
- `BatchGet exhausted retries: ${unprocessed.length} key(s) still unprocessed after ${BATCH_GET_MAX_ATTEMPTS} attempt(s)`
189
- );
190
- }
191
- pending = unprocessed;
192
- }
193
- return collected;
194
- }
195
- async function dispatchListMode(mode, shardResults, hooks) {
196
- if (mode === "count") {
197
- let total = 0;
198
- for (const shardResult of shardResults) {
199
- total += (shardResult.data ?? []).length;
200
- }
201
- return { entries: [], total };
202
- }
203
- if (mode === "summary") {
204
- const entries2 = [];
205
- for (const shardResult of shardResults) {
206
- for (const item of shardResult.data ?? []) {
207
- if (typeof item.summary !== "string") continue;
208
- let parsed;
209
- try {
210
- parsed = JSON.parse(item.summary);
211
- } catch {
212
- continue;
213
- }
214
- entries2.push(hooks.buildSummaryEntry(item.id, parsed));
215
- }
216
- }
217
- return { entries: entries2, total: entries2.length };
218
- }
219
- const orderedIds = [];
220
- for (const shardResult of shardResults) {
221
- for (const item of shardResult.data ?? []) {
222
- orderedIds.push(item.id);
223
- }
224
- }
225
- if (orderedIds.length === 0) return { entries: [], total: 0 };
226
- const items = await hooks.hydrate(orderedIds);
227
- const byId = new Map(items.map((item) => [hooks.getId(item), item]));
228
- const entries = [];
229
- for (const id of orderedIds) {
230
- const item = byId.get(id);
231
- if (!item) continue;
232
- entries.push(hooks.buildEntry(id, item));
233
- }
234
- return { entries, total: entries.length };
235
- }
236
- async function listDataEntitiesByWorkspace(entity, tenantId, workspaceId, mode = "full") {
237
- const shardResults = await Promise.all(
238
- Array.from(
239
- { length: SHARD_COUNT },
240
- (_, shard) => entity.query.gsi1({ tenantId, workspaceId, gsi1Shard: String(shard) }).go()
241
- )
242
- );
243
- return dispatchListMode(
244
- mode,
245
- shardResults,
246
- {
247
- hydrate: (orderedIds) => batchGetWithRetry(
248
- entity,
249
- orderedIds.map((id) => ({
250
- tenantId,
251
- workspaceId,
252
- id,
253
- sk: DATA_ENTITY_SK
254
- }))
255
- ),
256
- getId: (item) => item.id,
257
- buildEntry: (id, item) => {
258
- const parsed = JSON.parse(decompressResource(item.resource));
259
- return { id, resource: { ...parsed, id } };
260
- },
261
- buildSummaryEntry: (id, parsed) => ({
262
- id,
263
- resource: { ...parsed, id }
264
- })
265
- }
266
- );
267
- }
268
- async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourceWithAudit, fallbackDate) {
269
- const lastUpdated = resourceWithAudit.meta?.lastUpdated ?? fallbackDate ?? (/* @__PURE__ */ new Date()).toISOString();
270
- const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
271
- const resourceLike = resourceWithAudit;
272
- const summary = JSON.stringify(extractSummary2(resourceLike));
273
- const gsi1sk = extractSortKey(resourceLike);
274
- await entity.put({
275
- sk: DATA_ENTITY_SK,
276
- tenantId,
277
- workspaceId,
278
- id,
279
- resource: compressResource(JSON.stringify(resourceWithAudit)),
280
- summary,
281
- vid,
282
- lastUpdated,
283
- gsi1sk
284
- }).go();
285
- return {
286
- id,
287
- resource: resourceWithAudit
288
- };
289
- }
290
- function buildUpdatedResourceWithAudit(body, id, date, actorId, actorName, existingResourceStr, resourceType) {
291
- const existingMeta = JSON.parse(existingResourceStr).meta;
292
- const bodyWithMeta = body;
293
- const resourceWithVersion = {
294
- ...body,
295
- resourceType,
296
- id,
297
- meta: {
298
- ...bodyWithMeta.meta ?? {},
299
- lastUpdated: date,
300
- versionId: "2"
301
- }
302
- };
303
- const resourceWithAudit = {
304
- ...resourceWithVersion,
305
- meta: mergeAuditIntoMeta(resourceWithVersion.meta ?? existingMeta, {
306
- modifiedDate: date,
307
- modifiedById: actorId,
308
- modifiedByName: actorName
309
- })
310
- };
311
- return {
312
- resource: resourceWithAudit,
313
- lastUpdated: date
314
- };
315
- }
316
- async function updateDataEntityById(entity, tenantId, workspaceId, id, resourceLabel, context, buildPatched) {
317
- const existing = await entity.get({
318
- tenantId,
319
- workspaceId,
320
- id,
321
- sk: DATA_ENTITY_SK
322
- }).go();
323
- if (!existing.data) {
324
- throw new NotFoundError(`${resourceLabel} ${id} not found`, {
325
- details: { id }
326
- });
327
- }
328
- const existingStr = decompressResource(existing.data.resource);
329
- const { resource, lastUpdated } = buildPatched(existingStr);
330
- const resourceLike = resource;
331
- const summary = JSON.stringify(extractSummary2(resourceLike));
332
- const gsi1sk = extractSortKey(resourceLike);
333
- await entity.patch({
334
- tenantId,
335
- workspaceId,
336
- id,
337
- sk: DATA_ENTITY_SK
338
- }).set({
339
- resource: compressResource(JSON.stringify(resource)),
340
- summary,
341
- lastUpdated,
342
- gsi1sk
343
- }).go();
344
- return {
345
- id,
346
- resource
347
- };
348
- }
349
-
350
- // src/data/operations/control/user/user-list-operation.ts
351
- var SK = "CURRENT";
352
- async function listUsersOperation(params) {
353
- const { tableName, mode = "full" } = params;
354
- const service = getDynamoControlService(tableName);
355
- const shardResults = await Promise.all(
356
- Array.from(
357
- { length: SHARD_COUNT },
358
- (_, shard) => service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go()
359
- )
360
- );
361
- return dispatchListMode(mode, shardResults, {
362
- hydrate: (orderedIds) => batchGetWithRetry(
363
- service.entities.user,
364
- orderedIds.map((id) => ({ id, sk: SK }))
365
- ),
366
- getId: (item) => item.id,
367
- buildEntry: (id, item) => ({
368
- id,
369
- resource: {
370
- resourceType: "User",
371
- id,
372
- ...JSON.parse(item.resource)
373
- }
374
- }),
375
- buildSummaryEntry: (id, parsed) => ({
376
- id,
377
- resource: { resourceType: "User", id, ...parsed }
378
- })
379
- });
380
- }
381
-
382
- // src/data/operations/control/user/user-update-operation.ts
383
- import { extractSummary as extractSummary3 } from "@openhi/types";
384
- async function updateUserOperation(params) {
385
- const { context, id, body, tableName } = params;
386
- const service = getDynamoControlService(tableName);
387
- const existing = await service.entities.user.get({ id, sk: "CURRENT" }).go();
388
- if (!existing.data) {
389
- throw new NotFoundError(`User not found: ${id}`);
390
- }
391
- const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
392
- const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
393
- const vid = `${Date.now()}`;
394
- const resource = { resourceType: "User", id, ...parsedResource };
395
- const summary = JSON.stringify(extractSummary3(resource));
396
- await service.entities.user.put({
397
- id,
398
- resource: JSON.stringify(resource),
399
- summary,
400
- vid,
401
- lastUpdated
402
- }).go();
403
- return {
404
- id,
405
- resource,
406
- meta: { lastUpdated, versionId: vid }
407
- };
408
- }
409
-
410
- export {
411
- compressResource,
412
- decompressResource,
413
- mergeAuditIntoMeta,
414
- getDataEntityById,
415
- deleteDataEntityById,
416
- batchGetWithRetry,
417
- dispatchListMode,
418
- listDataEntitiesByWorkspace,
419
- createDataEntityRecord,
420
- buildUpdatedResourceWithAudit,
421
- updateDataEntityById,
422
- createUserOperation,
423
- deleteUserOperation,
424
- getUserByIdOperation,
425
- listUsersOperation,
426
- updateUserOperation
427
- };
428
- //# sourceMappingURL=chunk-JUNL76HF.mjs.map