@openhi/constructs 0.0.177 → 0.0.179

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/lib/{chunk-Z4PZSLYY.mjs → chunk-3M4QTQH6.mjs} +2 -2
  2. package/lib/{chunk-JUSVETWK.mjs → chunk-4LQR32D2.mjs} +38 -40
  3. package/lib/{chunk-JUSVETWK.mjs.map → chunk-4LQR32D2.mjs.map} +1 -1
  4. package/lib/{chunk-XNUCKVSE.mjs → chunk-7GMTHOYF.mjs} +2 -2
  5. package/lib/{chunk-E2OWEBBH.mjs → chunk-DIVYB6GD.mjs} +18 -4
  6. package/lib/chunk-DIVYB6GD.mjs.map +1 -0
  7. package/lib/chunk-F2LY4TEI.mjs +272 -0
  8. package/lib/chunk-F2LY4TEI.mjs.map +1 -0
  9. package/lib/{chunk-GG2WD6TA.mjs → chunk-JJ3AQ6G5.mjs} +9 -3
  10. package/lib/{chunk-GG2WD6TA.mjs.map → chunk-JJ3AQ6G5.mjs.map} +1 -1
  11. package/lib/{chunk-EBB4RNUG.mjs → chunk-PIQISEGW.mjs} +2 -2
  12. package/lib/{chunk-FDBBTNCI.mjs → chunk-Q4KQD2NB.mjs} +117 -5
  13. package/lib/chunk-Q4KQD2NB.mjs.map +1 -0
  14. package/lib/{chunk-Y4RGUAM2.mjs → chunk-V6KLFEHC.mjs} +105 -34
  15. package/lib/chunk-V6KLFEHC.mjs.map +1 -0
  16. package/lib/chunk-VQY57NOV.mjs +60 -0
  17. package/lib/chunk-VQY57NOV.mjs.map +1 -0
  18. package/lib/counter-maintenance.handler.mjs +4 -4
  19. package/lib/counter-reconciliation.handler.js +2 -2
  20. package/lib/counter-reconciliation.handler.js.map +1 -1
  21. package/lib/counter-reconciliation.handler.mjs +9 -267
  22. package/lib/counter-reconciliation.handler.mjs.map +1 -1
  23. package/lib/index.d.mts +117 -2
  24. package/lib/index.d.ts +117 -2
  25. package/lib/index.js +6454 -6243
  26. package/lib/index.js.map +1 -1
  27. package/lib/index.mjs +106 -4
  28. package/lib/index.mjs.map +1 -1
  29. package/lib/pre-token-generation.handler.js +28 -19
  30. package/lib/pre-token-generation.handler.js.map +1 -1
  31. package/lib/pre-token-generation.handler.mjs +4 -5
  32. package/lib/pre-token-generation.handler.mjs.map +1 -1
  33. package/lib/provision-default-workspace.handler.js +22 -19
  34. package/lib/provision-default-workspace.handler.js.map +1 -1
  35. package/lib/provision-default-workspace.handler.mjs +3 -4
  36. package/lib/provision-default-workspace.handler.mjs.map +1 -1
  37. package/lib/rest-api-lambda.handler.js +400 -214
  38. package/lib/rest-api-lambda.handler.js.map +1 -1
  39. package/lib/rest-api-lambda.handler.mjs +243 -171
  40. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  41. package/lib/seed-demo-data.handler.d.mts +19 -0
  42. package/lib/seed-demo-data.handler.d.ts +19 -0
  43. package/lib/seed-demo-data.handler.js +805 -159
  44. package/lib/seed-demo-data.handler.js.map +1 -1
  45. package/lib/seed-demo-data.handler.mjs +8 -4
  46. package/package.json +3 -3
  47. package/lib/chunk-6HGSR3TG.mjs +0 -123
  48. package/lib/chunk-6HGSR3TG.mjs.map +0 -1
  49. package/lib/chunk-E2OWEBBH.mjs.map +0 -1
  50. package/lib/chunk-FDBBTNCI.mjs.map +0 -1
  51. package/lib/chunk-Y4RGUAM2.mjs.map +0 -1
  52. /package/lib/{chunk-Z4PZSLYY.mjs.map → chunk-3M4QTQH6.mjs.map} +0 -0
  53. /package/lib/{chunk-XNUCKVSE.mjs.map → chunk-7GMTHOYF.mjs.map} +0 -0
  54. /package/lib/{chunk-EBB4RNUG.mjs.map → chunk-PIQISEGW.mjs.map} +0 -0
@@ -6634,19 +6634,349 @@ var ConflictError = class extends DomainError {
6634
6634
  }
6635
6635
  };
6636
6636
 
6637
- // src/data/operations/control/membership/membership-create-operation.ts
6638
- var import_types5 = require("@openhi/types");
6639
- var import_ulid = require("ulid");
6637
+ // src/data/operations/control/counters/counter-apply-operation.ts
6638
+ var COUNTER_TARGET = {
6639
+ Tenant: "Tenant",
6640
+ Workspace: "Workspace",
6641
+ User: "User"
6642
+ };
6640
6643
 
