@openhi/constructs 0.0.111 → 0.0.112
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-23PUSHBV.mjs +24 -0
- package/lib/chunk-23PUSHBV.mjs.map +1 -0
- package/lib/{chunk-7FUAMZOF.mjs → chunk-53OHXLIL.mjs} +3 -3
- package/lib/chunk-6NBGYGFL.mjs +1803 -0
- package/lib/chunk-6NBGYGFL.mjs.map +1 -0
- package/lib/chunk-7RZHFI77.mjs +22 -0
- package/lib/chunk-7RZHFI77.mjs.map +1 -0
- package/lib/{chunk-7Q2IJ2J5.mjs → chunk-CUUKXDB2.mjs} +6 -6
- package/lib/chunk-FYHBHHWK.mjs +47 -0
- package/lib/chunk-FYHBHHWK.mjs.map +1 -0
- package/lib/{chunk-MULKGFIJ.mjs → chunk-GBDIGTNV.mjs} +165 -10
- package/lib/chunk-GBDIGTNV.mjs.map +1 -0
- package/lib/chunk-HQ67J7BP.mjs +199 -0
- package/lib/chunk-HQ67J7BP.mjs.map +1 -0
- package/lib/{chunk-AJ3G3THO.mjs → chunk-KO64HPWQ.mjs} +2 -2
- package/lib/{chunk-BB5MK4L3.mjs → chunk-KSFC72TT.mjs} +3 -3
- package/lib/{chunk-2TPJ6HOF.mjs → chunk-NZRW7ROK.mjs} +72 -54
- package/lib/chunk-NZRW7ROK.mjs.map +1 -0
- package/lib/chunk-QJDHVMKT.mjs +117 -0
- package/lib/chunk-QJDHVMKT.mjs.map +1 -0
- package/lib/{chunk-IS4VQRI4.mjs → chunk-QMBJ4VHC.mjs} +12 -47
- package/lib/chunk-QMBJ4VHC.mjs.map +1 -0
- package/lib/chunk-TRY7JGWO.mjs +16 -0
- package/lib/chunk-TRY7JGWO.mjs.map +1 -0
- package/lib/chunk-W4KR4CSL.mjs +236 -0
- package/lib/chunk-W4KR4CSL.mjs.map +1 -0
- package/lib/{chunk-AGF3RAAZ.mjs → chunk-WPCBVDFZ.mjs} +2 -2
- package/lib/chunk-WQWFVEVX.mjs +66 -0
- package/lib/chunk-WQWFVEVX.mjs.map +1 -0
- package/lib/{chunk-SYBADQXI.mjs → chunk-ZM4GDHHC.mjs} +77 -2
- package/lib/chunk-ZM4GDHHC.mjs.map +1 -0
- package/lib/delete-chunk.handler.d.mts +29 -0
- package/lib/delete-chunk.handler.d.ts +29 -0
- package/lib/delete-chunk.handler.js +2716 -0
- package/lib/delete-chunk.handler.js.map +1 -0
- package/lib/delete-chunk.handler.mjs +47 -0
- package/lib/delete-chunk.handler.mjs.map +1 -0
- package/lib/events-CjS-sm0W.d.mts +107 -0
- package/lib/events-CjS-sm0W.d.ts +107 -0
- package/lib/events-Da_cFgtc.d.mts +208 -0
- package/lib/events-Da_cFgtc.d.ts +208 -0
- package/lib/finalize.handler.d.mts +35 -0
- package/lib/finalize.handler.d.ts +35 -0
- package/lib/finalize.handler.js +875 -0
- package/lib/finalize.handler.js.map +1 -0
- package/lib/finalize.handler.mjs +166 -0
- package/lib/finalize.handler.mjs.map +1 -0
- package/lib/index.d.mts +189 -2
- package/lib/index.d.ts +500 -3
- package/lib/index.js +1753 -174
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +571 -17
- package/lib/index.mjs.map +1 -1
- package/lib/list-chunks.handler.d.mts +28 -0
- package/lib/list-chunks.handler.d.ts +28 -0
- package/lib/list-chunks.handler.js +2746 -0
- package/lib/list-chunks.handler.js.map +1 -0
- package/lib/list-chunks.handler.mjs +54 -0
- package/lib/list-chunks.handler.mjs.map +1 -0
- package/lib/platform-deploy-bridge.handler.js +76 -1
- 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 +1106 -155
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +6 -4
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +1529 -142
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +8 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rename-finalize.handler.d.mts +30 -0
- package/lib/rename-finalize.handler.d.ts +30 -0
- package/lib/rename-finalize.handler.js +795 -0
- package/lib/rename-finalize.handler.js.map +1 -0
- package/lib/rename-finalize.handler.mjs +90 -0
- package/lib/rename-finalize.handler.mjs.map +1 -0
- package/lib/rename-list-targets.handler.d.mts +26 -0
- package/lib/rename-list-targets.handler.d.ts +26 -0
- package/lib/rename-list-targets.handler.js +2985 -0
- package/lib/rename-list-targets.handler.js.map +1 -0
- package/lib/rename-list-targets.handler.mjs +431 -0
- package/lib/rename-list-targets.handler.mjs.map +1 -0
- package/lib/rename-rewrite-chunk.handler.d.mts +35 -0
- package/lib/rename-rewrite-chunk.handler.d.ts +35 -0
- package/lib/rename-rewrite-chunk.handler.js +2021 -0
- package/lib/rename-rewrite-chunk.handler.js.map +1 -0
- package/lib/rename-rewrite-chunk.handler.mjs +27 -0
- package/lib/rename-rewrite-chunk.handler.mjs.map +1 -0
- package/lib/rest-api-lambda.handler.js +4021 -932
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1786 -80
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js +1588 -124
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +10 -6
- package/lib/seed-system-data.handler.js +1179 -155
- package/lib/seed-system-data.handler.js.map +1 -1
- package/lib/seed-system-data.handler.mjs +5 -4
- package/lib/seed-system-data.handler.mjs.map +1 -1
- package/package.json +3 -3
- package/lib/chunk-2TPJ6HOF.mjs.map +0 -1
- package/lib/chunk-IS4VQRI4.mjs.map +0 -1
- package/lib/chunk-MULKGFIJ.mjs.map +0 -1
- package/lib/chunk-QR5JVSCF.mjs +0 -862
- package/lib/chunk-QR5JVSCF.mjs.map +0 -1
- package/lib/chunk-SYBADQXI.mjs.map +0 -1
- /package/lib/{chunk-7FUAMZOF.mjs.map → chunk-53OHXLIL.mjs.map} +0 -0
- /package/lib/{chunk-7Q2IJ2J5.mjs.map → chunk-CUUKXDB2.mjs.map} +0 -0
- /package/lib/{chunk-AJ3G3THO.mjs.map → chunk-KO64HPWQ.mjs.map} +0 -0
- /package/lib/{chunk-BB5MK4L3.mjs.map → chunk-KSFC72TT.mjs.map} +0 -0
- /package/lib/{chunk-AGF3RAAZ.mjs.map → chunk-WPCBVDFZ.mjs.map} +0 -0
|
@@ -134,6 +134,56 @@ var require_registry = __commonJS({
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
// ../workflows/lib/detail-types/control-plane.js
|
|
138
|
+
var require_control_plane = __commonJS({
|
|
139
|
+
"../workflows/lib/detail-types/control-plane.js"(exports2) {
|
|
140
|
+
"use strict";
|
|
141
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
142
|
+
exports2.ControlPlaneRenameFailedV1 = exports2.ControlPlaneRenameCompleteV1 = exports2.ControlPlaneRenameV1 = exports2.RENAMABLE_ENTITY_TYPE = exports2.ControlPlaneOwningDeleteFailedV1 = exports2.ControlPlaneOwningDeleteCompleteV1 = exports2.ControlPlaneOwningDeleteV1 = exports2.OWNING_ENTITY_TYPE = void 0;
|
|
143
|
+
var sources_1 = require_sources();
|
|
144
|
+
var registry_1 = require_registry();
|
|
145
|
+
exports2.OWNING_ENTITY_TYPE = {
|
|
146
|
+
Workspace: "Workspace",
|
|
147
|
+
User: "User"
|
|
148
|
+
};
|
|
149
|
+
exports2.ControlPlaneOwningDeleteV1 = (0, registry_1.defineDetailType)({
|
|
150
|
+
detailType: "control-plane.owning-delete.v1",
|
|
151
|
+
source: sources_1.OPENHI_DATA_SOURCE,
|
|
152
|
+
dedupRequired: true
|
|
153
|
+
});
|
|
154
|
+
exports2.ControlPlaneOwningDeleteCompleteV1 = (0, registry_1.defineDetailType)({
|
|
155
|
+
detailType: "control-plane.owning-delete-complete.v1",
|
|
156
|
+
source: sources_1.OPENHI_OPS_SOURCE,
|
|
157
|
+
dedupRequired: true
|
|
158
|
+
});
|
|
159
|
+
exports2.ControlPlaneOwningDeleteFailedV1 = (0, registry_1.defineDetailType)({
|
|
160
|
+
detailType: "control-plane.owning-delete-failed.v1",
|
|
161
|
+
source: sources_1.OPENHI_OPS_SOURCE,
|
|
162
|
+
dedupRequired: true
|
|
163
|
+
});
|
|
164
|
+
exports2.RENAMABLE_ENTITY_TYPE = {
|
|
165
|
+
Tenant: "Tenant",
|
|
166
|
+
User: "User",
|
|
167
|
+
Role: "Role"
|
|
168
|
+
};
|
|
169
|
+
exports2.ControlPlaneRenameV1 = (0, registry_1.defineDetailType)({
|
|
170
|
+
detailType: "control-plane.rename.v1",
|
|
171
|
+
source: sources_1.OPENHI_DATA_SOURCE,
|
|
172
|
+
dedupRequired: true
|
|
173
|
+
});
|
|
174
|
+
exports2.ControlPlaneRenameCompleteV1 = (0, registry_1.defineDetailType)({
|
|
175
|
+
detailType: "control-plane.rename-complete.v1",
|
|
176
|
+
source: sources_1.OPENHI_OPS_SOURCE,
|
|
177
|
+
dedupRequired: true
|
|
178
|
+
});
|
|
179
|
+
exports2.ControlPlaneRenameFailedV1 = (0, registry_1.defineDetailType)({
|
|
180
|
+
detailType: "control-plane.rename-failed.v1",
|
|
181
|
+
source: sources_1.OPENHI_OPS_SOURCE,
|
|
182
|
+
dedupRequired: true
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
137
187
|
// ../workflows/lib/detail-types/platform.js
|
|
138
188
|
var require_platform = __commonJS({
|
|
139
189
|
"../workflows/lib/detail-types/platform.js"(exports2) {
|
|
@@ -176,6 +226,7 @@ var require_detail_types = __commonJS({
|
|
|
176
226
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
177
227
|
};
|
|
178
228
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
229
|
+
__exportStar(require_control_plane(), exports2);
|
|
179
230
|
__exportStar(require_platform(), exports2);
|
|
180
231
|
__exportStar(require_registry(), exports2);
|
|
181
232
|
}
|
|
@@ -527,7 +578,7 @@ var require_lib = __commonJS({
|
|
|
527
578
|
"../workflows/lib/index.js"(exports2) {
|
|
528
579
|
"use strict";
|
|
529
580
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
530
|
-
exports2.workflowDedupClient = exports2.recordIfAbsent = exports2.markFailed = exports2.encodeSortKey = exports2.WorkflowDedupTableNameMissingError = exports2.WorkflowDedupInvalidInputError = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.parseWorkflowEvent = exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = exports2.workflowsClient = exports2.publishWorkflowEvent = exports2.WorkflowPublishError = exports2.isWellFormedDetailType = exports2.defineDetailType = exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = exports2.InvalidDetailTypeRegistrationError = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.workflowUserActorFromClaims = exports2.isWorkflowUserActor = exports2.isWorkflowSystemActor = exports2.MissingActorContextError = exports2.isSupportedEnvelopeVersion = exports2.ENVELOPE_VERSION = void 0;
|
|
581
|
+
exports2.workflowDedupClient = exports2.recordIfAbsent = exports2.markFailed = exports2.encodeSortKey = exports2.WorkflowDedupTableNameMissingError = exports2.WorkflowDedupInvalidInputError = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.parseWorkflowEvent = exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = exports2.workflowsClient = exports2.publishWorkflowEvent = exports2.WorkflowPublishError = exports2.isWellFormedDetailType = exports2.defineDetailType = exports2.RENAMABLE_ENTITY_TYPE = exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = exports2.OWNING_ENTITY_TYPE = exports2.InvalidDetailTypeRegistrationError = exports2.ControlPlaneRenameV1 = exports2.ControlPlaneRenameFailedV1 = exports2.ControlPlaneRenameCompleteV1 = exports2.ControlPlaneOwningDeleteV1 = exports2.ControlPlaneOwningDeleteFailedV1 = exports2.ControlPlaneOwningDeleteCompleteV1 = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.workflowUserActorFromClaims = exports2.isWorkflowUserActor = exports2.isWorkflowSystemActor = exports2.MissingActorContextError = exports2.isSupportedEnvelopeVersion = exports2.ENVELOPE_VERSION = void 0;
|
|
531
582
|
var envelope_version_1 = require_envelope_version();
|
|
532
583
|
Object.defineProperty(exports2, "ENVELOPE_VERSION", { enumerable: true, get: function() {
|
|
533
584
|
return envelope_version_1.ENVELOPE_VERSION;
|
|
@@ -562,15 +613,39 @@ var require_lib = __commonJS({
|
|
|
562
613
|
return sources_1.OPENHI_OPS_SOURCE;
|
|
563
614
|
} });
|
|
564
615
|
var detail_types_1 = require_detail_types();
|
|
616
|
+
Object.defineProperty(exports2, "ControlPlaneOwningDeleteCompleteV1", { enumerable: true, get: function() {
|
|
617
|
+
return detail_types_1.ControlPlaneOwningDeleteCompleteV1;
|
|
618
|
+
} });
|
|
619
|
+
Object.defineProperty(exports2, "ControlPlaneOwningDeleteFailedV1", { enumerable: true, get: function() {
|
|
620
|
+
return detail_types_1.ControlPlaneOwningDeleteFailedV1;
|
|
621
|
+
} });
|
|
622
|
+
Object.defineProperty(exports2, "ControlPlaneOwningDeleteV1", { enumerable: true, get: function() {
|
|
623
|
+
return detail_types_1.ControlPlaneOwningDeleteV1;
|
|
624
|
+
} });
|
|
625
|
+
Object.defineProperty(exports2, "ControlPlaneRenameCompleteV1", { enumerable: true, get: function() {
|
|
626
|
+
return detail_types_1.ControlPlaneRenameCompleteV1;
|
|
627
|
+
} });
|
|
628
|
+
Object.defineProperty(exports2, "ControlPlaneRenameFailedV1", { enumerable: true, get: function() {
|
|
629
|
+
return detail_types_1.ControlPlaneRenameFailedV1;
|
|
630
|
+
} });
|
|
631
|
+
Object.defineProperty(exports2, "ControlPlaneRenameV1", { enumerable: true, get: function() {
|
|
632
|
+
return detail_types_1.ControlPlaneRenameV1;
|
|
633
|
+
} });
|
|
565
634
|
Object.defineProperty(exports2, "InvalidDetailTypeRegistrationError", { enumerable: true, get: function() {
|
|
566
635
|
return detail_types_1.InvalidDetailTypeRegistrationError;
|
|
567
636
|
} });
|
|
637
|
+
Object.defineProperty(exports2, "OWNING_ENTITY_TYPE", { enumerable: true, get: function() {
|
|
638
|
+
return detail_types_1.OWNING_ENTITY_TYPE;
|
|
639
|
+
} });
|
|
568
640
|
Object.defineProperty(exports2, "PlatformDeploymentCompletedV1", { enumerable: true, get: function() {
|
|
569
641
|
return detail_types_1.PlatformDeploymentCompletedV1;
|
|
570
642
|
} });
|
|
571
643
|
Object.defineProperty(exports2, "PlatformSystemDataSeededV1", { enumerable: true, get: function() {
|
|
572
644
|
return detail_types_1.PlatformSystemDataSeededV1;
|
|
573
645
|
} });
|
|
646
|
+
Object.defineProperty(exports2, "RENAMABLE_ENTITY_TYPE", { enumerable: true, get: function() {
|
|
647
|
+
return detail_types_1.RENAMABLE_ENTITY_TYPE;
|
|
648
|
+
} });
|
|
574
649
|
Object.defineProperty(exports2, "defineDetailType", { enumerable: true, get: function() {
|
|
575
650
|
return detail_types_1.defineDetailType;
|
|
576
651
|
} });
|
|
@@ -642,7 +717,7 @@ module.exports = __toCommonJS(seed_demo_data_handler_exports);
|
|
|
642
717
|
var import_node_crypto = require("crypto");
|
|
643
718
|
var import_client_cognito_identity_provider = require("@aws-sdk/client-cognito-identity-provider");
|
|
644
719
|
var import_client_dynamodb2 = require("@aws-sdk/client-dynamodb");
|
|
645
|
-
var
|
|
720
|
+
var import_types12 = require("@openhi/types");
|
|
646
721
|
var import_workflows2 = __toESM(require_lib());
|
|
647
722
|
|
|
648
723
|
// src/workflows/control-plane/seed-demo-data/events.ts
|
|
@@ -739,7 +814,7 @@ var demoRolesForUserInTenant = (_user, _tenantId) => {
|
|
|
739
814
|
};
|
|
740
815
|
|
|
741
816
|
// src/data/dynamo/dynamo-control-service.ts
|
|
742
|
-
var
|
|
817
|
+
var import_electrodb14 = require("electrodb");
|
|
743
818
|
|
|
744
819
|
// src/data/dynamo/dynamo-client.ts
|
|
745
820
|
var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
@@ -803,6 +878,60 @@ var gsi1skAttribute = {
|
|
|
803
878
|
return label !== void 0 ? `${label}#${id}` : fallback;
|
|
804
879
|
}
|
|
805
880
|
};
|
|
881
|
+
function extractRoleId(resource) {
|
|
882
|
+
const flat = resource.roleId;
|
|
883
|
+
if (typeof flat === "string" && flat.length > 0) return flat;
|
|
884
|
+
const role = resource.role;
|
|
885
|
+
if (role && typeof role === "object") {
|
|
886
|
+
const reference = role.reference;
|
|
887
|
+
if (typeof reference === "string" && reference.length > 0) {
|
|
888
|
+
const slash = reference.lastIndexOf("/");
|
|
889
|
+
const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
|
|
890
|
+
if (tail.length > 0) return tail;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return void 0;
|
|
894
|
+
}
|
|
895
|
+
var roleAssignmentGsi1skAttribute = {
|
|
896
|
+
type: "string",
|
|
897
|
+
watch: ["resource", "denormalizedUserName", "lastUpdated", "id"],
|
|
898
|
+
set: (_val, item) => {
|
|
899
|
+
const id = typeof item?.id === "string" ? item.id : "";
|
|
900
|
+
const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
|
|
901
|
+
const fallback = `${lastUpdated}#${id}`;
|
|
902
|
+
if (typeof item?.resource !== "string" || item.resource.length === 0) {
|
|
903
|
+
return fallback;
|
|
904
|
+
}
|
|
905
|
+
let parsed;
|
|
906
|
+
try {
|
|
907
|
+
parsed = JSON.parse(item.resource);
|
|
908
|
+
} catch {
|
|
909
|
+
return fallback;
|
|
910
|
+
}
|
|
911
|
+
if (!parsed || typeof parsed !== "object") return fallback;
|
|
912
|
+
const roleId = extractRoleId(parsed);
|
|
913
|
+
if (roleId === void 0) return fallback;
|
|
914
|
+
const denormalizedUserName = typeof item.denormalizedUserName === "string" ? item.denormalizedUserName : "";
|
|
915
|
+
const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types2.normalizeLabel)(denormalizedUserName) : "";
|
|
916
|
+
if (normalizedUserName.length === 0) return fallback;
|
|
917
|
+
return `${roleId}#${normalizedUserName}#${id}`;
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
var membershipGsi1skAttribute = {
|
|
921
|
+
type: "string",
|
|
922
|
+
watch: ["denormalizedUserName", "lastUpdated", "id"],
|
|
923
|
+
set: (_val, item) => {
|
|
924
|
+
const id = typeof item?.id === "string" ? item.id : "";
|
|
925
|
+
const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
|
|
926
|
+
const fallback = `${lastUpdated}#${id}`;
|
|
927
|
+
const denormalizedUserName = typeof item?.denormalizedUserName === "string" ? item.denormalizedUserName : "";
|
|
928
|
+
const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types2.normalizeLabel)(denormalizedUserName) : "";
|
|
929
|
+
if (normalizedUserName.length === 0) {
|
|
930
|
+
return fallback;
|
|
931
|
+
}
|
|
932
|
+
return `${normalizedUserName}#${id}`;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
806
935
|
|
|
807
936
|
// src/data/dynamo/entities/control/configuration-entity.ts
|
|
808
937
|
var ConfigurationEntity = new import_electrodb.Entity({
|
|
@@ -929,9 +1058,239 @@ var ConfigurationEntity = new import_electrodb.Entity({
|
|
|
929
1058
|
}
|
|
930
1059
|
});
|
|
931
1060
|
|
|
932
|
-
// src/data/dynamo/entities/control/
|
|
1061
|
+
// src/data/dynamo/entities/control/configuration-user-projection-entity.ts
|
|
933
1062
|
var import_electrodb2 = require("electrodb");
|
|
934
|
-
var
|
|
1063
|
+
var ConfigurationUserProjectionEntity = new import_electrodb2.Entity({
|
|
1064
|
+
model: {
|
|
1065
|
+
entity: "configurationUserProjection",
|
|
1066
|
+
service: "control",
|
|
1067
|
+
version: "01"
|
|
1068
|
+
},
|
|
1069
|
+
attributes: {
|
|
1070
|
+
/**
|
|
1071
|
+
* User partition discriminator. Renders as `USER#ID#<userId>` on the
|
|
1072
|
+
* base-table PK. Always required — the projection has no meaning
|
|
1073
|
+
* outside a user partition.
|
|
1074
|
+
*/
|
|
1075
|
+
userId: {
|
|
1076
|
+
type: "string",
|
|
1077
|
+
required: true
|
|
1078
|
+
},
|
|
1079
|
+
/**
|
|
1080
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1081
|
+
* writer via `buildConfigurationUserProjectionSk`. The entity stores
|
|
1082
|
+
* the value verbatim so the SK grammar (pattern #10 user-scope) is
|
|
1083
|
+
* owned by the operations layer, not duplicated here.
|
|
1084
|
+
*/
|
|
1085
|
+
sk: {
|
|
1086
|
+
type: "string",
|
|
1087
|
+
required: true
|
|
1088
|
+
},
|
|
1089
|
+
/**
|
|
1090
|
+
* Configuration canonical-record id. Stored as a discriminating
|
|
1091
|
+
* field so consumers can hydrate the canonical row via the
|
|
1092
|
+
* Configuration get-by-id operation when the projection's `summary`
|
|
1093
|
+
* is insufficient.
|
|
1094
|
+
*/
|
|
1095
|
+
configurationId: {
|
|
1096
|
+
type: "string",
|
|
1097
|
+
required: true
|
|
1098
|
+
},
|
|
1099
|
+
/**
|
|
1100
|
+
* Tenant the Configuration is associated with. The canonical row
|
|
1101
|
+
* keys off `(tenantId, workspaceId, userId, roleId)`; the projection
|
|
1102
|
+
* carries `tenantId` so consumers reconstructing the canonical PK
|
|
1103
|
+
* have the tenant segment without a hop.
|
|
1104
|
+
*/
|
|
1105
|
+
tenantId: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
required: true
|
|
1108
|
+
},
|
|
1109
|
+
/**
|
|
1110
|
+
* Scope marker. Always `"user"` on this projection — recorded
|
|
1111
|
+
* explicitly so future scope-bearing projections (workspace,
|
|
1112
|
+
* tenant, role) can share filter semantics in a unified
|
|
1113
|
+
* cross-projection list query if one ever lands.
|
|
1114
|
+
*/
|
|
1115
|
+
scope: {
|
|
1116
|
+
type: "string",
|
|
1117
|
+
required: true,
|
|
1118
|
+
default: "user"
|
|
1119
|
+
},
|
|
1120
|
+
/**
|
|
1121
|
+
* Configuration's `key` attribute (config category, e.g. endpoints,
|
|
1122
|
+
* branding, display). Mirrored from the canonical row so consumers
|
|
1123
|
+
* reading the projection get the natural display label without a
|
|
1124
|
+
* BatchGet hop. Doubles as the source of `<normalizedConfigName>` in
|
|
1125
|
+
* the SK.
|
|
1126
|
+
*/
|
|
1127
|
+
displayName: {
|
|
1128
|
+
type: "string",
|
|
1129
|
+
required: false
|
|
1130
|
+
},
|
|
1131
|
+
/**
|
|
1132
|
+
* Summary projection (key display fields as JSON string) — mirrored
|
|
1133
|
+
* from the canonical Configuration row so user-partition queries do
|
|
1134
|
+
* not need a BatchGet hop.
|
|
1135
|
+
*/
|
|
1136
|
+
summary: {
|
|
1137
|
+
type: "string",
|
|
1138
|
+
required: true
|
|
1139
|
+
},
|
|
1140
|
+
/** Version id mirrored from the canonical Configuration row. */
|
|
1141
|
+
vid: {
|
|
1142
|
+
type: "string",
|
|
1143
|
+
required: true
|
|
1144
|
+
},
|
|
1145
|
+
/** Last-updated timestamp mirrored from the canonical Configuration row. */
|
|
1146
|
+
lastUpdated: {
|
|
1147
|
+
type: "string",
|
|
1148
|
+
required: true
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
indexes: {
|
|
1152
|
+
/**
|
|
1153
|
+
* Base table: PK = USER#ID#<userId>, SK = operation-supplied. A
|
|
1154
|
+
* single `Query(PK = USER#ID#<userId>, SK begins_with
|
|
1155
|
+
* 'CONFIGURATION#')` returns the user's user-scoped Configurations
|
|
1156
|
+
* sorted by `<normalizedConfigName>` (then `<configurationId>` as
|
|
1157
|
+
* the tiebreaker).
|
|
1158
|
+
*/
|
|
1159
|
+
record: {
|
|
1160
|
+
pk: {
|
|
1161
|
+
field: "PK",
|
|
1162
|
+
composite: ["userId"],
|
|
1163
|
+
template: "USER#ID#${userId}"
|
|
1164
|
+
},
|
|
1165
|
+
sk: {
|
|
1166
|
+
field: "SK",
|
|
1167
|
+
casing: "none",
|
|
1168
|
+
composite: ["sk"],
|
|
1169
|
+
template: "${sk}"
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
// src/data/dynamo/entities/control/configuration-workspace-projection-entity.ts
|
|
1176
|
+
var import_electrodb3 = require("electrodb");
|
|
1177
|
+
var ConfigurationWorkspaceProjectionEntity = new import_electrodb3.Entity({
|
|
1178
|
+
model: {
|
|
1179
|
+
entity: "configurationWorkspaceProjection",
|
|
1180
|
+
service: "control",
|
|
1181
|
+
version: "01"
|
|
1182
|
+
},
|
|
1183
|
+
attributes: {
|
|
1184
|
+
/**
|
|
1185
|
+
* Tenant the workspace belongs to. Renders as the leading segment
|
|
1186
|
+
* of the base-table PK. Always required — the workspace partition
|
|
1187
|
+
* is tenant-scoped per ADR-011.
|
|
1188
|
+
*/
|
|
1189
|
+
tenantId: {
|
|
1190
|
+
type: "string",
|
|
1191
|
+
required: true
|
|
1192
|
+
},
|
|
1193
|
+
/**
|
|
1194
|
+
* Workspace partition discriminator. Renders as the trailing
|
|
1195
|
+
* segment of the base-table PK
|
|
1196
|
+
* (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
|
|
1197
|
+
* the projection has no meaning outside a workspace partition.
|
|
1198
|
+
*/
|
|
1199
|
+
workspaceId: {
|
|
1200
|
+
type: "string",
|
|
1201
|
+
required: true
|
|
1202
|
+
},
|
|
1203
|
+
/**
|
|
1204
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1205
|
+
* writer via `buildConfigurationWorkspaceProjectionSk`. The entity
|
|
1206
|
+
* stores the value verbatim so the SK grammar (pattern #10
|
|
1207
|
+
* workspace-scope) is owned by the operations layer, not
|
|
1208
|
+
* duplicated here.
|
|
1209
|
+
*/
|
|
1210
|
+
sk: {
|
|
1211
|
+
type: "string",
|
|
1212
|
+
required: true
|
|
1213
|
+
},
|
|
1214
|
+
/**
|
|
1215
|
+
* Configuration canonical-record id. Stored as a discriminating
|
|
1216
|
+
* field so consumers can hydrate the canonical row via the
|
|
1217
|
+
* Configuration get-by-id operation when the projection's `summary`
|
|
1218
|
+
* is insufficient.
|
|
1219
|
+
*/
|
|
1220
|
+
configurationId: {
|
|
1221
|
+
type: "string",
|
|
1222
|
+
required: true
|
|
1223
|
+
},
|
|
1224
|
+
/**
|
|
1225
|
+
* Scope marker. Always `"workspace"` on this projection — recorded
|
|
1226
|
+
* explicitly so future scope-bearing projections (user, tenant,
|
|
1227
|
+
* role) can share filter semantics in a unified cross-projection
|
|
1228
|
+
* list query if one ever lands.
|
|
1229
|
+
*/
|
|
1230
|
+
scope: {
|
|
1231
|
+
type: "string",
|
|
1232
|
+
required: true,
|
|
1233
|
+
default: "workspace"
|
|
1234
|
+
},
|
|
1235
|
+
/**
|
|
1236
|
+
* Configuration's `key` attribute (config category, e.g. endpoints,
|
|
1237
|
+
* branding, display). Mirrored from the canonical row so consumers
|
|
1238
|
+
* reading the projection get the natural display label without a
|
|
1239
|
+
* BatchGet hop. Doubles as the source of `<normalizedConfigName>`
|
|
1240
|
+
* in the SK.
|
|
1241
|
+
*/
|
|
1242
|
+
displayName: {
|
|
1243
|
+
type: "string",
|
|
1244
|
+
required: false
|
|
1245
|
+
},
|
|
1246
|
+
/**
|
|
1247
|
+
* Summary projection (key display fields as JSON string) — mirrored
|
|
1248
|
+
* from the canonical Configuration row so workspace-partition
|
|
1249
|
+
* queries do not need a BatchGet hop.
|
|
1250
|
+
*/
|
|
1251
|
+
summary: {
|
|
1252
|
+
type: "string",
|
|
1253
|
+
required: true
|
|
1254
|
+
},
|
|
1255
|
+
/** Version id mirrored from the canonical Configuration row. */
|
|
1256
|
+
vid: {
|
|
1257
|
+
type: "string",
|
|
1258
|
+
required: true
|
|
1259
|
+
},
|
|
1260
|
+
/** Last-updated timestamp mirrored from the canonical Configuration row. */
|
|
1261
|
+
lastUpdated: {
|
|
1262
|
+
type: "string",
|
|
1263
|
+
required: true
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
indexes: {
|
|
1267
|
+
/**
|
|
1268
|
+
* Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
|
|
1269
|
+
* SK = operation-supplied. A single
|
|
1270
|
+
* `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'CONFIGURATION#')`
|
|
1271
|
+
* returns the workspace's workspace-scoped Configurations sorted by
|
|
1272
|
+
* `<normalizedConfigName>` (then `<configurationId>` as the
|
|
1273
|
+
* tiebreaker).
|
|
1274
|
+
*/
|
|
1275
|
+
record: {
|
|
1276
|
+
pk: {
|
|
1277
|
+
field: "PK",
|
|
1278
|
+
composite: ["tenantId", "workspaceId"],
|
|
1279
|
+
template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
|
|
1280
|
+
},
|
|
1281
|
+
sk: {
|
|
1282
|
+
field: "SK",
|
|
1283
|
+
casing: "none",
|
|
1284
|
+
composite: ["sk"],
|
|
1285
|
+
template: "${sk}"
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
// src/data/dynamo/entities/control/membership-entity.ts
|
|
1292
|
+
var import_electrodb4 = require("electrodb");
|
|
1293
|
+
var MembershipEntity = new import_electrodb4.Entity({
|
|
935
1294
|
model: {
|
|
936
1295
|
entity: "membership",
|
|
937
1296
|
service: "control",
|
|
@@ -977,8 +1336,14 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
977
1336
|
required: true
|
|
978
1337
|
},
|
|
979
1338
|
gsi1Shard: gsi1ShardAttribute,
|
|
980
|
-
/**
|
|
981
|
-
|
|
1339
|
+
/**
|
|
1340
|
+
* Derived GSI1 sort key — `<normalizedUserName>#<id>` per ADR-018
|
|
1341
|
+
* pattern #1 so a GSI1 query partitioned on the tenant range-scans
|
|
1342
|
+
* by user-name prefix and returns memberships sorted by user name.
|
|
1343
|
+
* Falls back to `<lastUpdated>#<id>` when `denormalizedUserName`
|
|
1344
|
+
* is missing.
|
|
1345
|
+
*/
|
|
1346
|
+
gsi1sk: membershipGsi1skAttribute,
|
|
982
1347
|
deleted: {
|
|
983
1348
|
type: "boolean",
|
|
984
1349
|
required: false
|
|
@@ -1000,6 +1365,36 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
1000
1365
|
linkedDataIdentityRef: {
|
|
1001
1366
|
type: "string",
|
|
1002
1367
|
required: false
|
|
1368
|
+
},
|
|
1369
|
+
/**
|
|
1370
|
+
* Denormalized display name of the linked Tenant, captured at row
|
|
1371
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1372
|
+
* adjacency-list projection SKs (pattern #3 — `MEMBERSHIP#TENANT#<normalizedTenantName>#…`)
|
|
1373
|
+
* can be composed from a top-level field instead of digging into the
|
|
1374
|
+
* `resource` JSON. Optional on the schema so pre-TR-024 rows do not
|
|
1375
|
+
* break; the operations-layer multi-write helper (#1010) makes the
|
|
1376
|
+
* field load-bearing at write time per TR-024 rule 2 (write-time
|
|
1377
|
+
* source = canonical Tenant.displayName).
|
|
1378
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1379
|
+
*/
|
|
1380
|
+
denormalizedTenantName: {
|
|
1381
|
+
type: "string",
|
|
1382
|
+
required: false
|
|
1383
|
+
},
|
|
1384
|
+
/**
|
|
1385
|
+
* Denormalized display name of the linked User, captured at row
|
|
1386
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1387
|
+
* adjacency-list canonical-record GSI1SK (pattern #1 —
|
|
1388
|
+
* `<normalizedUserName>#<id>`) and workspace-projection SK (pattern #2)
|
|
1389
|
+
* can be composed from a top-level field. Optional on the schema so
|
|
1390
|
+
* pre-TR-024 rows do not break; the operations-layer multi-write helper
|
|
1391
|
+
* (#1010) makes the field load-bearing at write time per TR-024 rule 2
|
|
1392
|
+
* (write-time source = canonical User.displayName).
|
|
1393
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1394
|
+
*/
|
|
1395
|
+
denormalizedUserName: {
|
|
1396
|
+
type: "string",
|
|
1397
|
+
required: false
|
|
1003
1398
|
}
|
|
1004
1399
|
},
|
|
1005
1400
|
indexes: {
|
|
@@ -1019,9 +1414,11 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
1019
1414
|
/**
|
|
1020
1415
|
* GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
|
|
1021
1416
|
* four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
|
|
1022
|
-
* SK is derived via `
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1417
|
+
* SK is derived via `membershipGsi1skAttribute` — composes
|
|
1418
|
+
* `<normalizedUserName>#<id>` per ADR-018 pattern #1 (users in a
|
|
1419
|
+
* tenant, sorted by user name); falls back to `<lastUpdated>#<id>`
|
|
1420
|
+
* when `denormalizedUserName` is missing. `casing: "none"` preserves
|
|
1421
|
+
* the normalized label and ISO-8601 `T`/`Z`.
|
|
1025
1422
|
*/
|
|
1026
1423
|
gsi1: {
|
|
1027
1424
|
index: "GSI1",
|
|
@@ -1040,50 +1437,292 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
1040
1437
|
}
|
|
1041
1438
|
});
|
|
1042
1439
|
|
|
1043
|
-
// src/data/dynamo/entities/control/
|
|
1044
|
-
var
|
|
1045
|
-
var
|
|
1440
|
+
// src/data/dynamo/entities/control/membership-user-projection-entity.ts
|
|
1441
|
+
var import_electrodb5 = require("electrodb");
|
|
1442
|
+
var MembershipUserProjectionEntity = new import_electrodb5.Entity({
|
|
1046
1443
|
model: {
|
|
1047
|
-
entity: "
|
|
1444
|
+
entity: "membershipUserProjection",
|
|
1048
1445
|
service: "control",
|
|
1049
1446
|
version: "01"
|
|
1050
1447
|
},
|
|
1051
1448
|
attributes: {
|
|
1052
|
-
/**
|
|
1449
|
+
/**
|
|
1450
|
+
* User partition discriminator. Renders as `USER#ID#<userId>` on the
|
|
1451
|
+
* base-table PK. Always required — the projection has no meaning
|
|
1452
|
+
* outside a user partition.
|
|
1453
|
+
*/
|
|
1454
|
+
userId: {
|
|
1455
|
+
type: "string",
|
|
1456
|
+
required: true
|
|
1457
|
+
},
|
|
1458
|
+
/**
|
|
1459
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1460
|
+
* writer via `buildMembershipUserProjectionSk*` helpers. The entity
|
|
1461
|
+
* stores the value verbatim so the SK grammar (patterns #3 and #4)
|
|
1462
|
+
* is owned by the operations layer, not duplicated here.
|
|
1463
|
+
*/
|
|
1053
1464
|
sk: {
|
|
1054
1465
|
type: "string",
|
|
1055
|
-
required: true
|
|
1056
|
-
default: "CURRENT"
|
|
1466
|
+
required: true
|
|
1057
1467
|
},
|
|
1058
|
-
/**
|
|
1059
|
-
|
|
1468
|
+
/** Tenant in which the membership applies. Always required. */
|
|
1469
|
+
tenantId: {
|
|
1060
1470
|
type: "string",
|
|
1061
1471
|
required: true
|
|
1062
1472
|
},
|
|
1063
|
-
/**
|
|
1064
|
-
|
|
1473
|
+
/**
|
|
1474
|
+
* Workspace the membership scopes to. Present iff the projection
|
|
1475
|
+
* row is a pattern-#4 workspace sub-lane row; absent for pattern-#3
|
|
1476
|
+
* tenant sub-lane rows.
|
|
1477
|
+
*/
|
|
1478
|
+
workspaceId: {
|
|
1479
|
+
type: "string",
|
|
1480
|
+
required: false
|
|
1481
|
+
},
|
|
1482
|
+
/**
|
|
1483
|
+
* Membership canonical-record id. Stored as a discriminating field
|
|
1484
|
+
* so consumers can hydrate the canonical row via
|
|
1485
|
+
* `MembershipEntity.get({ tenantId, id: membershipId })` when the
|
|
1486
|
+
* projection's `summary` is insufficient.
|
|
1487
|
+
*/
|
|
1488
|
+
membershipId: {
|
|
1065
1489
|
type: "string",
|
|
1066
1490
|
required: true
|
|
1067
1491
|
},
|
|
1068
1492
|
/**
|
|
1069
|
-
* Summary projection (key display fields as JSON string: id,
|
|
1070
|
-
*
|
|
1493
|
+
* Summary projection (key display fields as JSON string: id,
|
|
1494
|
+
* displayName, status) — mirrored from the canonical Membership row
|
|
1495
|
+
* so user-partition queries do not need a BatchGet hop.
|
|
1071
1496
|
*/
|
|
1072
1497
|
summary: {
|
|
1073
1498
|
type: "string",
|
|
1074
1499
|
required: true
|
|
1075
1500
|
},
|
|
1076
|
-
/** Version id
|
|
1501
|
+
/** Version id mirrored from the canonical Membership row. */
|
|
1077
1502
|
vid: {
|
|
1078
1503
|
type: "string",
|
|
1079
1504
|
required: true
|
|
1080
1505
|
},
|
|
1506
|
+
/** Last-updated timestamp mirrored from the canonical Membership row. */
|
|
1081
1507
|
lastUpdated: {
|
|
1082
1508
|
type: "string",
|
|
1083
1509
|
required: true
|
|
1084
1510
|
},
|
|
1085
|
-
|
|
1086
|
-
|
|
1511
|
+
/**
|
|
1512
|
+
* Denormalized Tenant display name — required to compose pattern-#3
|
|
1513
|
+
* SK (`MEMBERSHIP#TENANT#<normalizedTenantName>#…`). Optional on the
|
|
1514
|
+
* schema because pre-TR-024 rows may not carry a display name; the
|
|
1515
|
+
* operations layer falls back gracefully when missing.
|
|
1516
|
+
*/
|
|
1517
|
+
denormalizedTenantName: {
|
|
1518
|
+
type: "string",
|
|
1519
|
+
required: false
|
|
1520
|
+
},
|
|
1521
|
+
/**
|
|
1522
|
+
* Denormalized User display name — mirrored from the canonical
|
|
1523
|
+
* Membership row per TR-024 rule 3 (canonical-record symmetry).
|
|
1524
|
+
* Carried on the projection so consumers can render the user's
|
|
1525
|
+
* display name without a hop to the User record.
|
|
1526
|
+
*/
|
|
1527
|
+
denormalizedUserName: {
|
|
1528
|
+
type: "string",
|
|
1529
|
+
required: false
|
|
1530
|
+
},
|
|
1531
|
+
/**
|
|
1532
|
+
* Denormalized Workspace display name — required to compose
|
|
1533
|
+
* pattern-#4 SK (`MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#…`).
|
|
1534
|
+
* Optional on the schema (TR-024 § Open Item #4 defers a formal
|
|
1535
|
+
* Workspace-rename cascade); the operations layer falls back to a
|
|
1536
|
+
* sentinel when missing so the SK still has a valid shape.
|
|
1537
|
+
*/
|
|
1538
|
+
denormalizedWorkspaceName: {
|
|
1539
|
+
type: "string",
|
|
1540
|
+
required: false
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
indexes: {
|
|
1544
|
+
/**
|
|
1545
|
+
* Base table: PK = USER#ID#<userId>, SK = operation-supplied.
|
|
1546
|
+
* Both pattern #3 and pattern #4 use this same index — the SK string
|
|
1547
|
+
* encodes the lane discriminator (`MEMBERSHIP#TENANT#…` vs
|
|
1548
|
+
* `MEMBERSHIP#WORKSPACE#…`) so a single `Query(PK = USER#ID#<userId>,
|
|
1549
|
+
* SK begins_with 'MEMBERSHIP#')` returns both lanes interleaved.
|
|
1550
|
+
*/
|
|
1551
|
+
record: {
|
|
1552
|
+
pk: {
|
|
1553
|
+
field: "PK",
|
|
1554
|
+
composite: ["userId"],
|
|
1555
|
+
template: "USER#ID#${userId}"
|
|
1556
|
+
},
|
|
1557
|
+
sk: {
|
|
1558
|
+
field: "SK",
|
|
1559
|
+
casing: "none",
|
|
1560
|
+
composite: ["sk"],
|
|
1561
|
+
template: "${sk}"
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
// src/data/dynamo/entities/control/membership-workspace-projection-entity.ts
|
|
1568
|
+
var import_electrodb6 = require("electrodb");
|
|
1569
|
+
var MembershipWorkspaceProjectionEntity = new import_electrodb6.Entity({
|
|
1570
|
+
model: {
|
|
1571
|
+
entity: "membershipWorkspaceProjection",
|
|
1572
|
+
service: "control",
|
|
1573
|
+
version: "01"
|
|
1574
|
+
},
|
|
1575
|
+
attributes: {
|
|
1576
|
+
/**
|
|
1577
|
+
* Tenant the workspace belongs to. Renders as the leading segment
|
|
1578
|
+
* of the base-table PK. Always required — the workspace partition
|
|
1579
|
+
* is tenant-scoped per ADR-011.
|
|
1580
|
+
*/
|
|
1581
|
+
tenantId: {
|
|
1582
|
+
type: "string",
|
|
1583
|
+
required: true
|
|
1584
|
+
},
|
|
1585
|
+
/**
|
|
1586
|
+
* Workspace partition discriminator. Renders as the trailing
|
|
1587
|
+
* segment of the base-table PK
|
|
1588
|
+
* (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
|
|
1589
|
+
* the projection has no meaning outside a workspace partition.
|
|
1590
|
+
*/
|
|
1591
|
+
workspaceId: {
|
|
1592
|
+
type: "string",
|
|
1593
|
+
required: true
|
|
1594
|
+
},
|
|
1595
|
+
/**
|
|
1596
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1597
|
+
* writer via `buildMembershipWorkspaceProjectionSk`. The entity
|
|
1598
|
+
* stores the value verbatim so the SK grammar (pattern #2) is
|
|
1599
|
+
* owned by the operations layer, not duplicated here.
|
|
1600
|
+
*/
|
|
1601
|
+
sk: {
|
|
1602
|
+
type: "string",
|
|
1603
|
+
required: true
|
|
1604
|
+
},
|
|
1605
|
+
/**
|
|
1606
|
+
* User the membership links. Stored as a discriminating field so
|
|
1607
|
+
* consumers can hydrate the canonical User row via
|
|
1608
|
+
* `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
|
|
1609
|
+
* projection's `summary` is insufficient.
|
|
1610
|
+
*/
|
|
1611
|
+
userId: {
|
|
1612
|
+
type: "string",
|
|
1613
|
+
required: true
|
|
1614
|
+
},
|
|
1615
|
+
/**
|
|
1616
|
+
* Membership canonical-record id. Stored as a discriminating field
|
|
1617
|
+
* so consumers can hydrate the canonical row via
|
|
1618
|
+
* `MembershipEntity.get({ tenantId, id: membershipId })` when the
|
|
1619
|
+
* projection's `summary` is insufficient.
|
|
1620
|
+
*/
|
|
1621
|
+
membershipId: {
|
|
1622
|
+
type: "string",
|
|
1623
|
+
required: true
|
|
1624
|
+
},
|
|
1625
|
+
/**
|
|
1626
|
+
* Summary projection (key display fields as JSON string: id,
|
|
1627
|
+
* displayName, status) — mirrored from the canonical Membership row
|
|
1628
|
+
* so workspace-partition queries do not need a BatchGet hop.
|
|
1629
|
+
*/
|
|
1630
|
+
summary: {
|
|
1631
|
+
type: "string",
|
|
1632
|
+
required: true
|
|
1633
|
+
},
|
|
1634
|
+
/** Version id mirrored from the canonical Membership row. */
|
|
1635
|
+
vid: {
|
|
1636
|
+
type: "string",
|
|
1637
|
+
required: true
|
|
1638
|
+
},
|
|
1639
|
+
/** Last-updated timestamp mirrored from the canonical Membership row. */
|
|
1640
|
+
lastUpdated: {
|
|
1641
|
+
type: "string",
|
|
1642
|
+
required: true
|
|
1643
|
+
},
|
|
1644
|
+
/**
|
|
1645
|
+
* Denormalized User display name — required to compose the
|
|
1646
|
+
* pattern-#2 SK (`MEMBERSHIP#<normalizedUserName>#…`). Optional on
|
|
1647
|
+
* the schema because pre-TR-024 rows may not carry a display name;
|
|
1648
|
+
* the operations layer falls back to a sentinel when missing so
|
|
1649
|
+
* the SK still has a valid shape. The TR-023 rename-cascade
|
|
1650
|
+
* pipeline rewrites the SK on a User rename.
|
|
1651
|
+
*/
|
|
1652
|
+
denormalizedUserName: {
|
|
1653
|
+
type: "string",
|
|
1654
|
+
required: false
|
|
1655
|
+
}
|
|
1656
|
+
},
|
|
1657
|
+
indexes: {
|
|
1658
|
+
/**
|
|
1659
|
+
* Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
|
|
1660
|
+
* SK = operation-supplied. Pattern #2 uses this index — the SK
|
|
1661
|
+
* encodes the entity-type prefix (`MEMBERSHIP#…`) so a
|
|
1662
|
+
* `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`
|
|
1663
|
+
* returns every member projection for the workspace in normalized-
|
|
1664
|
+
* user-name sort order.
|
|
1665
|
+
*/
|
|
1666
|
+
record: {
|
|
1667
|
+
pk: {
|
|
1668
|
+
field: "PK",
|
|
1669
|
+
composite: ["tenantId", "workspaceId"],
|
|
1670
|
+
template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
|
|
1671
|
+
},
|
|
1672
|
+
sk: {
|
|
1673
|
+
field: "SK",
|
|
1674
|
+
casing: "none",
|
|
1675
|
+
composite: ["sk"],
|
|
1676
|
+
template: "${sk}"
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
// src/data/dynamo/entities/control/role-entity.ts
|
|
1683
|
+
var import_electrodb7 = require("electrodb");
|
|
1684
|
+
var RoleEntity = new import_electrodb7.Entity({
|
|
1685
|
+
model: {
|
|
1686
|
+
entity: "role",
|
|
1687
|
+
service: "control",
|
|
1688
|
+
version: "01"
|
|
1689
|
+
},
|
|
1690
|
+
attributes: {
|
|
1691
|
+
/** Sort key sentinel. Always "CURRENT". */
|
|
1692
|
+
sk: {
|
|
1693
|
+
type: "string",
|
|
1694
|
+
required: true,
|
|
1695
|
+
default: "CURRENT"
|
|
1696
|
+
},
|
|
1697
|
+
/** FHIR Resource.id; role id. */
|
|
1698
|
+
id: {
|
|
1699
|
+
type: "string",
|
|
1700
|
+
required: true
|
|
1701
|
+
},
|
|
1702
|
+
/** Full Role resource serialized as JSON string. */
|
|
1703
|
+
resource: {
|
|
1704
|
+
type: "string",
|
|
1705
|
+
required: true
|
|
1706
|
+
},
|
|
1707
|
+
/**
|
|
1708
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
1709
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
1710
|
+
*/
|
|
1711
|
+
summary: {
|
|
1712
|
+
type: "string",
|
|
1713
|
+
required: true
|
|
1714
|
+
},
|
|
1715
|
+
/** Version id (e.g. ULID). */
|
|
1716
|
+
vid: {
|
|
1717
|
+
type: "string",
|
|
1718
|
+
required: true
|
|
1719
|
+
},
|
|
1720
|
+
lastUpdated: {
|
|
1721
|
+
type: "string",
|
|
1722
|
+
required: true
|
|
1723
|
+
},
|
|
1724
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
1725
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
1087
1726
|
gsi1sk: gsi1skAttribute,
|
|
1088
1727
|
deleted: {
|
|
1089
1728
|
type: "boolean",
|
|
@@ -1119,127 +1758,460 @@ var RoleEntity = new import_electrodb3.Entity({
|
|
|
1119
1758
|
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
1120
1759
|
* normalized label and ISO-8601 `T`/`Z`.
|
|
1121
1760
|
*/
|
|
1122
|
-
gsi1: {
|
|
1123
|
-
index: "GSI1",
|
|
1761
|
+
gsi1: {
|
|
1762
|
+
index: "GSI1",
|
|
1763
|
+
pk: {
|
|
1764
|
+
field: "GSI1PK",
|
|
1765
|
+
composite: ["gsi1Shard"],
|
|
1766
|
+
template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
|
|
1767
|
+
},
|
|
1768
|
+
sk: {
|
|
1769
|
+
field: "GSI1SK",
|
|
1770
|
+
casing: "none",
|
|
1771
|
+
composite: ["gsi1sk"],
|
|
1772
|
+
template: "${gsi1sk}"
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
// src/data/dynamo/entities/control/roleassignment-entity.ts
|
|
1779
|
+
var import_electrodb8 = require("electrodb");
|
|
1780
|
+
var RoleAssignmentEntity = new import_electrodb8.Entity({
|
|
1781
|
+
model: {
|
|
1782
|
+
entity: "roleassignment",
|
|
1783
|
+
service: "control",
|
|
1784
|
+
version: "01"
|
|
1785
|
+
},
|
|
1786
|
+
attributes: {
|
|
1787
|
+
/** Sort key sentinel. Always "CURRENT". */
|
|
1788
|
+
sk: {
|
|
1789
|
+
type: "string",
|
|
1790
|
+
required: true,
|
|
1791
|
+
default: "CURRENT"
|
|
1792
|
+
},
|
|
1793
|
+
/** Tenant in which the role assignment applies (required). */
|
|
1794
|
+
tenantId: {
|
|
1795
|
+
type: "string",
|
|
1796
|
+
required: true
|
|
1797
|
+
},
|
|
1798
|
+
/** FHIR Resource.id; role assignment id. */
|
|
1799
|
+
id: {
|
|
1800
|
+
type: "string",
|
|
1801
|
+
required: true
|
|
1802
|
+
},
|
|
1803
|
+
/** Full RoleAssignment resource serialized as JSON string. */
|
|
1804
|
+
resource: {
|
|
1805
|
+
type: "string",
|
|
1806
|
+
required: true
|
|
1807
|
+
},
|
|
1808
|
+
/**
|
|
1809
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
1810
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
1811
|
+
*/
|
|
1812
|
+
summary: {
|
|
1813
|
+
type: "string",
|
|
1814
|
+
required: true
|
|
1815
|
+
},
|
|
1816
|
+
/** Version id (e.g. ULID). */
|
|
1817
|
+
vid: {
|
|
1818
|
+
type: "string",
|
|
1819
|
+
required: true
|
|
1820
|
+
},
|
|
1821
|
+
lastUpdated: {
|
|
1822
|
+
type: "string",
|
|
1823
|
+
required: true
|
|
1824
|
+
},
|
|
1825
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
1826
|
+
/**
|
|
1827
|
+
* Derived GSI1 sort key — discriminator-first
|
|
1828
|
+
* `<roleId>#<normalizedUserName>#<id>` per ADR-018 pattern #8 so a
|
|
1829
|
+
* GSI1 query partitioned on the tenant can `begins_with('<roleId>#')`
|
|
1830
|
+
* to enumerate every user assigned to a given role, sorted by user
|
|
1831
|
+
* name. Falls back to `<lastUpdated>#<id>` when either component is
|
|
1832
|
+
* missing.
|
|
1833
|
+
*/
|
|
1834
|
+
gsi1sk: roleAssignmentGsi1skAttribute,
|
|
1835
|
+
deleted: {
|
|
1836
|
+
type: "boolean",
|
|
1837
|
+
required: false
|
|
1838
|
+
},
|
|
1839
|
+
bundleId: {
|
|
1840
|
+
type: "string",
|
|
1841
|
+
required: false
|
|
1842
|
+
},
|
|
1843
|
+
msgId: {
|
|
1844
|
+
type: "string",
|
|
1845
|
+
required: false
|
|
1846
|
+
},
|
|
1847
|
+
/**
|
|
1848
|
+
* Denormalized display name of the linked Tenant, captured at row
|
|
1849
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1850
|
+
* adjacency-list user-projection SK (pattern #5 —
|
|
1851
|
+
* `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<id>`)
|
|
1852
|
+
* can be composed from a top-level field instead of digging into the
|
|
1853
|
+
* `resource` JSON. Optional on the schema so pre-TR-024 rows do not
|
|
1854
|
+
* break; the operations-layer multi-write helper (#1010) makes the
|
|
1855
|
+
* field load-bearing at write time per TR-024 rule 2 (write-time
|
|
1856
|
+
* source = canonical Tenant.displayName).
|
|
1857
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1858
|
+
*/
|
|
1859
|
+
denormalizedTenantName: {
|
|
1860
|
+
type: "string",
|
|
1861
|
+
required: false
|
|
1862
|
+
},
|
|
1863
|
+
/**
|
|
1864
|
+
* Denormalized display name of the linked User, captured at row
|
|
1865
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1866
|
+
* adjacency-list canonical-record GSI1SK (pattern #8 —
|
|
1867
|
+
* `<roleId>#<normalizedUserName>#<id>`) and workspace-projection SK
|
|
1868
|
+
* (pattern #9) can be composed from a top-level field. Optional on
|
|
1869
|
+
* the schema so pre-TR-024 rows do not break; the operations-layer
|
|
1870
|
+
* multi-write helper (#1010) makes the field load-bearing at write
|
|
1871
|
+
* time per TR-024 rule 2 (write-time source = canonical
|
|
1872
|
+
* User.displayName).
|
|
1873
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1874
|
+
*/
|
|
1875
|
+
denormalizedUserName: {
|
|
1876
|
+
type: "string",
|
|
1877
|
+
required: false
|
|
1878
|
+
},
|
|
1879
|
+
/**
|
|
1880
|
+
* Denormalized display name of the linked Role, captured at row
|
|
1881
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1882
|
+
* adjacency-list user-projection SK (pattern #5 —
|
|
1883
|
+
* `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#…`) can be composed from
|
|
1884
|
+
* a top-level field. Optional on the schema so pre-TR-024 rows do not
|
|
1885
|
+
* break; the operations-layer multi-write helper (#1010) makes the
|
|
1886
|
+
* field load-bearing at write time per TR-024 rule 2 (write-time
|
|
1887
|
+
* source = canonical Role.displayName).
|
|
1888
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1889
|
+
*/
|
|
1890
|
+
denormalizedRoleName: {
|
|
1891
|
+
type: "string",
|
|
1892
|
+
required: false
|
|
1893
|
+
}
|
|
1894
|
+
},
|
|
1895
|
+
indexes: {
|
|
1896
|
+
/** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
|
|
1897
|
+
record: {
|
|
1898
|
+
pk: {
|
|
1899
|
+
field: "PK",
|
|
1900
|
+
composite: ["tenantId", "id"],
|
|
1901
|
+
template: "TID#${tenantId}#ROLEASSIGNMENT#ID#${id}"
|
|
1902
|
+
},
|
|
1903
|
+
sk: {
|
|
1904
|
+
field: "SK",
|
|
1905
|
+
composite: ["sk"],
|
|
1906
|
+
template: "${sk}"
|
|
1907
|
+
}
|
|
1908
|
+
},
|
|
1909
|
+
/**
|
|
1910
|
+
* GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
|
|
1911
|
+
* four shards. Tenant-scoped only, so `WID#-` is a sentinel.
|
|
1912
|
+
* SK is derived via `roleAssignmentGsi1skAttribute` — composes the
|
|
1913
|
+
* discriminator-first `<roleId>#<normalizedUserName>#<id>` shape per
|
|
1914
|
+
* ADR-018 pattern #8 (users with a specific role in a tenant, sorted
|
|
1915
|
+
* by user name); falls back to `<lastUpdated>#<id>` when either
|
|
1916
|
+
* component is missing. `casing: "none"` preserves the normalized
|
|
1917
|
+
* label and ISO-8601 `T`/`Z`.
|
|
1918
|
+
*/
|
|
1919
|
+
gsi1: {
|
|
1920
|
+
index: "GSI1",
|
|
1921
|
+
pk: {
|
|
1922
|
+
field: "GSI1PK",
|
|
1923
|
+
composite: ["tenantId", "gsi1Shard"],
|
|
1924
|
+
template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
|
|
1925
|
+
},
|
|
1926
|
+
sk: {
|
|
1927
|
+
field: "GSI1SK",
|
|
1928
|
+
casing: "none",
|
|
1929
|
+
composite: ["gsi1sk"],
|
|
1930
|
+
template: "${gsi1sk}"
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1936
|
+
// src/data/dynamo/entities/control/roleassignment-user-projection-entity.ts
|
|
1937
|
+
var import_electrodb9 = require("electrodb");
|
|
1938
|
+
var RoleAssignmentUserProjectionEntity = new import_electrodb9.Entity({
|
|
1939
|
+
model: {
|
|
1940
|
+
entity: "roleAssignmentUserProjection",
|
|
1941
|
+
service: "control",
|
|
1942
|
+
version: "01"
|
|
1943
|
+
},
|
|
1944
|
+
attributes: {
|
|
1945
|
+
/**
|
|
1946
|
+
* User partition discriminator. Renders as `USER#ID#<userId>` on the
|
|
1947
|
+
* base-table PK. Always required — the projection has no meaning
|
|
1948
|
+
* outside a user partition.
|
|
1949
|
+
*/
|
|
1950
|
+
userId: {
|
|
1951
|
+
type: "string",
|
|
1952
|
+
required: true
|
|
1953
|
+
},
|
|
1954
|
+
/**
|
|
1955
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1956
|
+
* writer via `buildRoleAssignmentUserProjectionSk*` helpers. The
|
|
1957
|
+
* entity stores the value verbatim so the SK grammar (tenant-lane
|
|
1958
|
+
* vs workspace-lane) is owned by the operations layer, not
|
|
1959
|
+
* duplicated here.
|
|
1960
|
+
*/
|
|
1961
|
+
sk: {
|
|
1962
|
+
type: "string",
|
|
1963
|
+
required: true
|
|
1964
|
+
},
|
|
1965
|
+
/** Tenant in which the role assignment applies. Always required. */
|
|
1966
|
+
tenantId: {
|
|
1967
|
+
type: "string",
|
|
1968
|
+
required: true
|
|
1969
|
+
},
|
|
1970
|
+
/**
|
|
1971
|
+
* Workspace the role assignment scopes to. Present iff the
|
|
1972
|
+
* projection row is the workspace-level sub-lane; absent for
|
|
1973
|
+
* tenant-level sub-lane rows.
|
|
1974
|
+
*/
|
|
1975
|
+
workspaceId: {
|
|
1976
|
+
type: "string",
|
|
1977
|
+
required: false
|
|
1978
|
+
},
|
|
1979
|
+
/**
|
|
1980
|
+
* Role the assignment grants. Stored as a discriminating field so
|
|
1981
|
+
* `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#…')`
|
|
1982
|
+
* results carry the role id without a hop to the canonical row.
|
|
1983
|
+
*/
|
|
1984
|
+
roleId: {
|
|
1985
|
+
type: "string",
|
|
1986
|
+
required: true
|
|
1987
|
+
},
|
|
1988
|
+
/**
|
|
1989
|
+
* RoleAssignment canonical-record id. Stored as a discriminating
|
|
1990
|
+
* field so consumers can hydrate the canonical row via
|
|
1991
|
+
* `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
|
|
1992
|
+
* when the projection's `summary` is insufficient.
|
|
1993
|
+
*/
|
|
1994
|
+
roleAssignmentId: {
|
|
1995
|
+
type: "string",
|
|
1996
|
+
required: true
|
|
1997
|
+
},
|
|
1998
|
+
/**
|
|
1999
|
+
* Summary projection (key display fields as JSON string: id,
|
|
2000
|
+
* displayName, status) — mirrored from the canonical RoleAssignment
|
|
2001
|
+
* row so user-partition queries do not need a BatchGet hop.
|
|
2002
|
+
*/
|
|
2003
|
+
summary: {
|
|
2004
|
+
type: "string",
|
|
2005
|
+
required: true
|
|
2006
|
+
},
|
|
2007
|
+
/** Version id mirrored from the canonical RoleAssignment row. */
|
|
2008
|
+
vid: {
|
|
2009
|
+
type: "string",
|
|
2010
|
+
required: true
|
|
2011
|
+
},
|
|
2012
|
+
/** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
|
|
2013
|
+
lastUpdated: {
|
|
2014
|
+
type: "string",
|
|
2015
|
+
required: true
|
|
2016
|
+
},
|
|
2017
|
+
/**
|
|
2018
|
+
* Denormalized Tenant display name — mirrored from the canonical
|
|
2019
|
+
* RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
|
|
2020
|
+
* Optional on the schema because pre-TR-024 rows may not carry a
|
|
2021
|
+
* display name; the operations layer falls back gracefully when
|
|
2022
|
+
* missing.
|
|
2023
|
+
*/
|
|
2024
|
+
denormalizedTenantName: {
|
|
2025
|
+
type: "string",
|
|
2026
|
+
required: false
|
|
2027
|
+
},
|
|
2028
|
+
/**
|
|
2029
|
+
* Denormalized User display name — mirrored from the canonical
|
|
2030
|
+
* RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
|
|
2031
|
+
* Carried on the projection so consumers can render the user's
|
|
2032
|
+
* display name without a hop to the User record.
|
|
2033
|
+
*/
|
|
2034
|
+
denormalizedUserName: {
|
|
2035
|
+
type: "string",
|
|
2036
|
+
required: false
|
|
2037
|
+
},
|
|
2038
|
+
/**
|
|
2039
|
+
* Denormalized Role display name — required to compose the SK's
|
|
2040
|
+
* `<normalizedRoleName>` segment. Optional on the schema (pre-TR-024
|
|
2041
|
+
* rows fall back to a sentinel) but expected to be present at write
|
|
2042
|
+
* time per TR-024 rule 2 (write-time source =
|
|
2043
|
+
* canonical Role.displayName).
|
|
2044
|
+
*/
|
|
2045
|
+
denormalizedRoleName: {
|
|
2046
|
+
type: "string",
|
|
2047
|
+
required: false
|
|
2048
|
+
}
|
|
2049
|
+
},
|
|
2050
|
+
indexes: {
|
|
2051
|
+
/**
|
|
2052
|
+
* Base table: PK = USER#ID#<userId>, SK = operation-supplied. Both
|
|
2053
|
+
* sub-lanes (tenant-level and workspace-level) use this same index —
|
|
2054
|
+
* the SK string encodes the lane discriminator
|
|
2055
|
+
* (`ROLEASSIGNMENT#TENANT#…` vs `ROLEASSIGNMENT#WORKSPACE#…`) so a
|
|
2056
|
+
* single `Query(PK = USER#ID#<userId>, SK begins_with
|
|
2057
|
+
* 'ROLEASSIGNMENT#')` returns both lanes interleaved.
|
|
2058
|
+
*/
|
|
2059
|
+
record: {
|
|
1124
2060
|
pk: {
|
|
1125
|
-
field: "
|
|
1126
|
-
composite: ["
|
|
1127
|
-
template: "
|
|
2061
|
+
field: "PK",
|
|
2062
|
+
composite: ["userId"],
|
|
2063
|
+
template: "USER#ID#${userId}"
|
|
1128
2064
|
},
|
|
1129
2065
|
sk: {
|
|
1130
|
-
field: "
|
|
2066
|
+
field: "SK",
|
|
1131
2067
|
casing: "none",
|
|
1132
|
-
composite: ["
|
|
1133
|
-
template: "${
|
|
2068
|
+
composite: ["sk"],
|
|
2069
|
+
template: "${sk}"
|
|
1134
2070
|
}
|
|
1135
2071
|
}
|
|
1136
2072
|
}
|
|
1137
2073
|
});
|
|
1138
2074
|
|
|
1139
|
-
// src/data/dynamo/entities/control/roleassignment-entity.ts
|
|
1140
|
-
var
|
|
1141
|
-
var
|
|
2075
|
+
// src/data/dynamo/entities/control/roleassignment-workspace-projection-entity.ts
|
|
2076
|
+
var import_electrodb10 = require("electrodb");
|
|
2077
|
+
var RoleAssignmentWorkspaceProjectionEntity = new import_electrodb10.Entity({
|
|
1142
2078
|
model: {
|
|
1143
|
-
entity: "
|
|
2079
|
+
entity: "roleAssignmentWorkspaceProjection",
|
|
1144
2080
|
service: "control",
|
|
1145
2081
|
version: "01"
|
|
1146
2082
|
},
|
|
1147
2083
|
attributes: {
|
|
1148
|
-
/**
|
|
2084
|
+
/**
|
|
2085
|
+
* Tenant the workspace belongs to. Renders as the leading segment
|
|
2086
|
+
* of the base-table PK. Always required — the workspace partition
|
|
2087
|
+
* is tenant-scoped per ADR-011.
|
|
2088
|
+
*/
|
|
2089
|
+
tenantId: {
|
|
2090
|
+
type: "string",
|
|
2091
|
+
required: true
|
|
2092
|
+
},
|
|
2093
|
+
/**
|
|
2094
|
+
* Workspace partition discriminator. Renders as the trailing
|
|
2095
|
+
* segment of the base-table PK
|
|
2096
|
+
* (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
|
|
2097
|
+
* the projection has no meaning outside a workspace partition.
|
|
2098
|
+
*/
|
|
2099
|
+
workspaceId: {
|
|
2100
|
+
type: "string",
|
|
2101
|
+
required: true
|
|
2102
|
+
},
|
|
2103
|
+
/**
|
|
2104
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
2105
|
+
* writer via `buildRoleAssignmentWorkspaceProjectionSk`. The entity
|
|
2106
|
+
* stores the value verbatim so the SK grammar (pattern #9) is
|
|
2107
|
+
* owned by the operations layer, not duplicated here.
|
|
2108
|
+
*/
|
|
1149
2109
|
sk: {
|
|
1150
2110
|
type: "string",
|
|
1151
|
-
required: true
|
|
1152
|
-
default: "CURRENT"
|
|
2111
|
+
required: true
|
|
1153
2112
|
},
|
|
1154
|
-
/**
|
|
1155
|
-
|
|
2113
|
+
/**
|
|
2114
|
+
* User the role assignment grants the role to. Stored as a
|
|
2115
|
+
* discriminating field so consumers can hydrate the canonical User
|
|
2116
|
+
* row via `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
|
|
2117
|
+
* projection's `summary` is insufficient.
|
|
2118
|
+
*/
|
|
2119
|
+
userId: {
|
|
1156
2120
|
type: "string",
|
|
1157
2121
|
required: true
|
|
1158
2122
|
},
|
|
1159
|
-
/**
|
|
1160
|
-
|
|
2123
|
+
/**
|
|
2124
|
+
* Role the assignment grants. Stored as a discriminating field —
|
|
2125
|
+
* also rendered into the SK as the discriminator-first segment so
|
|
2126
|
+
* `begins_with('ROLEASSIGNMENT#<roleId>#')` filters one role.
|
|
2127
|
+
*/
|
|
2128
|
+
roleId: {
|
|
1161
2129
|
type: "string",
|
|
1162
2130
|
required: true
|
|
1163
2131
|
},
|
|
1164
|
-
/**
|
|
1165
|
-
|
|
2132
|
+
/**
|
|
2133
|
+
* RoleAssignment canonical-record id. Stored as a discriminating
|
|
2134
|
+
* field so consumers can hydrate the canonical row via
|
|
2135
|
+
* `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
|
|
2136
|
+
* when the projection's `summary` is insufficient.
|
|
2137
|
+
*/
|
|
2138
|
+
roleAssignmentId: {
|
|
1166
2139
|
type: "string",
|
|
1167
2140
|
required: true
|
|
1168
2141
|
},
|
|
1169
2142
|
/**
|
|
1170
|
-
* Summary projection (key display fields as JSON string: id,
|
|
1171
|
-
*
|
|
2143
|
+
* Summary projection (key display fields as JSON string: id,
|
|
2144
|
+
* displayName, status) — mirrored from the canonical RoleAssignment
|
|
2145
|
+
* row so workspace-partition queries do not need a BatchGet hop.
|
|
1172
2146
|
*/
|
|
1173
2147
|
summary: {
|
|
1174
2148
|
type: "string",
|
|
1175
2149
|
required: true
|
|
1176
2150
|
},
|
|
1177
|
-
/** Version id
|
|
2151
|
+
/** Version id mirrored from the canonical RoleAssignment row. */
|
|
1178
2152
|
vid: {
|
|
1179
2153
|
type: "string",
|
|
1180
2154
|
required: true
|
|
1181
2155
|
},
|
|
2156
|
+
/** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
|
|
1182
2157
|
lastUpdated: {
|
|
1183
2158
|
type: "string",
|
|
1184
2159
|
required: true
|
|
1185
2160
|
},
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
2161
|
+
/**
|
|
2162
|
+
* Denormalized User display name — required to compose the
|
|
2163
|
+
* pattern-#9 SK (`ROLEASSIGNMENT#<roleId>#<normalizedUserName>#…`).
|
|
2164
|
+
* Optional on the schema because pre-TR-024 rows may not carry a
|
|
2165
|
+
* display name; the operations layer falls back to a sentinel when
|
|
2166
|
+
* missing so the SK still has a valid shape. The TR-023 rename-
|
|
2167
|
+
* cascade pipeline rewrites the SK on a User rename.
|
|
2168
|
+
*/
|
|
2169
|
+
denormalizedUserName: {
|
|
1194
2170
|
type: "string",
|
|
1195
2171
|
required: false
|
|
1196
2172
|
},
|
|
1197
|
-
|
|
2173
|
+
/**
|
|
2174
|
+
* Denormalized Role display name — mirrored from the canonical
|
|
2175
|
+
* RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
|
|
2176
|
+
* Carried on the projection so consumers can render the role's
|
|
2177
|
+
* display name without a hop to the Role record. Not part of the
|
|
2178
|
+
* SK (pattern #9 sorts on `<normalizedUserName>`, not role name) —
|
|
2179
|
+
* a Role rename does NOT rewrite this SK.
|
|
2180
|
+
*/
|
|
2181
|
+
denormalizedRoleName: {
|
|
1198
2182
|
type: "string",
|
|
1199
2183
|
required: false
|
|
1200
2184
|
}
|
|
1201
2185
|
},
|
|
1202
2186
|
indexes: {
|
|
1203
|
-
/**
|
|
2187
|
+
/**
|
|
2188
|
+
* Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
|
|
2189
|
+
* SK = operation-supplied. Pattern #9 uses this index — the SK
|
|
2190
|
+
* encodes the entity-type prefix and discriminator-first roleId
|
|
2191
|
+
* (`ROLEASSIGNMENT#<roleId>#…`) so
|
|
2192
|
+
* `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'ROLEASSIGNMENT#<roleId>#')`
|
|
2193
|
+
* returns every user-assignment for that role in the workspace, sorted
|
|
2194
|
+
* by normalized user name.
|
|
2195
|
+
*/
|
|
1204
2196
|
record: {
|
|
1205
2197
|
pk: {
|
|
1206
2198
|
field: "PK",
|
|
1207
|
-
composite: ["tenantId", "
|
|
1208
|
-
template: "TID#${tenantId}#
|
|
2199
|
+
composite: ["tenantId", "workspaceId"],
|
|
2200
|
+
template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
|
|
1209
2201
|
},
|
|
1210
2202
|
sk: {
|
|
1211
2203
|
field: "SK",
|
|
2204
|
+
casing: "none",
|
|
1212
2205
|
composite: ["sk"],
|
|
1213
2206
|
template: "${sk}"
|
|
1214
2207
|
}
|
|
1215
|
-
},
|
|
1216
|
-
/**
|
|
1217
|
-
* GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
|
|
1218
|
-
* four shards. Tenant-scoped only, so `WID#-` is a sentinel.
|
|
1219
|
-
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
1220
|
-
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
1221
|
-
* normalized label and ISO-8601 `T`/`Z`.
|
|
1222
|
-
*/
|
|
1223
|
-
gsi1: {
|
|
1224
|
-
index: "GSI1",
|
|
1225
|
-
pk: {
|
|
1226
|
-
field: "GSI1PK",
|
|
1227
|
-
composite: ["tenantId", "gsi1Shard"],
|
|
1228
|
-
template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
|
|
1229
|
-
},
|
|
1230
|
-
sk: {
|
|
1231
|
-
field: "GSI1SK",
|
|
1232
|
-
casing: "none",
|
|
1233
|
-
composite: ["gsi1sk"],
|
|
1234
|
-
template: "${gsi1sk}"
|
|
1235
|
-
}
|
|
1236
2208
|
}
|
|
1237
2209
|
}
|
|
1238
2210
|
});
|
|
1239
2211
|
|
|
1240
2212
|
// src/data/dynamo/entities/control/tenant-entity.ts
|
|
1241
|
-
var
|
|
1242
|
-
var TenantEntity = new
|
|
2213
|
+
var import_electrodb11 = require("electrodb");
|
|
2214
|
+
var TenantEntity = new import_electrodb11.Entity({
|
|
1243
2215
|
model: {
|
|
1244
2216
|
entity: "tenant",
|
|
1245
2217
|
service: "control",
|
|
@@ -1339,8 +2311,8 @@ var TenantEntity = new import_electrodb5.Entity({
|
|
|
1339
2311
|
});
|
|
1340
2312
|
|
|
1341
2313
|
// src/data/dynamo/entities/control/user-entity.ts
|
|
1342
|
-
var
|
|
1343
|
-
var UserEntity = new
|
|
2314
|
+
var import_electrodb12 = require("electrodb");
|
|
2315
|
+
var UserEntity = new import_electrodb12.Entity({
|
|
1344
2316
|
model: {
|
|
1345
2317
|
entity: "user",
|
|
1346
2318
|
service: "control",
|
|
@@ -1395,6 +2367,28 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
1395
2367
|
type: "boolean",
|
|
1396
2368
|
required: false
|
|
1397
2369
|
},
|
|
2370
|
+
/**
|
|
2371
|
+
* TR-022 / ADR-018 lifecycle state for the cascade pipeline.
|
|
2372
|
+
*
|
|
2373
|
+
* - `active` (or undefined) — normal, readable state.
|
|
2374
|
+
* - `deleting` — intermediate state set synchronously by the
|
|
2375
|
+
* hard-delete API entry point. The owning-delete cascade state
|
|
2376
|
+
* machine fans out from this transition (DynamoDB stream →
|
|
2377
|
+
* `control-plane.owning-delete.v1` → Step Functions). Readers MUST
|
|
2378
|
+
* short-circuit on `deleting` so partial cascades stay invisible.
|
|
2379
|
+
* - `deleted-failed` — terminal failure state set by the cascade
|
|
2380
|
+
* finalize Lambda when the cascade run fails irrecoverably.
|
|
2381
|
+
* Operators recover by re-running the cascade or by direct
|
|
2382
|
+
* intervention.
|
|
2383
|
+
*
|
|
2384
|
+
* The cascade finalize step deletes the canonical record conditional
|
|
2385
|
+
* on `lifecycleState = "deleting"`; on replay the conditional check
|
|
2386
|
+
* fails and the finalize step treats that as a no-op success.
|
|
2387
|
+
*/
|
|
2388
|
+
lifecycleState: {
|
|
2389
|
+
type: ["active", "deleting", "deleted-failed"],
|
|
2390
|
+
required: false
|
|
2391
|
+
},
|
|
1398
2392
|
bundleId: {
|
|
1399
2393
|
type: "string",
|
|
1400
2394
|
required: false
|
|
@@ -1464,8 +2458,8 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
1464
2458
|
});
|
|
1465
2459
|
|
|
1466
2460
|
// src/data/dynamo/entities/control/workspace-entity.ts
|
|
1467
|
-
var
|
|
1468
|
-
var WorkspaceEntity = new
|
|
2461
|
+
var import_electrodb13 = require("electrodb");
|
|
2462
|
+
var WorkspaceEntity = new import_electrodb13.Entity({
|
|
1469
2463
|
model: {
|
|
1470
2464
|
entity: "workspace",
|
|
1471
2465
|
service: "control",
|
|
@@ -1517,6 +2511,28 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
1517
2511
|
type: "boolean",
|
|
1518
2512
|
required: false
|
|
1519
2513
|
},
|
|
2514
|
+
/**
|
|
2515
|
+
* TR-022 / ADR-018 lifecycle state for the cascade pipeline.
|
|
2516
|
+
*
|
|
2517
|
+
* - `active` (or undefined) — normal, readable state.
|
|
2518
|
+
* - `deleting` — intermediate state set synchronously by the
|
|
2519
|
+
* hard-delete API entry point. The owning-delete cascade state
|
|
2520
|
+
* machine fans out from this transition (DynamoDB stream →
|
|
2521
|
+
* `control-plane.owning-delete.v1` → Step Functions). Readers MUST
|
|
2522
|
+
* short-circuit on `deleting` so partial cascades stay invisible.
|
|
2523
|
+
* - `deleted-failed` — terminal failure state set by the cascade
|
|
2524
|
+
* finalize Lambda when the cascade run fails irrecoverably.
|
|
2525
|
+
* Operators recover by re-running the cascade or by direct
|
|
2526
|
+
* intervention.
|
|
2527
|
+
*
|
|
2528
|
+
* The cascade finalize step deletes the canonical record conditional
|
|
2529
|
+
* on `lifecycleState = "deleting"`; on replay the conditional check
|
|
2530
|
+
* fails and the finalize step treats that as a no-op success.
|
|
2531
|
+
*/
|
|
2532
|
+
lifecycleState: {
|
|
2533
|
+
type: ["active", "deleting", "deleted-failed"],
|
|
2534
|
+
required: false
|
|
2535
|
+
},
|
|
1520
2536
|
bundleId: {
|
|
1521
2537
|
type: "string",
|
|
1522
2538
|
required: false
|
|
@@ -1567,28 +2583,36 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
1567
2583
|
// src/data/dynamo/dynamo-control-service.ts
|
|
1568
2584
|
var controlPlaneEntities = {
|
|
1569
2585
|
configuration: ConfigurationEntity,
|
|
2586
|
+
configurationUserProjection: ConfigurationUserProjectionEntity,
|
|
2587
|
+
configurationWorkspaceProjection: ConfigurationWorkspaceProjectionEntity,
|
|
1570
2588
|
membership: MembershipEntity,
|
|
2589
|
+
membershipUserProjection: MembershipUserProjectionEntity,
|
|
2590
|
+
membershipWorkspaceProjection: MembershipWorkspaceProjectionEntity,
|
|
1571
2591
|
role: RoleEntity,
|
|
1572
2592
|
roleAssignment: RoleAssignmentEntity,
|
|
2593
|
+
roleAssignmentUserProjection: RoleAssignmentUserProjectionEntity,
|
|
2594
|
+
roleAssignmentWorkspaceProjection: RoleAssignmentWorkspaceProjectionEntity,
|
|
1573
2595
|
tenant: TenantEntity,
|
|
1574
2596
|
user: UserEntity,
|
|
1575
2597
|
workspace: WorkspaceEntity
|
|
1576
2598
|
};
|
|
1577
|
-
var controlPlaneService = new
|
|
2599
|
+
var controlPlaneService = new import_electrodb14.Service(controlPlaneEntities, {
|
|
1578
2600
|
table: defaultTableName,
|
|
1579
2601
|
client: dynamoClient
|
|
1580
2602
|
});
|
|
1581
2603
|
var DynamoControlService = {
|
|
1582
|
-
entities: controlPlaneService.entities
|
|
2604
|
+
entities: controlPlaneService.entities,
|
|
2605
|
+
transaction: controlPlaneService.transaction
|
|
1583
2606
|
};
|
|
1584
2607
|
function getDynamoControlService(tableName) {
|
|
1585
2608
|
const resolved = tableName ?? defaultTableName;
|
|
1586
|
-
const service = new
|
|
2609
|
+
const service = new import_electrodb14.Service(controlPlaneEntities, {
|
|
1587
2610
|
table: resolved,
|
|
1588
2611
|
client: dynamoClient
|
|
1589
2612
|
});
|
|
1590
2613
|
return {
|
|
1591
|
-
entities: service.entities
|
|
2614
|
+
entities: service.entities,
|
|
2615
|
+
transaction: service.transaction
|
|
1592
2616
|
};
|
|
1593
2617
|
}
|
|
1594
2618
|
|
|
@@ -1612,9 +2636,222 @@ var ValidationError = class extends DomainError {
|
|
|
1612
2636
|
super(message, "VALIDATION", options);
|
|
1613
2637
|
}
|
|
1614
2638
|
};
|
|
2639
|
+
var ConflictError = class extends DomainError {
|
|
2640
|
+
constructor(message, options) {
|
|
2641
|
+
super(message, "CONFLICT", options);
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
1615
2644
|
|
|
1616
2645
|
// src/data/operations/control/membership/membership-create-operation.ts
|
|
2646
|
+
var import_types5 = require("@openhi/types");
|
|
2647
|
+
|
|
2648
|
+
// src/data/operations/control/membership/membership-user-projection.ts
|
|
1617
2649
|
var import_types3 = require("@openhi/types");
|
|
2650
|
+
var MISSING_NAME_SENTINEL = "-";
|
|
2651
|
+
function buildMembershipUserProjectionSkTenantLane(params) {
|
|
2652
|
+
const normalizedTenantName = typeof params.denormalizedTenantName === "string" && params.denormalizedTenantName.length > 0 ? (0, import_types3.normalizeLabel)(params.denormalizedTenantName) : MISSING_NAME_SENTINEL;
|
|
2653
|
+
return `MEMBERSHIP#TENANT#${normalizedTenantName}#TID#${params.tenantId}#${params.membershipId}`;
|
|
2654
|
+
}
|
|
2655
|
+
function buildMembershipUserProjectionSkWorkspaceLane(params) {
|
|
2656
|
+
const normalizedWorkspaceName = typeof params.denormalizedWorkspaceName === "string" && params.denormalizedWorkspaceName.length > 0 ? (0, import_types3.normalizeLabel)(params.denormalizedWorkspaceName) : MISSING_NAME_SENTINEL;
|
|
2657
|
+
return `MEMBERSHIP#WORKSPACE#TID#${params.tenantId}#${normalizedWorkspaceName}#WID#${params.workspaceId}#${params.membershipId}`;
|
|
2658
|
+
}
|
|
2659
|
+
function buildMembershipUserProjectionItem(input) {
|
|
2660
|
+
if (!input.userId || input.userId.length === 0) {
|
|
2661
|
+
return void 0;
|
|
2662
|
+
}
|
|
2663
|
+
const hasWorkspace = typeof input.workspaceId === "string" && input.workspaceId.length > 0;
|
|
2664
|
+
const sk = hasWorkspace ? buildMembershipUserProjectionSkWorkspaceLane({
|
|
2665
|
+
tenantId: input.tenantId,
|
|
2666
|
+
workspaceId: input.workspaceId,
|
|
2667
|
+
membershipId: input.membershipId,
|
|
2668
|
+
denormalizedWorkspaceName: input.denormalizedWorkspaceName
|
|
2669
|
+
}) : buildMembershipUserProjectionSkTenantLane({
|
|
2670
|
+
tenantId: input.tenantId,
|
|
2671
|
+
membershipId: input.membershipId,
|
|
2672
|
+
denormalizedTenantName: input.denormalizedTenantName
|
|
2673
|
+
});
|
|
2674
|
+
return {
|
|
2675
|
+
userId: input.userId,
|
|
2676
|
+
sk,
|
|
2677
|
+
tenantId: input.tenantId,
|
|
2678
|
+
workspaceId: hasWorkspace ? input.workspaceId : void 0,
|
|
2679
|
+
membershipId: input.membershipId,
|
|
2680
|
+
summary: input.summary,
|
|
2681
|
+
vid: input.vid,
|
|
2682
|
+
lastUpdated: input.lastUpdated,
|
|
2683
|
+
denormalizedTenantName: input.denormalizedTenantName,
|
|
2684
|
+
denormalizedUserName: input.denormalizedUserName,
|
|
2685
|
+
denormalizedWorkspaceName: hasWorkspace ? input.denormalizedWorkspaceName : void 0
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
function extractReferenceSlug(resource, fieldName) {
|
|
2689
|
+
const field = resource[fieldName];
|
|
2690
|
+
if (!field || typeof field !== "object") {
|
|
2691
|
+
return void 0;
|
|
2692
|
+
}
|
|
2693
|
+
const reference = field.reference;
|
|
2694
|
+
if (typeof reference !== "string" || reference.length === 0) {
|
|
2695
|
+
return void 0;
|
|
2696
|
+
}
|
|
2697
|
+
const slash = reference.lastIndexOf("/");
|
|
2698
|
+
const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
|
|
2699
|
+
return tail.length > 0 ? tail : void 0;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
// src/data/operations/control/membership/membership-workspace-projection.ts
|
|
2703
|
+
var import_types4 = require("@openhi/types");
|
|
2704
|
+
var MISSING_NAME_SENTINEL2 = "-";
|
|
2705
|
+
function buildMembershipWorkspaceProjectionSk(params) {
|
|
2706
|
+
const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types4.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL2;
|
|
2707
|
+
return `MEMBERSHIP#${normalizedUserName}#USER#${params.userId}#${params.membershipId}`;
|
|
2708
|
+
}
|
|
2709
|
+
function buildMembershipWorkspaceProjectionItem(input) {
|
|
2710
|
+
if (!input.workspaceId || input.workspaceId.length === 0) {
|
|
2711
|
+
return void 0;
|
|
2712
|
+
}
|
|
2713
|
+
if (!input.userId || input.userId.length === 0) {
|
|
2714
|
+
return void 0;
|
|
2715
|
+
}
|
|
2716
|
+
const sk = buildMembershipWorkspaceProjectionSk({
|
|
2717
|
+
userId: input.userId,
|
|
2718
|
+
membershipId: input.membershipId,
|
|
2719
|
+
denormalizedUserName: input.denormalizedUserName
|
|
2720
|
+
});
|
|
2721
|
+
return {
|
|
2722
|
+
tenantId: input.tenantId,
|
|
2723
|
+
workspaceId: input.workspaceId,
|
|
2724
|
+
sk,
|
|
2725
|
+
userId: input.userId,
|
|
2726
|
+
membershipId: input.membershipId,
|
|
2727
|
+
summary: input.summary,
|
|
2728
|
+
vid: input.vid,
|
|
2729
|
+
lastUpdated: input.lastUpdated,
|
|
2730
|
+
denormalizedUserName: input.denormalizedUserName
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
// src/data/operations/control/denormalized-display-names.ts
|
|
2735
|
+
function extractDenormalizedReferenceDisplay(resource, fieldName) {
|
|
2736
|
+
const field = resource[fieldName];
|
|
2737
|
+
if (!field || typeof field !== "object") {
|
|
2738
|
+
return void 0;
|
|
2739
|
+
}
|
|
2740
|
+
const display = field.display;
|
|
2741
|
+
if (typeof display !== "string") {
|
|
2742
|
+
return void 0;
|
|
2743
|
+
}
|
|
2744
|
+
const trimmed = display.trim();
|
|
2745
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// src/data/operations/control/multi-write-operation.ts
|
|
2749
|
+
var TRANSACT_WRITE_ITEM_LIMIT = 100;
|
|
2750
|
+
async function executeMultiWrite(params) {
|
|
2751
|
+
const { service, triples, token } = params;
|
|
2752
|
+
if (triples.length === 0) {
|
|
2753
|
+
throw new ValidationError(
|
|
2754
|
+
"executeMultiWrite called with zero triples; at least one triple is required"
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
if (triples.length > TRANSACT_WRITE_ITEM_LIMIT) {
|
|
2758
|
+
throw new ValidationError(
|
|
2759
|
+
`executeMultiWrite received ${triples.length} triples; DynamoDB TransactWriteItems is limited to ${TRANSACT_WRITE_ITEM_LIMIT} items per call`,
|
|
2760
|
+
{
|
|
2761
|
+
details: {
|
|
2762
|
+
itemsRequested: triples.length,
|
|
2763
|
+
limit: TRANSACT_WRITE_ITEM_LIMIT
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2767
|
+
}
|
|
2768
|
+
for (const [index, triple] of triples.entries()) {
|
|
2769
|
+
if (!triple || typeof triple !== "object") {
|
|
2770
|
+
throw new ValidationError(
|
|
2771
|
+
`executeMultiWrite triple at index ${index} is not an object`
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
if (typeof triple.entity !== "string" || triple.entity.length === 0) {
|
|
2775
|
+
throw new ValidationError(
|
|
2776
|
+
`executeMultiWrite triple at index ${index} is missing a non-empty 'entity' key`
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
if (!isSupportedAction(triple.action)) {
|
|
2780
|
+
throw new ValidationError(
|
|
2781
|
+
`executeMultiWrite triple at index ${index} has unsupported action '${String(
|
|
2782
|
+
triple.action
|
|
2783
|
+
)}'; supported: 'put' | 'create' | 'delete'`
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
if (!triple.item || typeof triple.item !== "object") {
|
|
2787
|
+
throw new ValidationError(
|
|
2788
|
+
`executeMultiWrite triple at index ${index} is missing an 'item' payload`
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
let result;
|
|
2793
|
+
try {
|
|
2794
|
+
result = await service.transaction.write(
|
|
2795
|
+
(entities) => triples.map((triple, index) => {
|
|
2796
|
+
const transactEntity = entities[triple.entity];
|
|
2797
|
+
if (transactEntity === void 0) {
|
|
2798
|
+
throw new ValidationError(
|
|
2799
|
+
`executeMultiWrite triple at index ${index} references unknown entity '${triple.entity}'; ensure the service exposes it`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
switch (triple.action) {
|
|
2803
|
+
case "put":
|
|
2804
|
+
return transactEntity.put(triple.item).commit();
|
|
2805
|
+
case "create":
|
|
2806
|
+
return transactEntity.create(triple.item).commit();
|
|
2807
|
+
case "delete":
|
|
2808
|
+
return transactEntity.delete(triple.item).commit();
|
|
2809
|
+
default:
|
|
2810
|
+
throw new ValidationError(
|
|
2811
|
+
`executeMultiWrite triple at index ${index} has unsupported action '${String(
|
|
2812
|
+
triple.action
|
|
2813
|
+
)}'`
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
})
|
|
2817
|
+
).go(token === void 0 ? void 0 : { token });
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
if (err instanceof DomainError) {
|
|
2820
|
+
throw err;
|
|
2821
|
+
}
|
|
2822
|
+
throw new ConflictError(buildCancellationMessage(err), {
|
|
2823
|
+
cause: err,
|
|
2824
|
+
details: extractCancellationReasons(err)
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
if (result.canceled) {
|
|
2828
|
+
throw new ConflictError(
|
|
2829
|
+
"TransactWriteItems was canceled by DynamoDB (check CancellationReasons on the cause for details)",
|
|
2830
|
+
{ details: { canceled: true, data: result.data } }
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
return { itemsWritten: triples.length, canceled: false };
|
|
2834
|
+
}
|
|
2835
|
+
function isSupportedAction(value) {
|
|
2836
|
+
return value === "put" || value === "create" || value === "delete";
|
|
2837
|
+
}
|
|
2838
|
+
function buildCancellationMessage(err) {
|
|
2839
|
+
if (err instanceof Error && err.message) {
|
|
2840
|
+
return `TransactWriteItems failed: ${err.message}`;
|
|
2841
|
+
}
|
|
2842
|
+
return "TransactWriteItems failed (no error message available)";
|
|
2843
|
+
}
|
|
2844
|
+
function extractCancellationReasons(err) {
|
|
2845
|
+
if (err && typeof err === "object") {
|
|
2846
|
+
const cancellationReasons = err.CancellationReasons;
|
|
2847
|
+
if (cancellationReasons !== void 0) {
|
|
2848
|
+
return { CancellationReasons: cancellationReasons };
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
return void 0;
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
// src/data/operations/control/membership/membership-create-operation.ts
|
|
1618
2855
|
async function createMembershipOperation(params) {
|
|
1619
2856
|
const { context, body, tableName } = params;
|
|
1620
2857
|
const service = getDynamoControlService(tableName);
|
|
@@ -1625,26 +2862,86 @@ async function createMembershipOperation(params) {
|
|
|
1625
2862
|
const resource = { resourceType: "Membership", id, ...parsedResource };
|
|
1626
2863
|
let linkedDataIdentityRef;
|
|
1627
2864
|
try {
|
|
1628
|
-
const ext = (0,
|
|
2865
|
+
const ext = (0, import_types5.assertLinkedDataIdentityCardinality)(
|
|
1629
2866
|
resource
|
|
1630
2867
|
);
|
|
1631
2868
|
linkedDataIdentityRef = ext?.valueReference?.reference;
|
|
1632
2869
|
} catch (e) {
|
|
1633
|
-
if (e instanceof
|
|
2870
|
+
if (e instanceof import_types5.LinkedDataIdentityCardinalityError) {
|
|
1634
2871
|
throw new ValidationError(e.message, { cause: e });
|
|
1635
2872
|
}
|
|
1636
2873
|
throw e;
|
|
1637
2874
|
}
|
|
1638
|
-
const
|
|
1639
|
-
|
|
2875
|
+
const resourceRecord = resource;
|
|
2876
|
+
const denormalizedTenantName = extractDenormalizedReferenceDisplay(
|
|
2877
|
+
resourceRecord,
|
|
2878
|
+
"tenant"
|
|
2879
|
+
);
|
|
2880
|
+
const denormalizedUserName = extractDenormalizedReferenceDisplay(
|
|
2881
|
+
resourceRecord,
|
|
2882
|
+
"user"
|
|
2883
|
+
);
|
|
2884
|
+
const denormalizedWorkspaceName = extractDenormalizedReferenceDisplay(
|
|
2885
|
+
resourceRecord,
|
|
2886
|
+
"workspace"
|
|
2887
|
+
);
|
|
2888
|
+
const summary = JSON.stringify((0, import_types5.extractSummary)(resource));
|
|
2889
|
+
const userIdFromResource = extractReferenceSlug(resourceRecord, "user");
|
|
2890
|
+
const workspaceIdFromResource = extractReferenceSlug(
|
|
2891
|
+
resourceRecord,
|
|
2892
|
+
"workspace"
|
|
2893
|
+
);
|
|
2894
|
+
const userProjectionItem = userIdFromResource !== void 0 ? buildMembershipUserProjectionItem({
|
|
2895
|
+
tenantId: context.tenantId,
|
|
2896
|
+
userId: userIdFromResource,
|
|
2897
|
+
workspaceId: workspaceIdFromResource,
|
|
2898
|
+
membershipId: id,
|
|
2899
|
+
summary,
|
|
2900
|
+
vid,
|
|
2901
|
+
lastUpdated,
|
|
2902
|
+
denormalizedTenantName,
|
|
2903
|
+
denormalizedUserName,
|
|
2904
|
+
denormalizedWorkspaceName
|
|
2905
|
+
}) : void 0;
|
|
2906
|
+
const workspaceProjectionItem = userIdFromResource !== void 0 && workspaceIdFromResource !== void 0 ? buildMembershipWorkspaceProjectionItem({
|
|
2907
|
+
tenantId: context.tenantId,
|
|
2908
|
+
workspaceId: workspaceIdFromResource,
|
|
2909
|
+
userId: userIdFromResource,
|
|
2910
|
+
membershipId: id,
|
|
2911
|
+
summary,
|
|
2912
|
+
vid,
|
|
2913
|
+
lastUpdated,
|
|
2914
|
+
denormalizedUserName
|
|
2915
|
+
}) : void 0;
|
|
2916
|
+
const canonicalItem = {
|
|
1640
2917
|
tenantId: context.tenantId,
|
|
1641
2918
|
id,
|
|
1642
2919
|
resource: JSON.stringify(resource),
|
|
1643
2920
|
summary,
|
|
1644
2921
|
vid,
|
|
1645
2922
|
lastUpdated,
|
|
1646
|
-
linkedDataIdentityRef
|
|
1647
|
-
|
|
2923
|
+
linkedDataIdentityRef,
|
|
2924
|
+
denormalizedTenantName,
|
|
2925
|
+
denormalizedUserName
|
|
2926
|
+
};
|
|
2927
|
+
const triples = [
|
|
2928
|
+
{ entity: "membership", action: "put", item: canonicalItem }
|
|
2929
|
+
];
|
|
2930
|
+
if (userProjectionItem) {
|
|
2931
|
+
triples.push({
|
|
2932
|
+
entity: "membershipUserProjection",
|
|
2933
|
+
action: "put",
|
|
2934
|
+
item: userProjectionItem
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
if (workspaceProjectionItem) {
|
|
2938
|
+
triples.push({
|
|
2939
|
+
entity: "membershipWorkspaceProjection",
|
|
2940
|
+
action: "put",
|
|
2941
|
+
item: workspaceProjectionItem
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
await executeMultiWrite({ service, triples });
|
|
1648
2945
|
return {
|
|
1649
2946
|
id,
|
|
1650
2947
|
resource,
|
|
@@ -1669,7 +2966,107 @@ async function getRoleByIdOperation(params) {
|
|
|
1669
2966
|
}
|
|
1670
2967
|
|
|
1671
2968
|
// src/data/operations/control/roleassignment/roleassignment-create-operation.ts
|
|
1672
|
-
var
|
|
2969
|
+
var import_types8 = require("@openhi/types");
|
|
2970
|
+
|
|
2971
|
+
// src/data/operations/control/roleassignment/roleassignment-user-projection.ts
|
|
2972
|
+
var import_types6 = require("@openhi/types");
|
|
2973
|
+
var MISSING_NAME_SENTINEL3 = "-";
|
|
2974
|
+
function buildRoleAssignmentUserProjectionSkTenantLane(params) {
|
|
2975
|
+
const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types6.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
|
|
2976
|
+
return `ROLEASSIGNMENT#TENANT#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#${params.roleAssignmentId}`;
|
|
2977
|
+
}
|
|
2978
|
+
function buildRoleAssignmentUserProjectionSkWorkspaceLane(params) {
|
|
2979
|
+
const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types6.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
|
|
2980
|
+
return `ROLEASSIGNMENT#WORKSPACE#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#WID#${params.workspaceId}#${params.roleAssignmentId}`;
|
|
2981
|
+
}
|
|
2982
|
+
function buildRoleAssignmentUserProjectionItem(input) {
|
|
2983
|
+
if (!input.userId || input.userId.length === 0) {
|
|
2984
|
+
return void 0;
|
|
2985
|
+
}
|
|
2986
|
+
if (!input.roleId || input.roleId.length === 0) {
|
|
2987
|
+
return void 0;
|
|
2988
|
+
}
|
|
2989
|
+
const hasWorkspace = typeof input.workspaceId === "string" && input.workspaceId.length > 0;
|
|
2990
|
+
const sk = hasWorkspace ? buildRoleAssignmentUserProjectionSkWorkspaceLane({
|
|
2991
|
+
tenantId: input.tenantId,
|
|
2992
|
+
workspaceId: input.workspaceId,
|
|
2993
|
+
roleId: input.roleId,
|
|
2994
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
2995
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
2996
|
+
}) : buildRoleAssignmentUserProjectionSkTenantLane({
|
|
2997
|
+
tenantId: input.tenantId,
|
|
2998
|
+
roleId: input.roleId,
|
|
2999
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
3000
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
3001
|
+
});
|
|
3002
|
+
return {
|
|
3003
|
+
userId: input.userId,
|
|
3004
|
+
sk,
|
|
3005
|
+
tenantId: input.tenantId,
|
|
3006
|
+
workspaceId: hasWorkspace ? input.workspaceId : void 0,
|
|
3007
|
+
roleId: input.roleId,
|
|
3008
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
3009
|
+
summary: input.summary,
|
|
3010
|
+
vid: input.vid,
|
|
3011
|
+
lastUpdated: input.lastUpdated,
|
|
3012
|
+
denormalizedTenantName: input.denormalizedTenantName,
|
|
3013
|
+
denormalizedUserName: input.denormalizedUserName,
|
|
3014
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
function extractReferenceSlug2(resource, fieldName) {
|
|
3018
|
+
const field = resource[fieldName];
|
|
3019
|
+
if (!field || typeof field !== "object") {
|
|
3020
|
+
return void 0;
|
|
3021
|
+
}
|
|
3022
|
+
const reference = field.reference;
|
|
3023
|
+
if (typeof reference !== "string" || reference.length === 0) {
|
|
3024
|
+
return void 0;
|
|
3025
|
+
}
|
|
3026
|
+
const slash = reference.lastIndexOf("/");
|
|
3027
|
+
const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
|
|
3028
|
+
return tail.length > 0 ? tail : void 0;
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// src/data/operations/control/roleassignment/roleassignment-workspace-projection.ts
|
|
3032
|
+
var import_types7 = require("@openhi/types");
|
|
3033
|
+
var MISSING_NAME_SENTINEL4 = "-";
|
|
3034
|
+
function buildRoleAssignmentWorkspaceProjectionSk(params) {
|
|
3035
|
+
const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types7.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL4;
|
|
3036
|
+
return `ROLEASSIGNMENT#${params.roleId}#${normalizedUserName}#USER#${params.userId}#${params.roleAssignmentId}`;
|
|
3037
|
+
}
|
|
3038
|
+
function buildRoleAssignmentWorkspaceProjectionItem(input) {
|
|
3039
|
+
if (!input.workspaceId || input.workspaceId.length === 0) {
|
|
3040
|
+
return void 0;
|
|
3041
|
+
}
|
|
3042
|
+
if (!input.userId || input.userId.length === 0) {
|
|
3043
|
+
return void 0;
|
|
3044
|
+
}
|
|
3045
|
+
if (!input.roleId || input.roleId.length === 0) {
|
|
3046
|
+
return void 0;
|
|
3047
|
+
}
|
|
3048
|
+
const sk = buildRoleAssignmentWorkspaceProjectionSk({
|
|
3049
|
+
roleId: input.roleId,
|
|
3050
|
+
userId: input.userId,
|
|
3051
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
3052
|
+
denormalizedUserName: input.denormalizedUserName
|
|
3053
|
+
});
|
|
3054
|
+
return {
|
|
3055
|
+
tenantId: input.tenantId,
|
|
3056
|
+
workspaceId: input.workspaceId,
|
|
3057
|
+
sk,
|
|
3058
|
+
userId: input.userId,
|
|
3059
|
+
roleId: input.roleId,
|
|
3060
|
+
roleAssignmentId: input.roleAssignmentId,
|
|
3061
|
+
summary: input.summary,
|
|
3062
|
+
vid: input.vid,
|
|
3063
|
+
lastUpdated: input.lastUpdated,
|
|
3064
|
+
denormalizedUserName: input.denormalizedUserName,
|
|
3065
|
+
denormalizedRoleName: input.denormalizedRoleName
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
// src/data/operations/control/roleassignment/roleassignment-create-operation.ts
|
|
1673
3070
|
async function createRoleAssignmentOperation(params) {
|
|
1674
3071
|
const { context, body, tableName } = params;
|
|
1675
3072
|
const service = getDynamoControlService(tableName);
|
|
@@ -1678,15 +3075,80 @@ async function createRoleAssignmentOperation(params) {
|
|
|
1678
3075
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1679
3076
|
const vid = `1`;
|
|
1680
3077
|
const resource = { resourceType: "RoleAssignment", id, ...parsedResource };
|
|
1681
|
-
const
|
|
1682
|
-
|
|
3078
|
+
const resourceRecord = resource;
|
|
3079
|
+
const denormalizedTenantName = extractDenormalizedReferenceDisplay(
|
|
3080
|
+
resourceRecord,
|
|
3081
|
+
"tenant"
|
|
3082
|
+
);
|
|
3083
|
+
const denormalizedUserName = extractDenormalizedReferenceDisplay(
|
|
3084
|
+
resourceRecord,
|
|
3085
|
+
"user"
|
|
3086
|
+
);
|
|
3087
|
+
const denormalizedRoleName = extractDenormalizedReferenceDisplay(
|
|
3088
|
+
resourceRecord,
|
|
3089
|
+
"role"
|
|
3090
|
+
);
|
|
3091
|
+
const summary = JSON.stringify((0, import_types8.extractSummary)(resource));
|
|
3092
|
+
const userIdFromResource = extractReferenceSlug2(resourceRecord, "user");
|
|
3093
|
+
const roleIdFromResource = extractReferenceSlug2(resourceRecord, "role");
|
|
3094
|
+
const workspaceIdFromResource = extractReferenceSlug2(
|
|
3095
|
+
resourceRecord,
|
|
3096
|
+
"workspace"
|
|
3097
|
+
);
|
|
3098
|
+
const userProjectionItem = userIdFromResource !== void 0 && roleIdFromResource !== void 0 ? buildRoleAssignmentUserProjectionItem({
|
|
3099
|
+
tenantId: context.tenantId,
|
|
3100
|
+
userId: userIdFromResource,
|
|
3101
|
+
workspaceId: workspaceIdFromResource,
|
|
3102
|
+
roleId: roleIdFromResource,
|
|
3103
|
+
roleAssignmentId: id,
|
|
3104
|
+
summary,
|
|
3105
|
+
vid,
|
|
3106
|
+
lastUpdated,
|
|
3107
|
+
denormalizedTenantName,
|
|
3108
|
+
denormalizedUserName,
|
|
3109
|
+
denormalizedRoleName
|
|
3110
|
+
}) : void 0;
|
|
3111
|
+
const workspaceProjectionItem = userIdFromResource !== void 0 && roleIdFromResource !== void 0 && workspaceIdFromResource !== void 0 ? buildRoleAssignmentWorkspaceProjectionItem({
|
|
3112
|
+
tenantId: context.tenantId,
|
|
3113
|
+
workspaceId: workspaceIdFromResource,
|
|
3114
|
+
userId: userIdFromResource,
|
|
3115
|
+
roleId: roleIdFromResource,
|
|
3116
|
+
roleAssignmentId: id,
|
|
3117
|
+
summary,
|
|
3118
|
+
vid,
|
|
3119
|
+
lastUpdated,
|
|
3120
|
+
denormalizedUserName,
|
|
3121
|
+
denormalizedRoleName
|
|
3122
|
+
}) : void 0;
|
|
3123
|
+
const canonicalItem = {
|
|
1683
3124
|
tenantId: context.tenantId,
|
|
1684
3125
|
id,
|
|
1685
3126
|
resource: JSON.stringify(resource),
|
|
1686
3127
|
summary,
|
|
1687
3128
|
vid,
|
|
1688
|
-
lastUpdated
|
|
1689
|
-
|
|
3129
|
+
lastUpdated,
|
|
3130
|
+
denormalizedTenantName,
|
|
3131
|
+
denormalizedUserName,
|
|
3132
|
+
denormalizedRoleName
|
|
3133
|
+
};
|
|
3134
|
+
const triples = [
|
|
3135
|
+
{ entity: "roleAssignment", action: "put", item: canonicalItem }
|
|
3136
|
+
];
|
|
3137
|
+
if (userProjectionItem) {
|
|
3138
|
+
triples.push({
|
|
3139
|
+
entity: "roleAssignmentUserProjection",
|
|
3140
|
+
action: "put",
|
|
3141
|
+
item: userProjectionItem
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
if (workspaceProjectionItem) {
|
|
3145
|
+
triples.push({
|
|
3146
|
+
entity: "roleAssignmentWorkspaceProjection",
|
|
3147
|
+
action: "put",
|
|
3148
|
+
item: workspaceProjectionItem
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
await executeMultiWrite({ service, triples });
|
|
1690
3152
|
return {
|
|
1691
3153
|
id,
|
|
1692
3154
|
resource,
|
|
@@ -1695,7 +3157,7 @@ async function createRoleAssignmentOperation(params) {
|
|
|
1695
3157
|
}
|
|
1696
3158
|
|
|
1697
3159
|
// src/data/operations/control/tenant/tenant-create-operation.ts
|
|
1698
|
-
var
|
|
3160
|
+
var import_types9 = require("@openhi/types");
|
|
1699
3161
|
async function createTenantOperation(params) {
|
|
1700
3162
|
const { context, body, tableName } = params;
|
|
1701
3163
|
const service = getDynamoControlService(tableName);
|
|
@@ -1704,7 +3166,7 @@ async function createTenantOperation(params) {
|
|
|
1704
3166
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
1705
3167
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
1706
3168
|
const resource = { resourceType: "Tenant", id, ...parsedResource };
|
|
1707
|
-
const summary = JSON.stringify((0,
|
|
3169
|
+
const summary = JSON.stringify((0, import_types9.extractSummary)(resource));
|
|
1708
3170
|
await service.entities.tenant.put({
|
|
1709
3171
|
tenantId: id,
|
|
1710
3172
|
id,
|
|
@@ -1717,13 +3179,13 @@ async function createTenantOperation(params) {
|
|
|
1717
3179
|
}
|
|
1718
3180
|
|
|
1719
3181
|
// src/data/operations/control/workspace/workspace-create-operation.ts
|
|
1720
|
-
var
|
|
3182
|
+
var import_types11 = require("@openhi/types");
|
|
1721
3183
|
|
|
1722
3184
|
// src/data/dynamo/dynamo-data-service.ts
|
|
1723
|
-
var
|
|
3185
|
+
var import_electrodb16 = require("electrodb");
|
|
1724
3186
|
|
|
1725
3187
|
// src/data/dynamo/entities/data-entity-common.ts
|
|
1726
|
-
var
|
|
3188
|
+
var import_electrodb15 = require("electrodb");
|
|
1727
3189
|
var dataEntityAttributes = {
|
|
1728
3190
|
/** Sort key. "CURRENT" for current version; version history in S3. */
|
|
1729
3191
|
sk: {
|
|
@@ -1819,7 +3281,7 @@ var dataEntityAttributes = {
|
|
|
1819
3281
|
}
|
|
1820
3282
|
};
|
|
1821
3283
|
function createDataEntity(entity, resourceTypeLabel) {
|
|
1822
|
-
return new
|
|
3284
|
+
return new import_electrodb15.Entity({
|
|
1823
3285
|
model: {
|
|
1824
3286
|
entity,
|
|
1825
3287
|
service: "data",
|
|
@@ -2733,26 +4195,28 @@ var dataPlaneEntities = {
|
|
|
2733
4195
|
visionprescription: VisionPrescriptionEntity,
|
|
2734
4196
|
verificationresult: VerificationResultEntity
|
|
2735
4197
|
};
|
|
2736
|
-
var dataPlaneService = new
|
|
4198
|
+
var dataPlaneService = new import_electrodb16.Service(dataPlaneEntities, {
|
|
2737
4199
|
table: defaultTableName,
|
|
2738
4200
|
client: dynamoClient
|
|
2739
4201
|
});
|
|
2740
4202
|
var DynamoDataService = {
|
|
2741
|
-
entities: dataPlaneService.entities
|
|
4203
|
+
entities: dataPlaneService.entities,
|
|
4204
|
+
transaction: dataPlaneService.transaction
|
|
2742
4205
|
};
|
|
2743
4206
|
function getDynamoDataService(tableName) {
|
|
2744
4207
|
const resolved = tableName ?? defaultTableName;
|
|
2745
|
-
const service = new
|
|
4208
|
+
const service = new import_electrodb16.Service(dataPlaneEntities, {
|
|
2746
4209
|
table: resolved,
|
|
2747
4210
|
client: dynamoClient
|
|
2748
4211
|
});
|
|
2749
4212
|
return {
|
|
2750
|
-
entities: service.entities
|
|
4213
|
+
entities: service.entities,
|
|
4214
|
+
transaction: service.transaction
|
|
2751
4215
|
};
|
|
2752
4216
|
}
|
|
2753
4217
|
|
|
2754
4218
|
// src/data/operations/data-operations-common.ts
|
|
2755
|
-
var
|
|
4219
|
+
var import_types10 = require("@openhi/types");
|
|
2756
4220
|
|
|
2757
4221
|
// src/lib/compression.ts
|
|
2758
4222
|
var import_node_zlib = require("zlib");
|
|
@@ -2789,8 +4253,8 @@ async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourc
|
|
|
2789
4253
|
const lastUpdated = resourceWithAudit.meta?.lastUpdated ?? fallbackDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2790
4254
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
2791
4255
|
const resourceLike = resourceWithAudit;
|
|
2792
|
-
const summary = JSON.stringify((0,
|
|
2793
|
-
const gsi1sk = (0,
|
|
4256
|
+
const summary = JSON.stringify((0, import_types10.extractSummary)(resourceLike));
|
|
4257
|
+
const gsi1sk = (0, import_types10.extractSortKey)(resourceLike);
|
|
2794
4258
|
await entity.put({
|
|
2795
4259
|
sk: DATA_ENTITY_SK,
|
|
2796
4260
|
tenantId,
|
|
@@ -2856,7 +4320,7 @@ async function createWorkspaceOperation(params) {
|
|
|
2856
4320
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
2857
4321
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2858
4322
|
const resource = { resourceType: "Workspace", id, ...parsedResource };
|
|
2859
|
-
const summary = JSON.stringify((0,
|
|
4323
|
+
const summary = JSON.stringify((0, import_types11.extractSummary)(resource));
|
|
2860
4324
|
await service.entities.workspace.put({
|
|
2861
4325
|
tenantId,
|
|
2862
4326
|
id,
|
|
@@ -2884,9 +4348,9 @@ var errorMessage = (err) => {
|
|
|
2884
4348
|
return String(err);
|
|
2885
4349
|
};
|
|
2886
4350
|
var idForRoleCode = (code) => {
|
|
2887
|
-
for (const key of Object.keys(
|
|
2888
|
-
if (
|
|
2889
|
-
return
|
|
4351
|
+
for (const key of Object.keys(import_types12.PLATFORM_ROLE_IDS)) {
|
|
4352
|
+
if (import_types12.PLATFORM_ROLE_CONCEPTS[key].code === code) {
|
|
4353
|
+
return import_types12.PLATFORM_ROLE_IDS[key];
|
|
2890
4354
|
}
|
|
2891
4355
|
}
|
|
2892
4356
|
throw new Error(`No id mapping for role code "${code}".`);
|
|
@@ -2901,7 +4365,7 @@ var verifySystemRolesExist = async () => {
|
|
|
2901
4365
|
actorType: "internal-system",
|
|
2902
4366
|
source: "step-function"
|
|
2903
4367
|
};
|
|
2904
|
-
for (const id of Object.values(
|
|
4368
|
+
for (const id of Object.values(import_types12.PLATFORM_ROLE_IDS)) {
|
|
2905
4369
|
try {
|
|
2906
4370
|
await getRoleByIdOperation({ context: probeContext, id });
|
|
2907
4371
|
} catch (err) {
|
|
@@ -2982,7 +4446,7 @@ var userResourceBody = (user, cognitoSub) => ({
|
|
|
2982
4446
|
var upsertUser = async (context, user, cognitoSub) => {
|
|
2983
4447
|
const service = getDynamoControlService();
|
|
2984
4448
|
const resource = userResourceBody(user, cognitoSub);
|
|
2985
|
-
const summary = JSON.stringify((0,
|
|
4449
|
+
const summary = JSON.stringify((0, import_types12.extractSummary)(resource));
|
|
2986
4450
|
await service.entities.user.put({
|
|
2987
4451
|
id: user.id,
|
|
2988
4452
|
cognitoSub,
|
|
@@ -3050,7 +4514,7 @@ var seedDemoGraph = async (params) => {
|
|
|
3050
4514
|
...baseContext,
|
|
3051
4515
|
tenantId: PLATFORM_SCOPE_TENANT_ID
|
|
3052
4516
|
};
|
|
3053
|
-
const platformRoleCode =
|
|
4517
|
+
const platformRoleCode = import_types12.PLATFORM_ROLE_CODE.SYSTEM_ADMIN;
|
|
3054
4518
|
const platformRaId = demoRoleAssignmentId(
|
|
3055
4519
|
user.id,
|
|
3056
4520
|
PLATFORM_SCOPE_TENANT_ID,
|