@openhi/constructs 0.0.178 → 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.
- package/lib/{chunk-Z4PZSLYY.mjs → chunk-3M4QTQH6.mjs} +2 -2
- package/lib/{chunk-JUSVETWK.mjs → chunk-4LQR32D2.mjs} +38 -40
- package/lib/{chunk-JUSVETWK.mjs.map → chunk-4LQR32D2.mjs.map} +1 -1
- package/lib/{chunk-XNUCKVSE.mjs → chunk-7GMTHOYF.mjs} +2 -2
- package/lib/{chunk-E2OWEBBH.mjs → chunk-DIVYB6GD.mjs} +18 -4
- package/lib/chunk-DIVYB6GD.mjs.map +1 -0
- package/lib/chunk-F2LY4TEI.mjs +272 -0
- package/lib/chunk-F2LY4TEI.mjs.map +1 -0
- package/lib/{chunk-GG2WD6TA.mjs → chunk-JJ3AQ6G5.mjs} +9 -3
- package/lib/{chunk-GG2WD6TA.mjs.map → chunk-JJ3AQ6G5.mjs.map} +1 -1
- package/lib/{chunk-EBB4RNUG.mjs → chunk-PIQISEGW.mjs} +2 -2
- package/lib/{chunk-FDBBTNCI.mjs → chunk-Q4KQD2NB.mjs} +117 -5
- package/lib/chunk-Q4KQD2NB.mjs.map +1 -0
- package/lib/{chunk-Y4RGUAM2.mjs → chunk-V6KLFEHC.mjs} +105 -34
- package/lib/chunk-V6KLFEHC.mjs.map +1 -0
- package/lib/chunk-VQY57NOV.mjs +60 -0
- package/lib/chunk-VQY57NOV.mjs.map +1 -0
- package/lib/counter-maintenance.handler.mjs +4 -4
- package/lib/counter-reconciliation.handler.js +2 -2
- package/lib/counter-reconciliation.handler.js.map +1 -1
- package/lib/counter-reconciliation.handler.mjs +9 -267
- package/lib/counter-reconciliation.handler.mjs.map +1 -1
- package/lib/index.d.mts +117 -2
- package/lib/index.d.ts +117 -2
- package/lib/index.js +6454 -6243
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +106 -4
- package/lib/index.mjs.map +1 -1
- package/lib/pre-token-generation.handler.js +28 -19
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +4 -5
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +22 -19
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +3 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rest-api-lambda.handler.js +365 -207
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +208 -164
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.d.mts +19 -0
- package/lib/seed-demo-data.handler.d.ts +19 -0
- package/lib/seed-demo-data.handler.js +805 -159
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +8 -4
- package/package.json +1 -1
- package/lib/chunk-6HGSR3TG.mjs +0 -123
- package/lib/chunk-6HGSR3TG.mjs.map +0 -1
- package/lib/chunk-E2OWEBBH.mjs.map +0 -1
- package/lib/chunk-FDBBTNCI.mjs.map +0 -1
- package/lib/chunk-Y4RGUAM2.mjs.map +0 -1
- /package/lib/{chunk-Z4PZSLYY.mjs.map → chunk-3M4QTQH6.mjs.map} +0 -0
- /package/lib/{chunk-XNUCKVSE.mjs.map → chunk-7GMTHOYF.mjs.map} +0 -0
- /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/
|
|
6638
|
-
var
|
|
6639
|
-
|
|
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/
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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 {
|
|
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);
|