6641
- // src/data/operations/control/membership/membership-user-projection.ts
6644
+ // src/data/operations/control/counters/role-admin-classification.ts
6645
+ function isAdminRoleAssignment(input) {
6646
+ if (codeIsAdminTier(input.roleLevel)) {
6647
+ return true;
6648
+ }
6649
+ if (idMatchesAdmin(input.roleId)) {
6650
+ return true;
6651
+ }
6652
+ return false;
6653
+ }
6654
+ function codeIsAdminTier(roleLevel) {
6655
+ if (typeof roleLevel !== "string" || roleLevel.length === 0) {
6656
+ return false;
6657
+ }
6658
+ const lower = roleLevel.toLowerCase();
6659
+ return lower === "admin" || lower.endsWith("admin");
6660
+ }
6661
+ function idMatchesAdmin(roleId) {
6662
+ if (typeof roleId !== "string" || roleId.length === 0) {
6663
+ return false;
6664
+ }
6665
+ return roleId.toLowerCase().includes("admin");
6666
+ }
6667
+
6668
+ // src/data/operations/control/control-event-publisher.ts
6669
+ var import_client_eventbridge = require("@aws-sdk/client-eventbridge");
6670
+ var import_workflows2 = __toESM(require_lib());
6671
+ var CONTROL_EVENT_BUS_NAME_ENV_VAR = "CONTROL_EVENT_BUS_NAME";
6672
+ var cachedClient;
6673
+ function getClient() {
6674
+ if (!cachedClient) {
6675
+ cachedClient = new import_client_eventbridge.EventBridgeClient({
6676
+ region: process.env.AWS_REGION ?? "us-east-1"
6677
+ });
6678
+ }
6679
+ return cachedClient;
6680
+ }
6681
+ function actorFromContext(context) {
6682
+ return {
6683
+ ohi_tid: context.tenantId,
6684
+ ohi_wid: context.workspaceId,
6685
+ ohi_uid: context.actorId,
6686
+ ohi_uname: context.actorName
6687
+ };
6688
+ }
6689
+ async function publishControlEvent(entry, payload, context) {
6690
+ const busName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];
6691
+ if (!busName) {
6692
+ return;
6693
+ }
6694
+ try {
6695
+ await (0, import_workflows2.publishWorkflowEvent)(
6696
+ getClient(),
6697
+ entry,
6698
+ payload,
6699
+ { actor: actorFromContext(context) },
6700
+ { busNameByPlane: { [import_workflows2.OPENHI_CONTROL_SOURCE]: busName } }
6701
+ );
6702
+ } catch (err) {
6703
+ console.error(`control-event publish failed for ${entry.detailType}:`, err);
6704
+ }
6705
+ }
6706
+ async function publishMembershipCreated(context, detail) {
6707
+ await publishControlEvent(import_workflows2.ControlPlaneMembershipCreatedV1, detail, context);
6708
+ }
6709
+ async function publishRoleAssignmentCreated(context, detail) {
6710
+ await publishControlEvent(
6711
+ import_workflows2.ControlPlaneRoleAssignmentCreatedV1,
6712
+ detail,
6713
+ context
6714
+ );
6715
+ }
6716
+ async function publishWorkspaceCreated(context, detail) {
6717
+ await publishControlEvent(import_workflows2.ControlPlaneWorkspaceCreatedV1, detail, context);
6718
+ }
6719
+ function extractRoleLevel(resource) {
6720
+ const code = resource?.code;
6721
+ const first = code?.coding?.[0]?.code;
6722
+ return typeof first === "string" && first.length > 0 ? first : void 0;
6723
+ }
6724
+
6725
+ // src/data/operations/control/membership/membership-list-by-user-operation.ts
6726
+ function buildSkPrefix(mode, tenantId) {
6727
+ switch (mode) {
6728
+ case "tenant":
6729
+ return "MEMBERSHIP#TENANT#";
6730
+ case "workspace":
6731
+ return "MEMBERSHIP#WORKSPACE#";
6732
+ case "workspaceInTenant":
6733
+ return `MEMBERSHIP#WORKSPACE#TID#${tenantId}#`;
6734
+ case "all":
6735
+ default:
6736
+ return "MEMBERSHIP#";
6737
+ }
6738
+ }
6739
+
6740
+ // src/data/operations/control/membership/membership-count-by-user-operation.ts
6741
+ async function countMembershipsByUserOperation(params) {
6742
+ const { userId, mode = "all", tenantId, tableName } = params;
6743
+ if (mode === "workspaceInTenant" && !tenantId) {
6744
+ throw new Error(
6745
+ 'countMembershipsByUserOperation: tenantId is required when mode === "workspaceInTenant"'
6746
+ );
6747
+ }
6748
+ const service = getDynamoControlService(tableName);
6749
+ const skPrefix = buildSkPrefix(mode, tenantId);
6750
+ const result = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: skPrefix }).go({ pages: "all", attributes: ["membershipId"] });
6751
+ return (result.data ?? []).length;
6752
+ }
6753
+
6754
+ // src/data/operations/control/membership/membership-list-by-workspace-operation.ts
6755
+ async function membershipListByWorkspaceOperation(params) {
6756
+ const {
6757
+ tenantId,
6758
+ workspaceId,
6759
+ cursor = null,
6760
+ limit,
6761
+ order,
6762
+ tableName
6763
+ } = params;
6764
+ const service = getDynamoControlService(tableName);
6765
+ const goOptions = {
6766
+ cursor
6767
+ };
6768
+ if (limit !== void 0) {
6769
+ goOptions.limit = limit;
6770
+ }
6771
+ if (order !== void 0) {
6772
+ goOptions.order = order;
6773
+ }
6774
+ const result = await service.entities.membershipWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: "MEMBERSHIP#" }).go(goOptions);
6775
+ const items = (result.data ?? []).map((row) => ({
6776
+ tenantId: row.tenantId,
6777
+ workspaceId: row.workspaceId,
6778
+ sk: row.sk,
6779
+ userId: row.userId,
6780
+ membershipId: row.membershipId,
6781
+ summary: row.summary,
6782
+ vid: row.vid,
6783
+ lastUpdated: row.lastUpdated,
6784
+ denormalizedUserName: row.denormalizedUserName
6785
+ }));
6786
+ return { items, cursor: result.cursor ?? null };
6787
+ }
6788
+
6789
+ // src/data/operations/data-operations-common.ts
6642
6790
  var import_types3 = require("@openhi/types");
