@openhi/constructs 0.0.160 → 0.0.162
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-HQ67J7BP.mjs → chunk-5S6VFBLT.mjs} +12 -70
- package/lib/chunk-5S6VFBLT.mjs.map +1 -0
- package/lib/{chunk-MVQWAIMC.mjs → chunk-6BB4CRSS.mjs} +3 -312
- package/lib/chunk-6BB4CRSS.mjs.map +1 -0
- package/lib/{chunk-WPCBVDFZ.mjs → chunk-76UM2LQ5.mjs} +2 -2
- package/lib/{chunk-QFHYTCVY.mjs → chunk-7TRO2STL.mjs} +7 -7
- package/lib/chunk-BUAYVN3C.mjs +87 -0
- package/lib/chunk-BUAYVN3C.mjs.map +1 -0
- package/lib/{chunk-23PUSHBV.mjs → chunk-D2Y6DDOC.mjs} +2 -2
- package/lib/chunk-DWSWCUZR.mjs +123 -0
- package/lib/chunk-DWSWCUZR.mjs.map +1 -0
- package/lib/{chunk-VZCPGQXA.mjs → chunk-EUIP2U5F.mjs} +69 -1
- package/lib/{chunk-VZCPGQXA.mjs.map → chunk-EUIP2U5F.mjs.map} +1 -1
- package/lib/chunk-GJTPXJKD.mjs +46 -0
- package/lib/chunk-GJTPXJKD.mjs.map +1 -0
- package/lib/chunk-I6LUPJUY.mjs +61 -0
- package/lib/chunk-I6LUPJUY.mjs.map +1 -0
- package/lib/{chunk-KR2Y2CVQ.mjs → chunk-KA3OMP3X.mjs} +2 -2
- package/lib/{chunk-ZM4GDHHC.mjs → chunk-KMEWULMX.mjs} +51 -3
- package/lib/chunk-KMEWULMX.mjs.map +1 -0
- package/lib/chunk-LKKLO66E.mjs +25 -0
- package/lib/chunk-LKKLO66E.mjs.map +1 -0
- package/lib/{chunk-CFJDATDK.mjs → chunk-MLFMW5IF.mjs} +43 -9
- package/lib/chunk-MLFMW5IF.mjs.map +1 -0
- package/lib/chunk-O5VQWB6U.mjs +315 -0
- package/lib/chunk-O5VQWB6U.mjs.map +1 -0
- package/lib/{chunk-7BQHLC7U.mjs → chunk-P3CTZWC2.mjs} +8 -40
- package/lib/chunk-P3CTZWC2.mjs.map +1 -0
- package/lib/{chunk-EFB5OFM7.mjs → chunk-P3NFCKTZ.mjs} +6 -4
- package/lib/{chunk-EFB5OFM7.mjs.map → chunk-P3NFCKTZ.mjs.map} +1 -1
- package/lib/{chunk-M7Y3BOQW.mjs → chunk-Q3MKITPY.mjs} +5 -5
- package/lib/chunk-Q64MOYJ7.mjs +218 -0
- package/lib/chunk-Q64MOYJ7.mjs.map +1 -0
- package/lib/chunk-RQKJNMX5.mjs +89 -0
- package/lib/chunk-RQKJNMX5.mjs.map +1 -0
- package/lib/{chunk-ZWSGM6PZ.mjs → chunk-SD7J3N3C.mjs} +2 -2
- package/lib/{chunk-7RZHFI77.mjs → chunk-VESULYQQ.mjs} +2 -2
- package/lib/{chunk-AOSEKL7U.mjs → chunk-WOTU36P3.mjs} +6 -103
- package/lib/chunk-WOTU36P3.mjs.map +1 -0
- package/lib/{chunk-X5E4YJGZ.mjs → chunk-YPTJJ35S.mjs} +2 -2
- package/lib/counter-apply-operation-DZM3MIDm.d.mts +63 -0
- package/lib/counter-apply-operation-DZM3MIDm.d.ts +63 -0
- package/lib/counter-maintenance.handler.d.mts +38 -0
- package/lib/counter-maintenance.handler.d.ts +38 -0
- package/lib/counter-maintenance.handler.js +2885 -0
- package/lib/counter-maintenance.handler.js.map +1 -0
- package/lib/counter-maintenance.handler.mjs +180 -0
- package/lib/counter-maintenance.handler.mjs.map +1 -0
- package/lib/counter-reconciliation.handler.d.mts +116 -0
- package/lib/counter-reconciliation.handler.d.ts +116 -0
- package/lib/counter-reconciliation.handler.js +3324 -0
- package/lib/counter-reconciliation.handler.js.map +1 -0
- package/lib/counter-reconciliation.handler.mjs +295 -0
- package/lib/counter-reconciliation.handler.mjs.map +1 -0
- package/lib/data-store-postgres-replication.handler.js +50 -2
- package/lib/data-store-postgres-replication.handler.js.map +1 -1
- package/lib/data-store-postgres-replication.handler.mjs +2 -2
- package/lib/delete-chunk.handler.js +118 -2
- package/lib/delete-chunk.handler.js.map +1 -1
- package/lib/delete-chunk.handler.mjs +3 -3
- package/lib/finalize.handler.js +50 -2
- package/lib/finalize.handler.js.map +1 -1
- package/lib/finalize.handler.mjs +4 -4
- package/lib/firehose-archive-transform.handler.js +50 -2
- package/lib/firehose-archive-transform.handler.js.map +1 -1
- package/lib/firehose-archive-transform.handler.mjs +2 -2
- package/lib/index.d.mts +140 -2
- package/lib/index.d.ts +143 -5
- package/lib/index.js +493 -196
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +360 -193
- package/lib/index.mjs.map +1 -1
- package/lib/list-chunks.handler.js +118 -2
- package/lib/list-chunks.handler.js.map +1 -1
- package/lib/list-chunks.handler.mjs +3 -3
- package/lib/platform-deploy-bridge.handler.js +50 -2
- package/lib/platform-deploy-bridge.handler.js.map +1 -1
- package/lib/platform-deploy-bridge.handler.mjs +1 -1
- package/lib/pre-token-generation.handler.js +68 -0
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +9 -5
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +883 -0
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +10 -5
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rename-finalize.handler.js +50 -2
- package/lib/rename-finalize.handler.js.map +1 -1
- package/lib/rename-finalize.handler.mjs +2 -2
- package/lib/rename-list-targets.handler.js +118 -2
- package/lib/rename-list-targets.handler.js.map +1 -1
- package/lib/rename-list-targets.handler.mjs +11 -9
- package/lib/rename-list-targets.handler.mjs.map +1 -1
- package/lib/rename-rewrite-chunk.handler.js +68 -0
- package/lib/rename-rewrite-chunk.handler.js.map +1 -1
- package/lib/rename-rewrite-chunk.handler.mjs +2 -2
- package/lib/rest-api-lambda.handler.js +1454 -251
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +415 -291
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js +205 -8
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +10 -7
- package/lib/seed-system-data.handler.js +118 -2
- package/lib/seed-system-data.handler.js.map +1 -1
- package/lib/seed-system-data.handler.mjs +5 -5
- package/package.json +11 -11
- package/lib/chunk-7BQHLC7U.mjs.map +0 -1
- package/lib/chunk-AOSEKL7U.mjs.map +0 -1
- package/lib/chunk-CFJDATDK.mjs.map +0 -1
- package/lib/chunk-HQ67J7BP.mjs.map +0 -1
- package/lib/chunk-MVQWAIMC.mjs.map +0 -1
- package/lib/chunk-ZM4GDHHC.mjs.map +0 -1
- /package/lib/{chunk-WPCBVDFZ.mjs.map → chunk-76UM2LQ5.mjs.map} +0 -0
- /package/lib/{chunk-QFHYTCVY.mjs.map → chunk-7TRO2STL.mjs.map} +0 -0
- /package/lib/{chunk-23PUSHBV.mjs.map → chunk-D2Y6DDOC.mjs.map} +0 -0
- /package/lib/{chunk-KR2Y2CVQ.mjs.map → chunk-KA3OMP3X.mjs.map} +0 -0
- /package/lib/{chunk-M7Y3BOQW.mjs.map → chunk-Q3MKITPY.mjs.map} +0 -0
- /package/lib/{chunk-ZWSGM6PZ.mjs.map → chunk-SD7J3N3C.mjs.map} +0 -0
- /package/lib/{chunk-7RZHFI77.mjs.map → chunk-VESULYQQ.mjs.map} +0 -0
- /package/lib/{chunk-X5E4YJGZ.mjs.map → chunk-YPTJJ35S.mjs.map} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
require_lib
|
|
3
|
+
} from "./chunk-KMEWULMX.mjs";
|
|
4
|
+
import {
|
|
5
|
+
__toESM
|
|
6
|
+
} from "./chunk-LZOMFHX3.mjs";
|
|
7
|
+
|
|
8
|
+
// src/data/operations/control/control-event-publisher.ts
|
|
9
|
+
var import_workflows = __toESM(require_lib());
|
|
10
|
+
import { EventBridgeClient } from "@aws-sdk/client-eventbridge";
|
|
11
|
+
var CONTROL_EVENT_BUS_NAME_ENV_VAR = "CONTROL_EVENT_BUS_NAME";
|
|
12
|
+
var cachedClient;
|
|
13
|
+
function getClient() {
|
|
14
|
+
if (!cachedClient) {
|
|
15
|
+
cachedClient = new EventBridgeClient({
|
|
16
|
+
region: process.env.AWS_REGION ?? "us-east-1"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return cachedClient;
|
|
20
|
+
}
|
|
21
|
+
function actorFromContext(context) {
|
|
22
|
+
return {
|
|
23
|
+
ohi_tid: context.tenantId,
|
|
24
|
+
ohi_wid: context.workspaceId,
|
|
25
|
+
ohi_uid: context.actorId,
|
|
26
|
+
ohi_uname: context.actorName
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function publishControlEvent(entry, payload, context) {
|
|
30
|
+
const busName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];
|
|
31
|
+
if (!busName) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
await (0, import_workflows.publishWorkflowEvent)(
|
|
36
|
+
getClient(),
|
|
37
|
+
entry,
|
|
38
|
+
payload,
|
|
39
|
+
{ actor: actorFromContext(context) },
|
|
40
|
+
{ busNameByPlane: { [import_workflows.OPENHI_CONTROL_SOURCE]: busName } }
|
|
41
|
+
);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error(`control-event publish failed for ${entry.detailType}:`, err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function publishMembershipCreated(context, detail) {
|
|
47
|
+
await publishControlEvent(import_workflows.ControlPlaneMembershipCreatedV1, detail, context);
|
|
48
|
+
}
|
|
49
|
+
async function publishMembershipDeleted(context, detail) {
|
|
50
|
+
await publishControlEvent(import_workflows.ControlPlaneMembershipDeletedV1, detail, context);
|
|
51
|
+
}
|
|
52
|
+
async function publishRoleAssignmentCreated(context, detail) {
|
|
53
|
+
await publishControlEvent(
|
|
54
|
+
import_workflows.ControlPlaneRoleAssignmentCreatedV1,
|
|
55
|
+
detail,
|
|
56
|
+
context
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async function publishRoleAssignmentDeleted(context, detail) {
|
|
60
|
+
await publishControlEvent(
|
|
61
|
+
import_workflows.ControlPlaneRoleAssignmentDeletedV1,
|
|
62
|
+
detail,
|
|
63
|
+
context
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
async function publishWorkspaceCreated(context, detail) {
|
|
67
|
+
await publishControlEvent(import_workflows.ControlPlaneWorkspaceCreatedV1, detail, context);
|
|
68
|
+
}
|
|
69
|
+
async function publishWorkspaceDeleted(context, detail) {
|
|
70
|
+
await publishControlEvent(import_workflows.ControlPlaneWorkspaceDeletedV1, detail, context);
|
|
71
|
+
}
|
|
72
|
+
function extractRoleLevel(resource) {
|
|
73
|
+
const code = resource?.code;
|
|
74
|
+
const first = code?.coding?.[0]?.code;
|
|
75
|
+
return typeof first === "string" && first.length > 0 ? first : void 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
publishMembershipCreated,
|
|
80
|
+
publishMembershipDeleted,
|
|
81
|
+
publishRoleAssignmentCreated,
|
|
82
|
+
publishRoleAssignmentDeleted,
|
|
83
|
+
publishWorkspaceCreated,
|
|
84
|
+
publishWorkspaceDeleted,
|
|
85
|
+
extractRoleLevel
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=chunk-BUAYVN3C.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/control-event-publisher.ts"],"sourcesContent":["import { EventBridgeClient } from \"@aws-sdk/client-eventbridge\";\nimport {\n ControlPlaneMembershipCreatedV1,\n ControlPlaneMembershipDeletedV1,\n ControlPlaneRoleAssignmentCreatedV1,\n ControlPlaneRoleAssignmentDeletedV1,\n ControlPlaneWorkspaceCreatedV1,\n ControlPlaneWorkspaceDeletedV1,\n OPENHI_CONTROL_SOURCE,\n publishWorkflowEvent,\n type ControlPlaneMembershipChangedV1Detail,\n type ControlPlaneRoleAssignmentChangedV1Detail,\n type ControlPlaneWorkspaceChangedV1Detail,\n type WorkflowActor,\n type WorkflowDetailTypeEntry,\n} from \"@openhi/workflows\";\nimport { OpenHiContext } from \"../../openhi-context\";\n\n/**\n * Env var carrying the name of the control event bus the REST API Lambda\n * publishes control-plane domain events to. Set by\n * {@link RestApiLambda} from the deterministic `ControlEventBus` name.\n * When unset (seed / import / unit-test contexts) the publisher is a no-op,\n * so operations stay usable outside the Lambda and the counter backfill in\n * the reconciliation job (#1319) repairs any rows created without an event.\n */\nexport const CONTROL_EVENT_BUS_NAME_ENV_VAR = \"CONTROL_EVENT_BUS_NAME\";\n\nlet cachedClient: EventBridgeClient | undefined;\n\nfunction getClient(): EventBridgeClient {\n if (!cachedClient) {\n cachedClient = new EventBridgeClient({\n region: process.env.AWS_REGION ?? \"us-east-1\",\n });\n }\n return cachedClient;\n}\n\nfunction actorFromContext(context: OpenHiContext): WorkflowActor {\n return {\n ohi_tid: context.tenantId,\n ohi_wid: context.workspaceId,\n ohi_uid: context.actorId,\n ohi_uname: context.actorName,\n };\n}\n\n/**\n * Publish one control-plane domain event to the control event bus.\n *\n * Best-effort by contract (ADR-028): the canonical multi-write has already\n * committed by the time this runs, the counters it feeds are eventually\n * consistent, and the reconciliation job (#1319) is the correctness backstop.\n * A publish failure is therefore logged and swallowed so it never fails the\n * operation (which would 500 a request whose data write already succeeded).\n * When `CONTROL_EVENT_BUS_NAME` is unset the publish is skipped entirely.\n */\nasync function publishControlEvent<TDetail>(\n entry: WorkflowDetailTypeEntry<TDetail>,\n payload: TDetail,\n context: OpenHiContext,\n): Promise<void> {\n const busName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];\n if (!busName) {\n return;\n }\n try {\n await publishWorkflowEvent(\n getClient(),\n entry,\n payload,\n { actor: actorFromContext(context) },\n { busNameByPlane: { [OPENHI_CONTROL_SOURCE]: busName } },\n );\n } catch (err) {\n console.error(`control-event publish failed for ${entry.detailType}:`, err);\n }\n}\n\n/** Publish `control-plane.membership-created.v1`. */\nexport async function publishMembershipCreated(\n context: OpenHiContext,\n detail: ControlPlaneMembershipChangedV1Detail,\n): Promise<void> {\n await publishControlEvent(ControlPlaneMembershipCreatedV1, detail, context);\n}\n\n/** Publish `control-plane.membership-deleted.v1`. */\nexport async function publishMembershipDeleted(\n context: OpenHiContext,\n detail: ControlPlaneMembershipChangedV1Detail,\n): Promise<void> {\n await publishControlEvent(ControlPlaneMembershipDeletedV1, detail, context);\n}\n\n/** Publish `control-plane.role-assignment-created.v1`. */\nexport async function publishRoleAssignmentCreated(\n context: OpenHiContext,\n detail: ControlPlaneRoleAssignmentChangedV1Detail,\n): Promise<void> {\n await publishControlEvent(\n ControlPlaneRoleAssignmentCreatedV1,\n detail,\n context,\n );\n}\n\n/** Publish `control-plane.role-assignment-deleted.v1`. */\nexport async function publishRoleAssignmentDeleted(\n context: OpenHiContext,\n detail: ControlPlaneRoleAssignmentChangedV1Detail,\n): Promise<void> {\n await publishControlEvent(\n ControlPlaneRoleAssignmentDeletedV1,\n detail,\n context,\n );\n}\n\n/** Publish `control-plane.workspace-created.v1`. */\nexport async function publishWorkspaceCreated(\n context: OpenHiContext,\n detail: ControlPlaneWorkspaceChangedV1Detail,\n): Promise<void> {\n await publishControlEvent(ControlPlaneWorkspaceCreatedV1, detail, context);\n}\n\n/** Publish `control-plane.workspace-deleted.v1`. */\nexport async function publishWorkspaceDeleted(\n context: OpenHiContext,\n detail: ControlPlaneWorkspaceChangedV1Detail,\n): Promise<void> {\n await publishControlEvent(ControlPlaneWorkspaceDeletedV1, detail, context);\n}\n\n/**\n * Extract the ADR-019 organization-role level / type from a RoleAssignment\n * resource so the counter-maintenance consumer can classify a workspace-scoped\n * assignment as admin vs normal without re-reading the Role record. Reads the\n * slim `PractitionerRole.code` coding per ADR-019 §1.2; returns `undefined`\n * when no code is present.\n */\nexport function extractRoleLevel(\n resource: Record<string, unknown> | undefined,\n): string | undefined {\n const code = resource?.code as\n | { coding?: Array<{ code?: unknown }> }\n | undefined;\n const first = code?.coding?.[0]?.code;\n return typeof first === \"string\" && first.length > 0 ? first : undefined;\n}\n"],"mappings":";;;;;;;;AACA,uBAcO;AAfP,SAAS,yBAAyB;AA0B3B,IAAM,iCAAiC;AAE9C,IAAI;AAEJ,SAAS,YAA+B;AACtC,MAAI,CAAC,cAAc;AACjB,mBAAe,IAAI,kBAAkB;AAAA,MACnC,QAAQ,QAAQ,IAAI,cAAc;AAAA,IACpC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAAuC;AAC/D,SAAO;AAAA,IACL,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ;AAAA,IACjB,WAAW,QAAQ;AAAA,EACrB;AACF;AAYA,eAAe,oBACb,OACA,SACA,SACe;AACf,QAAM,UAAU,QAAQ,IAAI,8BAA8B;AAC1D,MAAI,CAAC,SAAS;AACZ;AAAA,EACF;AACA,MAAI;AACF,cAAM;AAAA,MACJ,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,EAAE,OAAO,iBAAiB,OAAO,EAAE;AAAA,MACnC,EAAE,gBAAgB,EAAE,CAAC,sCAAqB,GAAG,QAAQ,EAAE;AAAA,IACzD;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,oCAAoC,MAAM,UAAU,KAAK,GAAG;AAAA,EAC5E;AACF;AAGA,eAAsB,yBACpB,SACA,QACe;AACf,QAAM,oBAAoB,kDAAiC,QAAQ,OAAO;AAC5E;AAGA,eAAsB,yBACpB,SACA,QACe;AACf,QAAM,oBAAoB,kDAAiC,QAAQ,OAAO;AAC5E;AAGA,eAAsB,6BACpB,SACA,QACe;AACf,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,eAAsB,6BACpB,SACA,QACe;AACf,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,eAAsB,wBACpB,SACA,QACe;AACf,QAAM,oBAAoB,iDAAgC,QAAQ,OAAO;AAC3E;AAGA,eAAsB,wBACpB,SACA,QACe;AACf,QAAM,oBAAoB,iDAAgC,QAAQ,OAAO;AAC3E;AASO,SAAS,iBACd,UACoB;AACpB,QAAM,OAAO,UAAU;AAGvB,QAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;AACjC,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
require_lib
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KMEWULMX.mjs";
|
|
4
4
|
import {
|
|
5
5
|
__toESM
|
|
6
6
|
} from "./chunk-LZOMFHX3.mjs";
|
|
@@ -21,4 +21,4 @@ export {
|
|
|
21
21
|
RENAME_CASCADE_OPS_EVENT_BUS_ENV_VAR,
|
|
22
22
|
import_workflows
|
|
23
23
|
};
|
|
24
|
-
//# sourceMappingURL=chunk-
|
|
24
|
+
//# sourceMappingURL=chunk-D2Y6DDOC.mjs.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
batchGetWithRetry,
|
|
3
|
+
dispatchListMode
|
|
4
|
+
} from "./chunk-O5VQWB6U.mjs";
|
|
5
|
+
import {
|
|
6
|
+
SHARD_COUNT,
|
|
7
|
+
getDynamoControlService
|
|
8
|
+
} from "./chunk-EUIP2U5F.mjs";
|
|
9
|
+
|
|
10
|
+
// src/data/operations/control/user/user-list-operation.ts
|
|
11
|
+
var SK = "CURRENT";
|
|
12
|
+
function counterValue(value) {
|
|
13
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
14
|
+
}
|
|
15
|
+
async function listUsersOperation(params) {
|
|
16
|
+
const { tableName, mode = "full" } = params;
|
|
17
|
+
const service = getDynamoControlService(tableName);
|
|
18
|
+
const shardResults = await Promise.all(
|
|
19
|
+
Array.from(
|
|
20
|
+
{ length: SHARD_COUNT },
|
|
21
|
+
(_, shard) => service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go()
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
return dispatchListMode(mode, shardResults, {
|
|
25
|
+
hydrate: (orderedIds) => batchGetWithRetry(
|
|
26
|
+
service.entities.user,
|
|
27
|
+
orderedIds.map((id) => ({ id, sk: SK }))
|
|
28
|
+
),
|
|
29
|
+
getId: (item) => item.id,
|
|
30
|
+
// FULL mode (admin list default): read the ADR-028 counters off the
|
|
31
|
+
// canonical record hydrated by BatchGet and expose them as
|
|
32
|
+
// `resource.counts`. Missing counters render as 0.
|
|
33
|
+
buildEntry: (id, item) => ({
|
|
34
|
+
id,
|
|
35
|
+
resource: {
|
|
36
|
+
resourceType: "User",
|
|
37
|
+
id,
|
|
38
|
+
...JSON.parse(item.resource),
|
|
39
|
+
counts: {
|
|
40
|
+
tenantsForUser: counterValue(item.tenantsForUser),
|
|
41
|
+
workspacesForUser: counterValue(item.workspacesForUser)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
// SUMMARY mode reads only the GSI1 `summary` projection (no
|
|
46
|
+
// counters); surface zeros so the shape stays uniform.
|
|
47
|
+
buildSummaryEntry: (id, parsed) => ({
|
|
48
|
+
id,
|
|
49
|
+
resource: {
|
|
50
|
+
resourceType: "User",
|
|
51
|
+
id,
|
|
52
|
+
...parsed,
|
|
53
|
+
counts: { tenantsForUser: 0, workspacesForUser: 0 }
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/data/operations/control/membership/membership-list-by-user-operation.ts
|
|
60
|
+
function buildSkPrefix(mode, tenantId) {
|
|
61
|
+
switch (mode) {
|
|
62
|
+
case "tenant":
|
|
63
|
+
return "MEMBERSHIP#TENANT#";
|
|
64
|
+
case "workspace":
|
|
65
|
+
return "MEMBERSHIP#WORKSPACE#";
|
|
66
|
+
case "workspaceInTenant":
|
|
67
|
+
return `MEMBERSHIP#WORKSPACE#TID#${tenantId}#`;
|
|
68
|
+
case "all":
|
|
69
|
+
default:
|
|
70
|
+
return "MEMBERSHIP#";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function membershipListByUserOperation(params) {
|
|
74
|
+
const {
|
|
75
|
+
userId,
|
|
76
|
+
mode = "all",
|
|
77
|
+
tenantId,
|
|
78
|
+
cursor = null,
|
|
79
|
+
limit,
|
|
80
|
+
order,
|
|
81
|
+
tableName
|
|
82
|
+
} = params;
|
|
83
|
+
if (mode === "workspaceInTenant" && !tenantId) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
'membershipListByUserOperation: tenantId is required when mode === "workspaceInTenant"'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const service = getDynamoControlService(tableName);
|
|
89
|
+
const skPrefix = buildSkPrefix(mode, tenantId);
|
|
90
|
+
const goOptions = {
|
|
91
|
+
cursor
|
|
92
|
+
};
|
|
93
|
+
if (limit !== void 0) {
|
|
94
|
+
goOptions.limit = limit;
|
|
95
|
+
}
|
|
96
|
+
if (order !== void 0) {
|
|
97
|
+
goOptions.order = order;
|
|
98
|
+
}
|
|
99
|
+
const result = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: skPrefix }).go(goOptions);
|
|
100
|
+
const items = (result.data ?? []).map(
|
|
101
|
+
(row) => ({
|
|
102
|
+
userId: row.userId,
|
|
103
|
+
sk: row.sk,
|
|
104
|
+
tenantId: row.tenantId,
|
|
105
|
+
workspaceId: row.workspaceId,
|
|
106
|
+
membershipId: row.membershipId,
|
|
107
|
+
summary: row.summary,
|
|
108
|
+
vid: row.vid,
|
|
109
|
+
lastUpdated: row.lastUpdated,
|
|
110
|
+
denormalizedTenantName: row.denormalizedTenantName,
|
|
111
|
+
denormalizedUserName: row.denormalizedUserName,
|
|
112
|
+
denormalizedWorkspaceName: row.denormalizedWorkspaceName
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
return { items, cursor: result.cursor ?? null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
buildSkPrefix,
|
|
120
|
+
membershipListByUserOperation,
|
|
121
|
+
listUsersOperation
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-DWSWCUZR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/user/user-list-operation.ts","../src/data/operations/control/membership/membership-list-by-user-operation.ts"],"sourcesContent":["import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { SHARD_COUNT } from \"../../../dynamo/shard\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport {\n batchGetWithRetry,\n dispatchListMode,\n type ListOperationMode,\n} from \"../../data-operations-common\";\n\nconst SK = \"CURRENT\";\n\nexport interface UserListParams {\n context: OpenHiContext;\n tableName?: string;\n /** #853: defaults to `\"full\"`. `\"summary\"` skips BatchGet, `\"count\"` returns total only. */\n mode?: ListOperationMode;\n}\n\n/**\n * ADR-028 denormalized counter shape surfaced on a User list entry's\n * `resource.counts`. Missing counters render as `0`.\n */\nexport interface UserCounts {\n tenantsForUser: number;\n workspacesForUser: number;\n}\n\nexport interface UserListResult {\n entries: Array<{\n id: string;\n resource: {\n resourceType: string;\n id: string;\n counts: UserCounts;\n [key: string]: unknown;\n };\n }>;\n total: number;\n}\n\n/** Coerce a possibly-absent counter attribute to a non-negative number (default 0). */\nfunction counterValue(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\n/**\n * Lists Users via GSI1 (sharded). `mode` (default `\"full\"`) selects between BatchGet hydration,\n * summary-only (parse `summary` JSON projected on GSI1), or count-only (skip both). See\n * `dispatchListMode` in data-operations-common for the canonical mode contract.\n */\nexport async function listUsersOperation(\n params: UserListParams,\n): Promise<UserListResult> {\n const { tableName, mode = \"full\" } = params;\n const service = getDynamoControlService(tableName);\n\n const shardResults = await Promise.all(\n Array.from({ length: SHARD_COUNT }, (_, shard) =>\n service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go(),\n ),\n );\n\n return dispatchListMode<\n {\n id: string;\n resource: string;\n tenantsForUser?: number;\n workspacesForUser?: number;\n },\n UserListResult[\"entries\"][number]\n >(mode, shardResults, {\n hydrate: (orderedIds) =>\n batchGetWithRetry(\n service.entities.user,\n orderedIds.map((id) => ({ id, sk: SK })),\n ) as Promise<\n Array<{\n id: string;\n resource: string;\n tenantsForUser?: number;\n workspacesForUser?: number;\n }>\n >,\n getId: (item) => item.id,\n // FULL mode (admin list default): read the ADR-028 counters off the\n // canonical record hydrated by BatchGet and expose them as\n // `resource.counts`. Missing counters render as 0.\n buildEntry: (id, item) => ({\n id,\n resource: {\n resourceType: \"User\",\n id,\n ...(JSON.parse(item.resource) as Record<string, unknown>),\n counts: {\n tenantsForUser: counterValue(item.tenantsForUser),\n workspacesForUser: counterValue(item.workspacesForUser),\n },\n },\n }),\n // SUMMARY mode reads only the GSI1 `summary` projection (no\n // counters); surface zeros so the shape stays uniform.\n buildSummaryEntry: (id, parsed) => ({\n id,\n resource: {\n resourceType: \"User\",\n id,\n ...parsed,\n counts: { tenantsForUser: 0, workspacesForUser: 0 },\n },\n }),\n });\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\n\n/**\n * Filter modes for {@link membershipListByUserOperation}.\n *\n * Maps directly to the ADR-018 sub-lane discriminator in the user-projection\n * SK (`MEMBERSHIP#TENANT#…` vs `MEMBERSHIP#WORKSPACE#…`):\n *\n * - `\"all\"` — `Query(PK = USER#ID#<userId>, SK begins_with 'MEMBERSHIP#')`.\n * Returns both lanes interleaved in raw SK order.\n * - `\"tenant\"` — `SK begins_with 'MEMBERSHIP#TENANT#'`. Pattern #3 only.\n * - `\"workspace\"` — `SK begins_with 'MEMBERSHIP#WORKSPACE#'`. Pattern #4\n * across every tenant.\n * - `\"workspaceInTenant\"` — `SK begins_with 'MEMBERSHIP#WORKSPACE#TID#<tenantId>#'`.\n * Pattern #4 narrowed to one tenant. Requires `tenantId`.\n */\nexport type MembershipListByUserMode =\n | \"all\"\n | \"tenant\"\n | \"workspace\"\n | \"workspaceInTenant\";\n\n/** Inputs accepted by {@link membershipListByUserOperation}. */\nexport interface MembershipListByUserParams {\n readonly userId: string;\n /** Filter mode — see {@link MembershipListByUserMode}. Defaults to `\"all\"`. */\n readonly mode?: MembershipListByUserMode;\n /** Required only when `mode === \"workspaceInTenant\"`. */\n readonly tenantId?: string;\n /** ElectroDB cursor from a prior page. Forwarded to `.go({ cursor })`. */\n readonly cursor?: string | null;\n /** Per-page item limit forwarded to `.go({ limit })`. */\n readonly limit?: number;\n /** Sort order forwarded to `.go({ order })`. Defaults to ElectroDB's `\"asc\"`. */\n readonly order?: \"asc\" | \"desc\";\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n}\n\n/** One projection-row payload as returned to a consumer. */\nexport interface MembershipUserProjectionEntry {\n readonly userId: string;\n readonly sk: string;\n readonly tenantId: string;\n readonly workspaceId?: string;\n readonly membershipId: string;\n readonly summary: string;\n readonly vid: string;\n readonly lastUpdated: string;\n readonly denormalizedTenantName?: string;\n readonly denormalizedUserName?: string;\n readonly denormalizedWorkspaceName?: string;\n}\n\n/** Page returned by {@link membershipListByUserOperation}. */\nexport interface MembershipListByUserResult {\n readonly items: Array<MembershipUserProjectionEntry>;\n /** ElectroDB cursor for the next page, or `null` when exhausted. */\n readonly cursor: string | null;\n}\n\n/**\n * Compose the SK prefix for a given filter mode. Centralizing the\n * prefix string here keeps the SK grammar (owned by\n * `membership-user-projection.ts`) the single source of truth for the\n * lane discriminators — this function reads them, it does not invent them.\n */\nexport function buildSkPrefix(\n mode: MembershipListByUserMode,\n tenantId: string | undefined,\n): string {\n switch (mode) {\n case \"tenant\":\n return \"MEMBERSHIP#TENANT#\";\n case \"workspace\":\n return \"MEMBERSHIP#WORKSPACE#\";\n case \"workspaceInTenant\":\n // Pattern-#4 SK places `<tenantId>` directly after the\n // `MEMBERSHIP#WORKSPACE#TID#` segment so a `begins_with` filter\n // narrows the workspace lane to a single tenant.\n return `MEMBERSHIP#WORKSPACE#TID#${tenantId}#`;\n case \"all\":\n default:\n return \"MEMBERSHIP#\";\n }\n}\n\n/**\n * List Memberships for a user via the ADR-018 user-partition projection\n * (no GSI hop).\n *\n * Reads `MembershipUserProjectionEntity` rows under `PK = USER#ID#<userId>`\n * with an `SK begins_with` filter selected by `mode`:\n *\n * | Mode | SK begins_with | Covers |\n * |---|---|---|\n * | `all` (default) | `MEMBERSHIP#` | patterns #3 + #4 interleaved |\n * | `tenant` | `MEMBERSHIP#TENANT#` | pattern #3 only |\n * | `workspace` | `MEMBERSHIP#WORKSPACE#` | pattern #4 only, across tenants |\n * | `workspaceInTenant` | `MEMBERSHIP#WORKSPACE#TID#<tenantId>#` | pattern #4 in one tenant |\n *\n * Returns the projection rows verbatim (`summary`, `vid`, `lastUpdated`\n * plus the projection-discriminating fields) — full canonical-resource\n * hydration is opt-in for callers via\n * `MembershipEntity.get({ tenantId, id: membershipId })`. Pagination\n * mirrors ElectroDB's native `.go({ cursor })` shape; the returned\n * `cursor` is opaque to callers.\n *\n * @see ADR-018 § Access Pattern Coverage (patterns #3 and #4)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar)\n */\nexport async function membershipListByUserOperation(\n params: MembershipListByUserParams,\n): Promise<MembershipListByUserResult> {\n const {\n userId,\n mode = \"all\",\n tenantId,\n cursor = null,\n limit,\n order,\n tableName,\n } = params;\n\n if (mode === \"workspaceInTenant\" && !tenantId) {\n throw new Error(\n 'membershipListByUserOperation: tenantId is required when mode === \"workspaceInTenant\"',\n );\n }\n\n const service = getDynamoControlService(tableName);\n const skPrefix = buildSkPrefix(mode, tenantId);\n\n const goOptions: {\n cursor?: string | null;\n limit?: number;\n order?: \"asc\" | \"desc\";\n } = {\n cursor,\n };\n if (limit !== undefined) {\n goOptions.limit = limit;\n }\n if (order !== undefined) {\n goOptions.order = order;\n }\n\n const result = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: skPrefix })\n .go(goOptions);\n\n const items: Array<MembershipUserProjectionEntry> = (result.data ?? []).map(\n (row) => ({\n userId: row.userId,\n sk: row.sk,\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n membershipId: row.membershipId,\n summary: row.summary,\n vid: row.vid,\n lastUpdated: row.lastUpdated,\n denormalizedTenantName: row.denormalizedTenantName,\n denormalizedUserName: row.denormalizedUserName,\n denormalizedWorkspaceName: row.denormalizedWorkspaceName,\n }),\n );\n\n return { items, cursor: result.cursor ?? null };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1436,6 +1436,24 @@ var TenantEntity = new Entity11({
|
|
|
1436
1436
|
type: "string",
|
|
1437
1437
|
required: true
|
|
1438
1438
|
},
|
|
1439
|
+
/**
|
|
1440
|
+
* ADR-028 denormalized counter — number of tenant-scoped Memberships
|
|
1441
|
+
* (users) in this tenant. Maintained by the counter-maintenance
|
|
1442
|
+
* consumer via atomic ADD; absent/0 until first event or reconciliation.
|
|
1443
|
+
*/
|
|
1444
|
+
usersInTenant: {
|
|
1445
|
+
type: "number",
|
|
1446
|
+
required: false
|
|
1447
|
+
},
|
|
1448
|
+
/**
|
|
1449
|
+
* ADR-028 denormalized counter — number of Workspaces in this tenant.
|
|
1450
|
+
* Maintained by the counter-maintenance consumer via atomic ADD;
|
|
1451
|
+
* absent/0 until first event or reconciliation.
|
|
1452
|
+
*/
|
|
1453
|
+
workspacesInTenant: {
|
|
1454
|
+
type: "number",
|
|
1455
|
+
required: false
|
|
1456
|
+
},
|
|
1439
1457
|
gsi1Shard: gsi1ShardAttribute,
|
|
1440
1458
|
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
1441
1459
|
gsi1sk: gsi1skAttribute,
|
|
@@ -1540,6 +1558,26 @@ var UserEntity = new Entity12({
|
|
|
1540
1558
|
type: "string",
|
|
1541
1559
|
required: true
|
|
1542
1560
|
},
|
|
1561
|
+
/**
|
|
1562
|
+
* ADR-028 denormalized counter — number of tenant-scoped Memberships
|
|
1563
|
+
* (tenants) this user belongs to. Maintained by the
|
|
1564
|
+
* counter-maintenance consumer via atomic ADD; absent/0 until first
|
|
1565
|
+
* event or reconciliation.
|
|
1566
|
+
*/
|
|
1567
|
+
tenantsForUser: {
|
|
1568
|
+
type: "number",
|
|
1569
|
+
required: false
|
|
1570
|
+
},
|
|
1571
|
+
/**
|
|
1572
|
+
* ADR-028 denormalized counter — number of workspace-scoped
|
|
1573
|
+
* Memberships (workspaces) this user belongs to. Maintained by the
|
|
1574
|
+
* counter-maintenance consumer via atomic ADD; absent/0 until first
|
|
1575
|
+
* event or reconciliation.
|
|
1576
|
+
*/
|
|
1577
|
+
workspacesForUser: {
|
|
1578
|
+
type: "number",
|
|
1579
|
+
required: false
|
|
1580
|
+
},
|
|
1543
1581
|
gsi1Shard: gsi1ShardAttribute,
|
|
1544
1582
|
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
1545
1583
|
gsi1sk: gsi1skAttribute,
|
|
@@ -1684,6 +1722,36 @@ var WorkspaceEntity = new Entity13({
|
|
|
1684
1722
|
type: "string",
|
|
1685
1723
|
required: true
|
|
1686
1724
|
},
|
|
1725
|
+
/**
|
|
1726
|
+
* ADR-028 denormalized counter — number of workspace-scoped
|
|
1727
|
+
* Memberships (users) in this workspace. Maintained by the
|
|
1728
|
+
* counter-maintenance consumer via atomic ADD; absent/0 until first
|
|
1729
|
+
* event or reconciliation.
|
|
1730
|
+
*/
|
|
1731
|
+
usersInWorkspace: {
|
|
1732
|
+
type: "number",
|
|
1733
|
+
required: false
|
|
1734
|
+
},
|
|
1735
|
+
/**
|
|
1736
|
+
* ADR-028 denormalized counter — number of workspace-scoped
|
|
1737
|
+
* RoleAssignments classified as admin-tier in this workspace.
|
|
1738
|
+
* Maintained by the counter-maintenance consumer via atomic ADD;
|
|
1739
|
+
* absent/0 until first event or reconciliation.
|
|
1740
|
+
*/
|
|
1741
|
+
adminUsersInWorkspace: {
|
|
1742
|
+
type: "number",
|
|
1743
|
+
required: false
|
|
1744
|
+
},
|
|
1745
|
+
/**
|
|
1746
|
+
* ADR-028 denormalized counter — number of workspace-scoped
|
|
1747
|
+
* RoleAssignments classified as non-admin in this workspace.
|
|
1748
|
+
* Maintained by the counter-maintenance consumer via atomic ADD;
|
|
1749
|
+
* absent/0 until first event or reconciliation.
|
|
1750
|
+
*/
|
|
1751
|
+
normalUsersInWorkspace: {
|
|
1752
|
+
type: "number",
|
|
1753
|
+
required: false
|
|
1754
|
+
},
|
|
1687
1755
|
gsi1Shard: gsi1ShardAttribute,
|
|
1688
1756
|
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
1689
1757
|
gsi1sk: gsi1skAttribute,
|
|
@@ -1801,4 +1869,4 @@ export {
|
|
|
1801
1869
|
computeShard,
|
|
1802
1870
|
getDynamoControlService
|
|
1803
1871
|
};
|
|
1804
|
-
//# sourceMappingURL=chunk-
|
|
1872
|
+
//# sourceMappingURL=chunk-EUIP2U5F.mjs.map
|