6791
+
6792
+ // src/lib/compression.ts
6793
+ var import_node_zlib = require("zlib");
6794
+ var ENVELOPE_VERSION = 1;
6795
+ var COMPRESSION_ALGOS = {
6796
+ NONE: "none",
6797
+ GZIP: "gzip",
6798
+ BROTLI: "brotli",
6799
+ DEFLATE: "deflate"
6800
+ };
6801
+ function compressResource(jsonString, options) {
6802
+ const algo = options?.algo ?? COMPRESSION_ALGOS.GZIP;
6803
+ if (algo === COMPRESSION_ALGOS.NONE) {
6804
+ const envelope2 = {
6805
+ v: ENVELOPE_VERSION,
6806
+ algo: COMPRESSION_ALGOS.NONE,
6807
+ payload: jsonString
6808
+ };
6809
+ return JSON.stringify(envelope2);
6810
+ }
6811
+ const buf = Buffer.from(jsonString, "utf-8");
6812
+ const payload = (0, import_node_zlib.gzipSync)(buf).toString("base64");
6813
+ const envelope = {
6814
+ v: ENVELOPE_VERSION,
6815
+ algo: COMPRESSION_ALGOS.GZIP,
6816
+ payload
6817
+ };
6818
+ return JSON.stringify(envelope);
6819
+ }
6820
+
6821
+ // src/data/audit-meta.ts
6822
+ var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
6823
+ function mergeAuditIntoMeta(meta, audit) {
6824
+ const existing = meta ?? {};
6825
+ const ext = [
6826
+ ...Array.isArray(existing.extension) ? existing.extension : []
6827
+ ];
6828
+ const byUrl = new Map(ext.map((e) => [e.url, e]));
6829
+ function set(url, value, type) {
6830
+ if (value == null) return;
6831
+ byUrl.set(url, { url, [type]: value });
6832
+ }
6833
+ set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
6834
+ set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
6835
+ set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
6836
+ set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
6837
+ set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
6838
+ set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
6839
+ set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
6840
+ set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
6841
+ set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
6842
+ return { ...existing, extension: Array.from(byUrl.values()) };
6843
+ }
6844
+
6845
+ // src/data/operations/data-operations-common.ts
6846
+ var DATA_ENTITY_SK = "CURRENT";
6847
+ var BATCH_GET_MAX_ATTEMPTS = 3;
6848
+ var BATCH_GET_BASE_BACKOFF_MS = 50;
6849
+ async function batchGetWithRetry(entity, keys, options) {
6850
+ if (keys.length === 0) return [];
6851
+ const collected = [];
6852
+ let pending = keys;
6853
+ let attempt = 0;
6854
+ while (pending.length > 0) {
6855
+ if (attempt > 0) {
6856
+ await new Promise(
6857
+ (resolve) => setTimeout(resolve, BATCH_GET_BASE_BACKOFF_MS * 2 ** (attempt - 1))
6858
+ );
6859
+ }
6860
+ attempt++;
6861
+ const result = await entity.get(pending).go(options?.consistent ? { consistent: true } : void 0);
6862
+ collected.push(...result.data);
6863
+ const unprocessed = result.unprocessed ?? [];
6864
+ if (unprocessed.length === 0) break;
6865
+ if (attempt >= BATCH_GET_MAX_ATTEMPTS) {
6866
+ throw new Error(
6867
+ `BatchGet exhausted retries: ${unprocessed.length} key(s) still unprocessed after ${BATCH_GET_MAX_ATTEMPTS} attempt(s)`
6868
+ );
6869
+ }
6870
+ pending = unprocessed;
6871
+ }
6872
+ return collected;
6873
+ }
6874
+ async function dispatchListMode(mode, shardResults, hooks) {
6875
+ if (mode === "count") {
6876
+ let total = 0;
6877
+ for (const shardResult of shardResults) {
6878
+ total += (shardResult.data ?? []).length;
6879
+ }
6880
+ return { entries: [], total };
6881
+ }
6882
+ if (mode === "summary") {
6883
+ const entries2 = [];
6884
+ for (const shardResult of shardResults) {
6885
+ for (const item of shardResult.data ?? []) {
6886
+ if (typeof item.summary !== "string") continue;
6887
+ let parsed;
6888
+ try {
6889
+ parsed = JSON.parse(item.summary);
6890
+ } catch {
6891
+ continue;
6892
+ }
6893
+ entries2.push(hooks.buildSummaryEntry(item.id, parsed));
6894
+ }
6895
+ }
6896
+ return { entries: entries2, total: entries2.length };
6897
+ }
6898
+ const orderedIds = [];
6899
+ for (const shardResult of shardResults) {
6900
+ for (const item of shardResult.data ?? []) {
6901
+ orderedIds.push(item.id);
6902
+ }
6903
+ }
6904
+ if (orderedIds.length === 0) return { entries: [], total: 0 };
6905
+ const items = await hooks.hydrate(orderedIds);
6906
+ const byId = new Map(items.map((item) => [hooks.getId(item), item]));
6907
+ const entries = [];
6908
+ for (const id of orderedIds) {
6909
+ const item = byId.get(id);
6910
+ if (!item) continue;
6911
+ entries.push(hooks.buildEntry(id, item));
6912
+ }
6913
+ return { entries, total: entries.length };
6914
+ }
6915
+ async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourceWithAudit, fallbackDate) {
6916
+ const lastUpdated = resourceWithAudit.meta?.lastUpdated ?? fallbackDate ?? (/* @__PURE__ */ new Date()).toISOString();
6917
+ const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
6918
+ const resourceLike = resourceWithAudit;
6919
+ const summary = JSON.stringify((0, import_types3.extractSummary)(resourceLike));
6920
+ const gsi1sk = (0, import_types3.extractSortKey)(resourceLike);
6921
+ await entity.put({
6922
+ sk: DATA_ENTITY_SK,
6923
+ tenantId,
6924
+ workspaceId,
6925
+ id,
6926
+ resource: compressResource(JSON.stringify(resourceWithAudit)),
6927
+ summary,
6928
+ vid,
6929
+ lastUpdated,
6930
+ gsi1sk
6931
+ }).go();
6932
+ return {
6933
+ id,
6934
+ resource: resourceWithAudit
6935
+ };
6936
+ }
6937
+
6938
+ // src/data/operations/control/membership/membership-list-operation.ts
6939
+ var SK = "CURRENT";
6940
+ async function listMembershipsOperation(params) {
6941
+ const { context, tableName, mode = "full" } = params;
6942
+ const tenantId = context.tenantId;
6943
+ const service = getDynamoControlService(tableName);
6944
+ const shardResults = await Promise.all(
6945
+ Array.from(
6946
+ { length: SHARD_COUNT },
6947
+ (_, shard) => service.entities.membership.query.gsi1({ tenantId, gsi1Shard: String(shard) }).go()
6948
+ )
6949
+ );
6950
+ return dispatchListMode(mode, shardResults, {
6951
+ hydrate: (orderedIds) => batchGetWithRetry(
6952
+ service.entities.membership,
6953
+ orderedIds.map((id) => ({ tenantId, id, sk: SK }))
6954
+ ),
6955
+ getId: (item) => item.id,
6956
+ buildEntry: (id, item) => ({
6957
+ id,
6958
+ resource: {
6959
+ resourceType: "Membership",
6960
+ id,
6961
+ ...JSON.parse(item.resource)
6962
+ }
6963
+ }),
6964
+ buildSummaryEntry: (id, parsed) => ({
6965
+ id,
6966
+ resource: { resourceType: "Membership", id, ...parsed }
6967
+ })
6968
+ });
6969
+ }
6970
+
6971
+ // src/data/operations/control/membership/membership-user-projection.ts
6972
+ var import_types4 = require("@openhi/types");
6643
6973
  var MISSING_NAME_SENTINEL = "-";
6644
6974
  function buildMembershipUserProjectionSkTenantLane(params) {
6645
- const normalizedTenantName = typeof params.denormalizedTenantName === "string" && params.denormalizedTenantName.length > 0 ? (0, import_types3.normalizeLabel)(params.denormalizedTenantName) : MISSING_NAME_SENTINEL;
6975
+ const normalizedTenantName = typeof params.denormalizedTenantName === "string" && params.denormalizedTenantName.length > 0 ? (0, import_types4.normalizeLabel)(params.denormalizedTenantName) : MISSING_NAME_SENTINEL;
6646
6976
  return `MEMBERSHIP#TENANT#${normalizedTenantName}#TID#${params.tenantId}#${params.membershipId}`;
6647
6977
  }
6648
6978
  function buildMembershipUserProjectionSkWorkspaceLane(params) {
6649
- const normalizedWorkspaceName = typeof params.denormalizedWorkspaceName === "string" && params.denormalizedWorkspaceName.length > 0 ? (0, import_types3.normalizeLabel)(params.denormalizedWorkspaceName) : MISSING_NAME_SENTINEL;
6979
+ const normalizedWorkspaceName = typeof params.denormalizedWorkspaceName === "string" && params.denormalizedWorkspaceName.length > 0 ? (0, import_types4.normalizeLabel)(params.denormalizedWorkspaceName) : MISSING_NAME_SENTINEL;
6650
6980
  return `MEMBERSHIP#WORKSPACE#TID#${params.tenantId}#${normalizedWorkspaceName}#WID#${params.workspaceId}#${params.membershipId}`;
6651
6981
  }
6652
6982
  function buildMembershipUserProjectionItem(input) {
@@ -6692,11 +7022,454 @@ function extractReferenceSlug(resource, fieldName) {
6692
7022
  return tail.length > 0 ? tail : void 0;
6693
7023
  }
6694
7024
 
7025
+ // src/data/operations/control/roleassignment/roleassignment-list-by-workspace-operation.ts
7026
+ function buildSkPrefix2(roleId) {
7027
+ if (roleId === void 0 || roleId.length === 0) {
7028
+ return "ROLEASSIGNMENT#";
7029
+ }
7030
+ return `ROLEASSIGNMENT#${roleId}#`;
7031
+ }
7032
+ async function roleAssignmentListByWorkspaceOperation(params) {
7033
+ const {
7034
+ tenantId,
7035
+ workspaceId,
7036
+ roleId,
7037
+ cursor = null,
7038
+ limit,
7039
+ order,
7040
+ tableName
7041
+ } = params;
7042
+ const service = getDynamoControlService(tableName);
7043
+ const skPrefix = buildSkPrefix2(roleId);
7044
+ const goOptions = {
7045
+ cursor
7046
+ };
7047
+ if (limit !== void 0) {
7048
+ goOptions.limit = limit;
7049
+ }
7050
+ if (order !== void 0) {
7051
+ goOptions.order = order;
7052
+ }
7053
+ const result = await service.entities.roleAssignmentWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: skPrefix }).go(goOptions);
7054
+ const items = (result.data ?? []).map((row) => ({
7055
+ tenantId: row.tenantId,
7056
+ workspaceId: row.workspaceId,
7057
+ sk: row.sk,
7058
+ userId: row.userId,
7059
+ roleId: row.roleId,
7060
+ roleAssignmentId: row.roleAssignmentId,
7061
+ summary: row.summary,
7062
+ vid: row.vid,
7063
+ lastUpdated: row.lastUpdated,
7064
+ denormalizedUserName: row.denormalizedUserName,
7065
+ denormalizedRoleName: row.denormalizedRoleName
7066
+ }));
7067
+ return { items, cursor: result.cursor ?? null };
7068
+ }
7069
+
7070
+ // src/data/operations/control/workspace/workspace-list-operation.ts
7071
+ var SK2 = "CURRENT";
7072
+ function counterValue(value) {
7073
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
7074
+ }
7075
+ async function listWorkspacesOperation(params) {
7076
+ const { context, tableName, mode = "full" } = params;
7077
+ const { tenantId } = context;
7078
+ const service = getDynamoControlService(tableName);
7079
+ const shardResults = await Promise.all(
7080
+ Array.from(
7081
+ { length: SHARD_COUNT },
7082
+ (_, shard) => service.entities.workspace.query.gsi1({ tenantId, gsi1Shard: String(shard) }).go()
7083
+ )
7084
+ );
7085
+ return dispatchListMode(mode, shardResults, {
7086
+ hydrate: (orderedIds) => batchGetWithRetry(
7087
+ service.entities.workspace,
7088
+ orderedIds.map((id) => ({ tenantId, id, sk: SK2 }))
7089
+ ),
7090
+ getId: (item) => item.id,
7091
+ // FULL mode (admin list default): read the ADR-028 counters off the
7092
+ // canonical record hydrated by BatchGet and expose them as
7093
+ // `resource.counts`. Missing counters render as 0.
7094
+ buildEntry: (id, item) => ({
7095
+ id,
7096
+ resource: {
7097
+ resourceType: "Workspace",
7098
+ id,
7099
+ ...JSON.parse(item.resource),
7100
+ counts: {
7101
+ usersInWorkspace: counterValue(item.usersInWorkspace),
7102
+ adminUsersInWorkspace: counterValue(item.adminUsersInWorkspace),
7103
+ normalUsersInWorkspace: counterValue(item.normalUsersInWorkspace)
7104
+ }
7105
+ }
7106
+ }),
7107
+ // SUMMARY mode reads only the GSI1 `summary` projection (no
7108
+ // counters); surface zeros so the shape stays uniform.
7109
+ buildSummaryEntry: (id, parsed) => ({
7110
+ id,
7111
+ resource: {
7112
+ resourceType: "Workspace",
7113
+ id,
7114
+ ...parsed,
7115
+ counts: {
7116
+ usersInWorkspace: 0,
7117
+ adminUsersInWorkspace: 0,
7118
+ normalUsersInWorkspace: 0
7119
+ }
7120
+ }
7121
+ })
7122
+ });
7123
+ }
7124
+
7125
+ // src/data/operations/control/counters/counter-reconcile-operation.ts
7126
+ function counterValue2(value) {
7127
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
7128
+ }
7129
+ function reconcileContext(tenantId) {
7130
+ return {
7131
+ tenantId,
7132
+ workspaceId: "",
7133
+ date: (/* @__PURE__ */ new Date()).toISOString(),
7134
+ actorId: "counter-reconciliation",
7135
+ actorName: "Counter Reconciliation Job",
7136
+ actorType: "internal-system",
7137
+ source: "step-function"
7138
+ };
7139
+ }
7140
+ async function reconcileTenantCountersOperation(params) {
7141
+ const { tenantId, tableName } = params;
7142
+ const service = getDynamoControlService(tableName);
7143
+ const context = reconcileContext(tenantId);
7144
+ const workspacesResult = await listWorkspacesOperation({
7145
+ context,
7146
+ tableName,
7147
+ mode: "count"
7148
+ });
7149
+ const workspacesInTenant = workspacesResult.total;
7150
+ const memberships = await listMembershipsOperation({
7151
+ context,
7152
+ tableName,
7153
+ mode: "full"
7154
+ });
7155
+ let usersInTenant = 0;
7156
+ for (const entry of memberships.entries) {
7157
+ const workspaceSlug = extractReferenceSlug(entry.resource, "workspace");
7158
+ if (workspaceSlug === void 0) {
7159
+ usersInTenant += 1;
7160
+ }
7161
+ }
7162
+ const current = await service.entities.tenant.get({ tenantId, sk: "CURRENT" }).go();
7163
+ const drift = [];
7164
+ const recomputed = {
7165
+ usersInTenant,
7166
+ workspacesInTenant
7167
+ };
7168
+ for (const counter of Object.keys(recomputed)) {
7169
+ const oldValue = counterValue2(current.data?.[counter]);
7170
+ const newValue = recomputed[counter];
7171
+ if (oldValue !== newValue) {
7172
+ drift.push({
7173
+ target: COUNTER_TARGET.Tenant,
7174
+ id: tenantId,
7175
+ counter,
7176
+ old: oldValue,
7177
+ new: newValue
7178
+ });
7179
+ }
7180
+ }
7181
+ if (drift.length > 0) {
7182
+ await service.entities.tenant.patch({ tenantId, sk: "CURRENT" }).set(recomputed).go();
7183
+ }
7184
+ return { drift };
7185
+ }
7186
+ async function reconcileWorkspaceCountersOperation(params) {
7187
+ const { tenantId, workspaceId, tableName } = params;
7188
+ const service = getDynamoControlService(tableName);
7189
+ let usersInWorkspace = 0;
7190
+ let membershipCursor = null;
7191
+ do {
7192
+ const page = await membershipListByWorkspaceOperation({
7193
+ tenantId,
7194
+ workspaceId,
7195
+ cursor: membershipCursor,
7196
+ tableName
7197
+ });
7198
+ usersInWorkspace += page.items.length;
7199
+ membershipCursor = page.cursor;
7200
+ } while (membershipCursor !== null);
7201
+ let adminUsersInWorkspace = 0;
7202
+ let normalUsersInWorkspace = 0;
7203
+ let roleAssignmentCursor = null;
7204
+ do {
7205
+ const page = await roleAssignmentListByWorkspaceOperation({
7206
+ tenantId,
7207
+ workspaceId,
7208
+ cursor: roleAssignmentCursor,
7209
+ tableName
7210
+ });
7211
+ for (const item of page.items) {
7212
+ const roleLevel = await readRoleLevel(
7213
+ service,
7214
+ tenantId,
7215
+ item.roleAssignmentId
7216
+ );
7217
+ if (isAdminRoleAssignment({ roleLevel, roleId: item.roleId })) {
7218
+ adminUsersInWorkspace += 1;
7219
+ } else {
7220
+ normalUsersInWorkspace += 1;
7221
+ }
7222
+ }
7223
+ roleAssignmentCursor = page.cursor;
7224
+ } while (roleAssignmentCursor !== null);
7225
+ const current = await service.entities.workspace.get({ tenantId, id: workspaceId, sk: "CURRENT" }).go();
7226
+ const drift = [];
7227
+ const recomputed = {
7228
+ usersInWorkspace,
7229
+ adminUsersInWorkspace,
7230
+ normalUsersInWorkspace
7231
+ };
7232
+ for (const counter of Object.keys(recomputed)) {
7233
+ const oldValue = counterValue2(current.data?.[counter]);
7234
+ const newValue = recomputed[counter];
7235
+ if (oldValue !== newValue) {
7236
+ drift.push({
7237
+ target: COUNTER_TARGET.Workspace,
7238
+ id: workspaceId,
7239
+ tenantId,
7240
+ counter,
7241
+ old: oldValue,
7242
+ new: newValue
7243
+ });
7244
+ }
7245
+ }
7246
+ if (drift.length > 0) {
7247
+ await service.entities.workspace.patch({ tenantId, id: workspaceId, sk: "CURRENT" }).set(recomputed).go();
7248
+ }
7249
+ return { drift };
7250
+ }
7251
+ async function reconcileUserCountersOperation(params) {
7252
+ const { userId, tableName } = params;
7253
+ const service = getDynamoControlService(tableName);
7254
+ const tenantsForUser = await countMembershipsByUserOperation({
7255
+ userId,
7256
+ mode: "tenant",
7257
+ tableName
7258
+ });
7259
+ const workspacesForUser = await countMembershipsByUserOperation({
7260
+ userId,
7261
+ mode: "workspace",
7262
+ tableName
7263
+ });
7264
+ const current = await service.entities.user.get({ id: userId, sk: "CURRENT" }).go();
7265
+ const drift = [];
7266
+ const recomputed = {
7267
+ tenantsForUser,
7268
+ workspacesForUser
7269
+ };
7270
+ for (const counter of Object.keys(recomputed)) {
7271
+ const oldValue = counterValue2(current.data?.[counter]);
7272
+ const newValue = recomputed[counter];
7273
+ if (oldValue !== newValue) {
7274
+ drift.push({
7275
+ target: COUNTER_TARGET.User,
7276
+ id: userId,
7277
+ counter,
7278
+ old: oldValue,
7279
+ new: newValue
7280
+ });
7281
+ }
7282
+ }
7283
+ if (drift.length > 0) {
7284
+ await service.entities.user.patch({ id: userId, sk: "CURRENT" }).set(recomputed).go();
7285
+ }
7286
+ return { drift };
7287
+ }
7288
+ async function readRoleLevel(service, tenantId, roleAssignmentId) {
7289
+ const response = await service.entities.roleAssignment.get({ tenantId, id: roleAssignmentId, sk: "CURRENT" }).go();
7290
+ if (!response.data) {
7291
+ return void 0;
7292
+ }
7293
+ const resource = JSON.parse(response.data.resource);
7294
+ return extractRoleLevel(resource);
7295
+ }
7296
+
7297
+ // src/data/operations/control/tenant/tenant-list-operation.ts
7298
+ var SK3 = "CURRENT";
7299
+ function counterValue3(value) {
7300
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
7301
+ }
7302
+ async function listTenantsOperation(params) {
7303
+ const { tableName, mode = "full" } = params;
7304
+ const service = getDynamoControlService(tableName);
7305
+ const shardResults = await Promise.all(
7306
+ Array.from(
7307
+ { length: SHARD_COUNT },
7308
+ (_, shard) => service.entities.tenant.query.gsi1({ gsi1Shard: String(shard) }).go()
7309
+ )
7310
+ );
7311
+ return dispatchListMode(mode, shardResults, {
7312
+ hydrate: (orderedIds) => batchGetWithRetry(
7313
+ service.entities.tenant,
7314
+ orderedIds.map((id) => ({ tenantId: id, sk: SK3 }))
7315
+ ),
7316
+ getId: (item) => item.id,
7317
+ // FULL mode (admin list default): read the ADR-028 counters off the
7318
+ // canonical record hydrated by BatchGet and expose them as
7319
+ // `resource.counts`. Missing counters render as 0.
7320
+ buildEntry: (id, item) => ({
7321
+ id,
7322
+ resource: {
7323
+ resourceType: "Tenant",
7324
+ id,
7325
+ ...JSON.parse(item.resource),
7326
+ counts: {
7327
+ usersInTenant: counterValue3(item.usersInTenant),
7328
+ workspacesInTenant: counterValue3(item.workspacesInTenant)
7329
+ }
7330
+ }
7331
+ }),
7332
+ // SUMMARY mode reads only the GSI1 `summary` projection, which does
7333
+ // not carry the counters; surface zeros so the shape stays uniform.
7334
+ buildSummaryEntry: (id, parsed) => ({
7335
+ id,
7336
+ resource: {
7337
+ resourceType: "Tenant",
7338
+ id,
7339
+ ...parsed,
7340
+ counts: { usersInTenant: 0, workspacesInTenant: 0 }
7341
+ }
7342
+ })
7343
+ });
7344
+ }
7345
+
7346
+ // src/data/operations/control/user/user-list-operation.ts
7347
+ var SK4 = "CURRENT";
7348
+ function counterValue4(value) {
7349
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
7350
+ }
7351
+ async function listUsersOperation(params) {
7352
+ const { tableName, mode = "full" } = params;
7353
+ const service = getDynamoControlService(tableName);
7354
+ const shardResults = await Promise.all(
7355
+ Array.from(
7356
+ { length: SHARD_COUNT },
7357
+ (_, shard) => service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go()
7358
+ )
7359
+ );
7360
+ return dispatchListMode(mode, shardResults, {
7361
+ hydrate: (orderedIds) => batchGetWithRetry(
7362
+ service.entities.user,
7363
+ orderedIds.map((id) => ({ id, sk: SK4 }))
7364
+ ),
7365
+ getId: (item) => item.id,
7366
+ // FULL mode (admin list default): read the ADR-028 counters off the
7367
+ // canonical record hydrated by BatchGet and expose them as
7368
+ // `resource.counts`. Missing counters render as 0.
7369
+ buildEntry: (id, item) => ({
7370
+ id,
7371
+ resource: {
7372
+ resourceType: "User",
7373
+ id,
7374
+ ...JSON.parse(item.resource),
7375
+ counts: {
7376
+ tenantsForUser: counterValue4(item.tenantsForUser),
7377
+ workspacesForUser: counterValue4(item.workspacesForUser)
7378
+ }
7379
+ }
7380
+ }),
7381
+ // SUMMARY mode reads only the GSI1 `summary` projection (no
7382
+ // counters); surface zeros so the shape stays uniform.
7383
+ buildSummaryEntry: (id, parsed) => ({
7384
+ id,
7385
+ resource: {
7386
+ resourceType: "User",
7387
+ id,
7388
+ ...parsed,
7389
+ counts: { tenantsForUser: 0, workspacesForUser: 0 }
7390
+ }
7391
+ })
7392
+ });
7393
+ }
7394
+
7395
+ // src/data/operations/control/counters/counter-reconcile-driver.ts
7396
+ function driverContext(tenantId) {
7397
+ return {
7398
+ tenantId,
7399
+ workspaceId: "",
7400
+ date: (/* @__PURE__ */ new Date()).toISOString(),
7401
+ actorId: "counter-reconciliation",
7402
+ actorName: "Counter Reconciliation Job",
7403
+ actorType: "internal-system",
7404
+ source: "step-function"
7405
+ };
7406
+ }
7407
+ async function reconcileAllCountersOperation(params = {}) {
7408
+ const { tableName } = params;
7409
+ const drift = [];
7410
+ let tenantsScanned = 0;
7411
+ let workspacesScanned = 0;
7412
+ let usersScanned = 0;
7413
+ const tenants = await listTenantsOperation({
7414
+ context: driverContext(""),
7415
+ tableName,
7416
+ mode: "summary"
7417
+ });
7418
+ for (const tenant of tenants.entries) {
7419
+ tenantsScanned += 1;
7420
+ const tenantResult = await reconcileTenantCountersOperation({
7421
+ tenantId: tenant.id,
7422
+ tableName
7423
+ });
7424
+ drift.push(...tenantResult.drift);
7425
+ const workspaces = await listWorkspacesOperation({
7426
+ context: driverContext(tenant.id),
7427
+ tableName,
7428
+ mode: "summary"
7429
+ });
7430
+ for (const workspace of workspaces.entries) {
7431
+ workspacesScanned += 1;
7432
+ const workspaceResult = await reconcileWorkspaceCountersOperation({
7433
+ tenantId: tenant.id,
7434
+ workspaceId: workspace.id,
7435
+ tableName
7436
+ });
7437
+ drift.push(...workspaceResult.drift);
7438
+ }
7439
+ }
7440
+ const users = await listUsersOperation({
7441
+ context: driverContext(""),
7442
+ tableName,
7443
+ mode: "summary"
7444
+ });
7445
+ for (const user of users.entries) {
7446
+ usersScanned += 1;
7447
+ const userResult = await reconcileUserCountersOperation({
7448
+ userId: user.id,
7449
+ tableName
7450
+ });
7451
+ drift.push(...userResult.drift);
7452
+ }
7453
+ return {
7454
+ drift,
7455
+ scanned: {
7456
+ tenants: tenantsScanned,
7457
+ workspaces: workspacesScanned,
7458
+ users: usersScanned
7459
+ },
7460
+ countersCorrected: drift.length
7461
+ };
7462
+ }
7463
+
7464
+ // src/data/operations/control/membership/membership-create-operation.ts
7465
+ var import_types6 = require("@openhi/types");
7466
+ var import_ulid = require("ulid");
7467
+
6695
7468
  // src/data/operations/control/membership/membership-workspace-projection.ts
6696
- var import_types4 = require("@openhi/types");
7469
+ var import_types5 = require("@openhi/types");
6697
7470
  var MISSING_NAME_SENTINEL2 = "-";
6698
7471
  function buildMembershipWorkspaceProjectionSk(params) {
6699
- const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types4.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL2;
7472
+ const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types5.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL2;
6700
7473
  return `MEMBERSHIP#${normalizedUserName}#USER#${params.userId}#${params.membershipId}`;
6701
7474
  }
6702
7475
  function buildMembershipWorkspaceProjectionItem(input) {
@@ -6724,63 +7497,6 @@ function buildMembershipWorkspaceProjectionItem(input) {
6724
7497
  };
6725
7498
  }
6726
7499
 
6727
- // src/data/operations/control/control-event-publisher.ts
6728
- var import_client_eventbridge = require("@aws-sdk/client-eventbridge");
6729
- var import_workflows2 = __toESM(require_lib());
6730
- var CONTROL_EVENT_BUS_NAME_ENV_VAR = "CONTROL_EVENT_BUS_NAME";
6731
- var cachedClient;
6732
- function getClient() {
6733
- if (!cachedClient) {
6734
- cachedClient = new import_client_eventbridge.EventBridgeClient({
6735
- region: process.env.AWS_REGION ?? "us-east-1"
6736
- });
6737
- }
6738
- return cachedClient;
6739
- }
6740
- function actorFromContext(context) {
6741
- return {
6742
- ohi_tid: context.tenantId,
6743
- ohi_wid: context.workspaceId,
6744
- ohi_uid: context.actorId,
6745
- ohi_uname: context.actorName
6746
- };
6747
- }
6748
- async function publishControlEvent(entry, payload, context) {
6749
- const busName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];
6750
- if (!busName) {
6751
- return;
6752
- }
6753
- try {
6754
- await (0, import_workflows2.publishWorkflowEvent)(
6755
- getClient(),
6756
- entry,
6757
- payload,
6758
- { actor: actorFromContext(context) },
6759
- { busNameByPlane: { [import_workflows2.OPENHI_CONTROL_SOURCE]: busName } }
6760
- );
6761
- } catch (err) {
6762
- console.error(`control-event publish failed for ${entry.detailType}:`, err);
6763
- }
6764
- }
6765
- async function publishMembershipCreated(context, detail) {
6766
- await publishControlEvent(import_workflows2.ControlPlaneMembershipCreatedV1, detail, context);
6767
- }
6768
- async function publishRoleAssignmentCreated(context, detail) {
6769
- await publishControlEvent(
6770
- import_workflows2.ControlPlaneRoleAssignmentCreatedV1,
6771
- detail,
6772
- context
6773
- );
6774
- }
6775
- async function publishWorkspaceCreated(context, detail) {
6776
- await publishControlEvent(import_workflows2.ControlPlaneWorkspaceCreatedV1, detail, context);
6777
- }
6778
- function extractRoleLevel(resource) {
6779
- const code = resource?.code;
6780
- const first = code?.coding?.[0]?.code;
6781
- return typeof first === "string" && first.length > 0 ? first : void 0;
6782
- }
6783
-
6784
7500
  // src/data/operations/control/denormalized-display-names.ts
6785
7501
  function extractDenormalizedReferenceDisplay(resource, fieldName) {
6786
7502
  const field = resource[fieldName];
@@ -6925,12 +7641,12 @@ async function createMembershipOperation(params) {
6925
7641
  const resource = { resourceType: "Membership", id, ...parsedResource };
6926
7642
  let linkedDataIdentityRef;
6927
7643
  try {
6928
- const ext = (0, import_types5.assertLinkedDataIdentityCardinality)(
7644
+ const ext = (0, import_types6.assertLinkedDataIdentityCardinality)(
6929
7645
  resource
6930
7646
  );
6931
7647
  linkedDataIdentityRef = ext?.valueReference?.reference;
6932
7648
  } catch (e) {
6933
- if (e instanceof import_types5.LinkedDataIdentityCardinalityError) {
7649
+ if (e instanceof import_types6.LinkedDataIdentityCardinalityError) {
6934
7650
  throw new ValidationError(e.message, { cause: e });
6935
7651
  }
6936
7652
  throw e;
@@ -6948,7 +7664,7 @@ async function createMembershipOperation(params) {
6948
7664
  resourceRecord,
6949
7665
  "workspace"
6950
7666
  );
6951
- const summary = JSON.stringify((0, import_types5.extractSummary)(resource));
7667
+ const summary = JSON.stringify((0, import_types6.extractSummary)(resource));
6952
7668
  const userIdFromResource = extractReferenceSlug(resourceRecord, "user");
6953
7669
  const workspaceIdFromResource = extractReferenceSlug(
6954
7670
  resourceRecord,
@@ -7046,18 +7762,18 @@ async function getRoleByIdOperation(params) {
7046
7762
  }
7047
7763
 
7048
7764
  // src/data/operations/control/roleassignment/roleassignment-create-operation.ts
7049
- var import_types8 = require("@openhi/types");
7765
+ var import_types9 = require("@openhi/types");
7050
7766
  var import_ulid2 = require("ulid");
7051
7767
 
7052
7768
  // src/data/operations/control/roleassignment/roleassignment-user-projection.ts
7053
- var import_types6 = require("@openhi/types");
7769
+ var import_types7 = require("@openhi/types");
7054
7770
  var MISSING_NAME_SENTINEL3 = "-";
7055
7771
  function buildRoleAssignmentUserProjectionSkTenantLane(params) {
7056
- const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types6.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
7772
+ const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types7.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
7057
7773
  return `ROLEASSIGNMENT#TENANT#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#${params.roleAssignmentId}`;
7058
7774
  }
7059
7775
  function buildRoleAssignmentUserProjectionSkWorkspaceLane(params) {
7060
- const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types6.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
7776
+ const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types7.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
7061
7777
  return `ROLEASSIGNMENT#WORKSPACE#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#WID#${params.workspaceId}#${params.roleAssignmentId}`;
7062
7778
  }
7063
7779
  function buildRoleAssignmentUserProjectionItem(input) {
@@ -7110,10 +7826,10 @@ function extractReferenceSlug2(resource, fieldName) {
7110
7826
  }
7111
7827
 
7112
7828
  // src/data/operations/control/roleassignment/roleassignment-workspace-projection.ts
7113
- var import_types7 = require("@openhi/types");
7829
+ var import_types8 = require("@openhi/types");
7114
7830
  var MISSING_NAME_SENTINEL4 = "-";
7115
7831
  function buildRoleAssignmentWorkspaceProjectionSk(params) {
7116
- const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types7.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL4;
7832
+ const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types8.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL4;
7117
7833
  return `ROLEASSIGNMENT#${params.roleId}#${normalizedUserName}#USER#${params.userId}#${params.roleAssignmentId}`;
7118
7834
  }
7119
7835
  function buildRoleAssignmentWorkspaceProjectionItem(input) {
@@ -7187,7 +7903,7 @@ async function createRoleAssignmentOperation(params) {
7187
7903
  resourceRecord,
7188
7904
  "role"
7189
7905
  );
7190
- const summary = JSON.stringify((0, import_types8.extractSummary)(resource));
7906
+ const summary = JSON.stringify((0, import_types9.extractSummary)(resource));
7191
7907
  const userIdFromResource = extractReferenceSlug2(resourceRecord, "user");
7192
7908
  const roleIdFromResource = extractReferenceSlug2(resourceRecord, "role");
7193
7909
  const workspaceIdFromResource = extractReferenceSlug2(
@@ -7284,7 +8000,7 @@ async function createRoleAssignmentOperation(params) {
7284
8000
  }
7285
8001
 
7286
8002
  // src/data/operations/control/tenant/tenant-create-operation.ts
7287
- var import_types9 = require("@openhi/types");
8003
+ var import_types10 = require("@openhi/types");
7288
8004
  var import_ulid3 = require("ulid");
7289
8005
  async function createTenantOperation(params) {
7290
8006
  const { context, body, tableName } = params;
@@ -7294,7 +8010,7 @@ async function createTenantOperation(params) {
7294
8010
  const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
7295
8011
  const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
7296
8012
  const resource = { resourceType: "Tenant", id, ...parsedResource };
7297
- const summary = JSON.stringify((0, import_types9.extractSummary)(resource));
8013
+ const summary = JSON.stringify((0, import_types10.extractSummary)(resource));
7298
8014
  await service.entities.tenant.put({
7299
8015
  tenantId: id,
7300
8016
  id,
@@ -8344,87 +9060,6 @@ function getDynamoDataService(tableName) {
8344
9060
  };
8345
9061
  }
8346
9062
 
8347
- // src/data/operations/data-operations-common.ts
8348
- var import_types10 = require("@openhi/types");
8349
-
8350
- // src/lib/compression.ts
8351
- var import_node_zlib = require("zlib");
8352
- var ENVELOPE_VERSION = 1;
8353
- var COMPRESSION_ALGOS = {
8354
- NONE: "none",
8355
- GZIP: "gzip",
8356
- BROTLI: "brotli",
8357
- DEFLATE: "deflate"
8358
- };
8359
- function compressResource(jsonString, options) {
8360
- const algo = options?.algo ?? COMPRESSION_ALGOS.GZIP;
8361
- if (algo === COMPRESSION_ALGOS.NONE) {
8362
- const envelope2 = {
8363
- v: ENVELOPE_VERSION,
8364
- algo: COMPRESSION_ALGOS.NONE,
8365
- payload: jsonString
8366
- };
8367
- return JSON.stringify(envelope2);
8368
- }
8369
- const buf = Buffer.from(jsonString, "utf-8");
8370
- const payload = (0, import_node_zlib.gzipSync)(buf).toString("base64");
8371
- const envelope = {
8372
- v: ENVELOPE_VERSION,
8373
- algo: COMPRESSION_ALGOS.GZIP,
8374
- payload
8375
- };
8376
- return JSON.stringify(envelope);
8377
- }
8378
-
8379
- // src/data/audit-meta.ts
8380
- var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
8381
- function mergeAuditIntoMeta(meta, audit) {
8382
- const existing = meta ?? {};
8383
- const ext = [
8384
- ...Array.isArray(existing.extension) ? existing.extension : []
8385
- ];
8386
- const byUrl = new Map(ext.map((e) => [e.url, e]));
8387
- function set(url, value, type) {
8388
- if (value == null) return;
8389
- byUrl.set(url, { url, [type]: value });
8390
- }
8391
- set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
8392
- set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
8393
- set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
8394
- set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
8395
- set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
8396
- set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
8397
- set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
8398
- set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
8399
- set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
8400
- return { ...existing, extension: Array.from(byUrl.values()) };
8401
- }
8402
-
8403
- // src/data/operations/data-operations-common.ts
8404
- var DATA_ENTITY_SK = "CURRENT";
8405
- async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourceWithAudit, fallbackDate) {
8406
- const lastUpdated = resourceWithAudit.meta?.lastUpdated ?? fallbackDate ?? (/* @__PURE__ */ new Date()).toISOString();
8407
- const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
8408
- const resourceLike = resourceWithAudit;
8409
- const summary = JSON.stringify((0, import_types10.extractSummary)(resourceLike));
8410
- const gsi1sk = (0, import_types10.extractSortKey)(resourceLike);
8411
- await entity.put({
8412
- sk: DATA_ENTITY_SK,
8413
- tenantId,
8414
- workspaceId,
8415
- id,
8416
- resource: compressResource(JSON.stringify(resourceWithAudit)),
8417
- summary,
8418
- vid,
8419
- lastUpdated,
8420
- gsi1sk
8421
- }).go();
8422
- return {
8423
- id,
8424
- resource: resourceWithAudit
8425
- };
8426
- }
8427
-
8428
9063
  // src/data/operations/data/organization/organization-provision-for-workspace-operation.ts
8429
9064
  async function provisionOrganizationForWorkspaceOperation(params) {
8430
9065
  const { context, workspaceId, workspaceName, tableName } = params;
@@ -9479,7 +10114,13 @@ var seedWorkspaceDataPlane = async (baseContext, group, failures) => {
9479
10114
  }
9480
10115
  };
9481
10116
  var seedDemoGraph = async (params) => {
9482
- const { baseContext, devUsers, cognito, onSiteTesters = [] } = params;
10117
+ const {
10118
+ baseContext,
10119
+ devUsers,
10120
+ cognito,
10121
+ onSiteTesters = [],
10122
+ reconcileCounters = () => reconcileAllCountersOperation({})
10123
+ } = params;
9483
10124
  const failures = [];
9484
10125
  for (const spec of [...DEMO_TENANT_SPECS, ...ON_SITE_UAT_TENANT_SPECS]) {
9485
10126
  const tenantContext = {
@@ -9653,6 +10294,11 @@ var seedDemoGraph = async (params) => {
9653
10294
  tester.role
9654
10295
  );
9655
10296
  }
10297
+ try {
10298
+ await reconcileCounters();
10299
+ } catch (err) {
10300
+ console.error("seed-demo-data: counter reconciliation failed:", err);
10301
+ }
9656
10302
  for (const group of DEMO_DATA_PLANE_FIXTURES) {
9657
10303
  try {
9658
10304
  await seedWorkspaceDataPlane(baseContext, group, failures);