@openhi/constructs 0.0.110 → 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-2O3CXY2C.mjs +79 -0
- package/lib/chunk-2O3CXY2C.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/data-store-postgres-replication.handler.js +26 -17
- package/lib/data-store-postgres-replication.handler.js.map +1 -1
- package/lib/data-store-postgres-replication.handler.mjs +5 -65
- package/lib/data-store-postgres-replication.handler.mjs.map +1 -1
- 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 +4087 -921
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1827 -81
- 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 +1 -1
- 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
|
} });
|
|
@@ -650,7 +725,7 @@ var SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR = "CONTROL_EVENT_BUS_NAME";
|
|
|
650
725
|
var import_types2 = require("@openhi/types");
|
|
651
726
|
|
|
652
727
|
// src/data/dynamo/dynamo-control-service.ts
|
|
653
|
-
var
|
|
728
|
+
var import_electrodb14 = require("electrodb");
|
|
654
729
|
|
|
655
730
|
// src/data/dynamo/dynamo-client.ts
|
|
656
731
|
var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
@@ -714,6 +789,60 @@ var gsi1skAttribute = {
|
|
|
714
789
|
return label !== void 0 ? `${label}#${id}` : fallback;
|
|
715
790
|
}
|
|
716
791
|
};
|
|
792
|
+
function extractRoleId(resource) {
|
|
793
|
+
const flat = resource.roleId;
|
|
794
|
+
if (typeof flat === "string" && flat.length > 0) return flat;
|
|
795
|
+
const role = resource.role;
|
|
796
|
+
if (role && typeof role === "object") {
|
|
797
|
+
const reference = role.reference;
|
|
798
|
+
if (typeof reference === "string" && reference.length > 0) {
|
|
799
|
+
const slash = reference.lastIndexOf("/");
|
|
800
|
+
const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
|
|
801
|
+
if (tail.length > 0) return tail;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return void 0;
|
|
805
|
+
}
|
|
806
|
+
var roleAssignmentGsi1skAttribute = {
|
|
807
|
+
type: "string",
|
|
808
|
+
watch: ["resource", "denormalizedUserName", "lastUpdated", "id"],
|
|
809
|
+
set: (_val, item) => {
|
|
810
|
+
const id = typeof item?.id === "string" ? item.id : "";
|
|
811
|
+
const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
|
|
812
|
+
const fallback = `${lastUpdated}#${id}`;
|
|
813
|
+
if (typeof item?.resource !== "string" || item.resource.length === 0) {
|
|
814
|
+
return fallback;
|
|
815
|
+
}
|
|
816
|
+
let parsed;
|
|
817
|
+
try {
|
|
818
|
+
parsed = JSON.parse(item.resource);
|
|
819
|
+
} catch {
|
|
820
|
+
return fallback;
|
|
821
|
+
}
|
|
822
|
+
if (!parsed || typeof parsed !== "object") return fallback;
|
|
823
|
+
const roleId = extractRoleId(parsed);
|
|
824
|
+
if (roleId === void 0) return fallback;
|
|
825
|
+
const denormalizedUserName = typeof item.denormalizedUserName === "string" ? item.denormalizedUserName : "";
|
|
826
|
+
const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types.normalizeLabel)(denormalizedUserName) : "";
|
|
827
|
+
if (normalizedUserName.length === 0) return fallback;
|
|
828
|
+
return `${roleId}#${normalizedUserName}#${id}`;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
var membershipGsi1skAttribute = {
|
|
832
|
+
type: "string",
|
|
833
|
+
watch: ["denormalizedUserName", "lastUpdated", "id"],
|
|
834
|
+
set: (_val, item) => {
|
|
835
|
+
const id = typeof item?.id === "string" ? item.id : "";
|
|
836
|
+
const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
|
|
837
|
+
const fallback = `${lastUpdated}#${id}`;
|
|
838
|
+
const denormalizedUserName = typeof item?.denormalizedUserName === "string" ? item.denormalizedUserName : "";
|
|
839
|
+
const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types.normalizeLabel)(denormalizedUserName) : "";
|
|
840
|
+
if (normalizedUserName.length === 0) {
|
|
841
|
+
return fallback;
|
|
842
|
+
}
|
|
843
|
+
return `${normalizedUserName}#${id}`;
|
|
844
|
+
}
|
|
845
|
+
};
|
|
717
846
|
|
|
718
847
|
// src/data/dynamo/entities/control/configuration-entity.ts
|
|
719
848
|
var ConfigurationEntity = new import_electrodb.Entity({
|
|
@@ -840,218 +969,241 @@ var ConfigurationEntity = new import_electrodb.Entity({
|
|
|
840
969
|
}
|
|
841
970
|
});
|
|
842
971
|
|
|
843
|
-
// src/data/dynamo/entities/control/
|
|
972
|
+
// src/data/dynamo/entities/control/configuration-user-projection-entity.ts
|
|
844
973
|
var import_electrodb2 = require("electrodb");
|
|
845
|
-
var
|
|
974
|
+
var ConfigurationUserProjectionEntity = new import_electrodb2.Entity({
|
|
846
975
|
model: {
|
|
847
|
-
entity: "
|
|
976
|
+
entity: "configurationUserProjection",
|
|
848
977
|
service: "control",
|
|
849
978
|
version: "01"
|
|
850
979
|
},
|
|
851
980
|
attributes: {
|
|
852
|
-
/**
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
/** Tenant in which the user has membership (required). */
|
|
859
|
-
tenantId: {
|
|
981
|
+
/**
|
|
982
|
+
* User partition discriminator. Renders as `USER#ID#<userId>` on the
|
|
983
|
+
* base-table PK. Always required — the projection has no meaning
|
|
984
|
+
* outside a user partition.
|
|
985
|
+
*/
|
|
986
|
+
userId: {
|
|
860
987
|
type: "string",
|
|
861
988
|
required: true
|
|
862
989
|
},
|
|
863
|
-
/**
|
|
864
|
-
|
|
990
|
+
/**
|
|
991
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
992
|
+
* writer via `buildConfigurationUserProjectionSk`. The entity stores
|
|
993
|
+
* the value verbatim so the SK grammar (pattern #10 user-scope) is
|
|
994
|
+
* owned by the operations layer, not duplicated here.
|
|
995
|
+
*/
|
|
996
|
+
sk: {
|
|
865
997
|
type: "string",
|
|
866
998
|
required: true
|
|
867
999
|
},
|
|
868
|
-
/**
|
|
869
|
-
|
|
1000
|
+
/**
|
|
1001
|
+
* Configuration canonical-record id. Stored as a discriminating
|
|
1002
|
+
* field so consumers can hydrate the canonical row via the
|
|
1003
|
+
* Configuration get-by-id operation when the projection's `summary`
|
|
1004
|
+
* is insufficient.
|
|
1005
|
+
*/
|
|
1006
|
+
configurationId: {
|
|
870
1007
|
type: "string",
|
|
871
1008
|
required: true
|
|
872
1009
|
},
|
|
873
1010
|
/**
|
|
874
|
-
*
|
|
875
|
-
*
|
|
1011
|
+
* Tenant the Configuration is associated with. The canonical row
|
|
1012
|
+
* keys off `(tenantId, workspaceId, userId, roleId)`; the projection
|
|
1013
|
+
* carries `tenantId` so consumers reconstructing the canonical PK
|
|
1014
|
+
* have the tenant segment without a hop.
|
|
876
1015
|
*/
|
|
877
|
-
|
|
1016
|
+
tenantId: {
|
|
878
1017
|
type: "string",
|
|
879
1018
|
required: true
|
|
880
1019
|
},
|
|
881
|
-
/**
|
|
882
|
-
|
|
1020
|
+
/**
|
|
1021
|
+
* Scope marker. Always `"user"` on this projection — recorded
|
|
1022
|
+
* explicitly so future scope-bearing projections (workspace,
|
|
1023
|
+
* tenant, role) can share filter semantics in a unified
|
|
1024
|
+
* cross-projection list query if one ever lands.
|
|
1025
|
+
*/
|
|
1026
|
+
scope: {
|
|
883
1027
|
type: "string",
|
|
884
|
-
required: true
|
|
1028
|
+
required: true,
|
|
1029
|
+
default: "user"
|
|
885
1030
|
},
|
|
886
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* Configuration's `key` attribute (config category, e.g. endpoints,
|
|
1033
|
+
* branding, display). Mirrored from the canonical row so consumers
|
|
1034
|
+
* reading the projection get the natural display label without a
|
|
1035
|
+
* BatchGet hop. Doubles as the source of `<normalizedConfigName>` in
|
|
1036
|
+
* the SK.
|
|
1037
|
+
*/
|
|
1038
|
+
displayName: {
|
|
887
1039
|
type: "string",
|
|
888
|
-
required: true
|
|
889
|
-
},
|
|
890
|
-
gsi1Shard: gsi1ShardAttribute,
|
|
891
|
-
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
892
|
-
gsi1sk: gsi1skAttribute,
|
|
893
|
-
deleted: {
|
|
894
|
-
type: "boolean",
|
|
895
1040
|
required: false
|
|
896
1041
|
},
|
|
897
|
-
|
|
1042
|
+
/**
|
|
1043
|
+
* Summary projection (key display fields as JSON string) — mirrored
|
|
1044
|
+
* from the canonical Configuration row so user-partition queries do
|
|
1045
|
+
* not need a BatchGet hop.
|
|
1046
|
+
*/
|
|
1047
|
+
summary: {
|
|
898
1048
|
type: "string",
|
|
899
|
-
required:
|
|
1049
|
+
required: true
|
|
900
1050
|
},
|
|
901
|
-
|
|
1051
|
+
/** Version id mirrored from the canonical Configuration row. */
|
|
1052
|
+
vid: {
|
|
902
1053
|
type: "string",
|
|
903
|
-
required:
|
|
1054
|
+
required: true
|
|
904
1055
|
},
|
|
905
|
-
/**
|
|
906
|
-
|
|
907
|
-
* Populated from the FHIR extension on the Membership resource at write
|
|
908
|
-
* time so future GSIs can index data-plane identity lookups without
|
|
909
|
-
* deserializing the full resource JSON. See ADR 2026-03-13-02 §6.
|
|
910
|
-
*/
|
|
911
|
-
linkedDataIdentityRef: {
|
|
1056
|
+
/** Last-updated timestamp mirrored from the canonical Configuration row. */
|
|
1057
|
+
lastUpdated: {
|
|
912
1058
|
type: "string",
|
|
913
|
-
required:
|
|
1059
|
+
required: true
|
|
914
1060
|
}
|
|
915
1061
|
},
|
|
916
1062
|
indexes: {
|
|
917
|
-
/**
|
|
1063
|
+
/**
|
|
1064
|
+
* Base table: PK = USER#ID#<userId>, SK = operation-supplied. A
|
|
1065
|
+
* single `Query(PK = USER#ID#<userId>, SK begins_with
|
|
1066
|
+
* 'CONFIGURATION#')` returns the user's user-scoped Configurations
|
|
1067
|
+
* sorted by `<normalizedConfigName>` (then `<configurationId>` as
|
|
1068
|
+
* the tiebreaker).
|
|
1069
|
+
*/
|
|
918
1070
|
record: {
|
|
919
1071
|
pk: {
|
|
920
1072
|
field: "PK",
|
|
921
|
-
composite: ["
|
|
922
|
-
template: "
|
|
1073
|
+
composite: ["userId"],
|
|
1074
|
+
template: "USER#ID#${userId}"
|
|
923
1075
|
},
|
|
924
1076
|
sk: {
|
|
925
1077
|
field: "SK",
|
|
1078
|
+
casing: "none",
|
|
926
1079
|
composite: ["sk"],
|
|
927
1080
|
template: "${sk}"
|
|
928
1081
|
}
|
|
929
|
-
},
|
|
930
|
-
/**
|
|
931
|
-
* GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
|
|
932
|
-
* four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
|
|
933
|
-
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
934
|
-
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
935
|
-
* normalized label and ISO-8601 `T`/`Z`.
|
|
936
|
-
*/
|
|
937
|
-
gsi1: {
|
|
938
|
-
index: "GSI1",
|
|
939
|
-
pk: {
|
|
940
|
-
field: "GSI1PK",
|
|
941
|
-
composite: ["tenantId", "gsi1Shard"],
|
|
942
|
-
template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
|
|
943
|
-
},
|
|
944
|
-
sk: {
|
|
945
|
-
field: "GSI1SK",
|
|
946
|
-
casing: "none",
|
|
947
|
-
composite: ["gsi1sk"],
|
|
948
|
-
template: "${gsi1sk}"
|
|
949
|
-
}
|
|
950
1082
|
}
|
|
951
1083
|
}
|
|
952
1084
|
});
|
|
953
1085
|
|
|
954
|
-
// src/data/dynamo/entities/control/
|
|
1086
|
+
// src/data/dynamo/entities/control/configuration-workspace-projection-entity.ts
|
|
955
1087
|
var import_electrodb3 = require("electrodb");
|
|
956
|
-
var
|
|
1088
|
+
var ConfigurationWorkspaceProjectionEntity = new import_electrodb3.Entity({
|
|
957
1089
|
model: {
|
|
958
|
-
entity: "
|
|
1090
|
+
entity: "configurationWorkspaceProjection",
|
|
959
1091
|
service: "control",
|
|
960
1092
|
version: "01"
|
|
961
1093
|
},
|
|
962
1094
|
attributes: {
|
|
963
|
-
/**
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
/** FHIR Resource.id; role id. */
|
|
970
|
-
id: {
|
|
1095
|
+
/**
|
|
1096
|
+
* Tenant the workspace belongs to. Renders as the leading segment
|
|
1097
|
+
* of the base-table PK. Always required — the workspace partition
|
|
1098
|
+
* is tenant-scoped per ADR-011.
|
|
1099
|
+
*/
|
|
1100
|
+
tenantId: {
|
|
971
1101
|
type: "string",
|
|
972
1102
|
required: true
|
|
973
1103
|
},
|
|
974
|
-
/**
|
|
975
|
-
|
|
1104
|
+
/**
|
|
1105
|
+
* Workspace partition discriminator. Renders as the trailing
|
|
1106
|
+
* segment of the base-table PK
|
|
1107
|
+
* (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
|
|
1108
|
+
* the projection has no meaning outside a workspace partition.
|
|
1109
|
+
*/
|
|
1110
|
+
workspaceId: {
|
|
976
1111
|
type: "string",
|
|
977
1112
|
required: true
|
|
978
1113
|
},
|
|
979
1114
|
/**
|
|
980
|
-
*
|
|
981
|
-
*
|
|
1115
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1116
|
+
* writer via `buildConfigurationWorkspaceProjectionSk`. The entity
|
|
1117
|
+
* stores the value verbatim so the SK grammar (pattern #10
|
|
1118
|
+
* workspace-scope) is owned by the operations layer, not
|
|
1119
|
+
* duplicated here.
|
|
982
1120
|
*/
|
|
983
|
-
|
|
1121
|
+
sk: {
|
|
984
1122
|
type: "string",
|
|
985
1123
|
required: true
|
|
986
1124
|
},
|
|
987
|
-
/**
|
|
988
|
-
|
|
1125
|
+
/**
|
|
1126
|
+
* Configuration canonical-record id. Stored as a discriminating
|
|
1127
|
+
* field so consumers can hydrate the canonical row via the
|
|
1128
|
+
* Configuration get-by-id operation when the projection's `summary`
|
|
1129
|
+
* is insufficient.
|
|
1130
|
+
*/
|
|
1131
|
+
configurationId: {
|
|
989
1132
|
type: "string",
|
|
990
1133
|
required: true
|
|
991
1134
|
},
|
|
992
|
-
|
|
1135
|
+
/**
|
|
1136
|
+
* Scope marker. Always `"workspace"` on this projection — recorded
|
|
1137
|
+
* explicitly so future scope-bearing projections (user, tenant,
|
|
1138
|
+
* role) can share filter semantics in a unified cross-projection
|
|
1139
|
+
* list query if one ever lands.
|
|
1140
|
+
*/
|
|
1141
|
+
scope: {
|
|
993
1142
|
type: "string",
|
|
994
|
-
required: true
|
|
1143
|
+
required: true,
|
|
1144
|
+
default: "workspace"
|
|
995
1145
|
},
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* Configuration's `key` attribute (config category, e.g. endpoints,
|
|
1148
|
+
* branding, display). Mirrored from the canonical row so consumers
|
|
1149
|
+
* reading the projection get the natural display label without a
|
|
1150
|
+
* BatchGet hop. Doubles as the source of `<normalizedConfigName>`
|
|
1151
|
+
* in the SK.
|
|
1152
|
+
*/
|
|
1153
|
+
displayName: {
|
|
1154
|
+
type: "string",
|
|
1001
1155
|
required: false
|
|
1002
1156
|
},
|
|
1003
|
-
|
|
1157
|
+
/**
|
|
1158
|
+
* Summary projection (key display fields as JSON string) — mirrored
|
|
1159
|
+
* from the canonical Configuration row so workspace-partition
|
|
1160
|
+
* queries do not need a BatchGet hop.
|
|
1161
|
+
*/
|
|
1162
|
+
summary: {
|
|
1004
1163
|
type: "string",
|
|
1005
|
-
required:
|
|
1164
|
+
required: true
|
|
1006
1165
|
},
|
|
1007
|
-
|
|
1166
|
+
/** Version id mirrored from the canonical Configuration row. */
|
|
1167
|
+
vid: {
|
|
1008
1168
|
type: "string",
|
|
1009
|
-
required:
|
|
1169
|
+
required: true
|
|
1170
|
+
},
|
|
1171
|
+
/** Last-updated timestamp mirrored from the canonical Configuration row. */
|
|
1172
|
+
lastUpdated: {
|
|
1173
|
+
type: "string",
|
|
1174
|
+
required: true
|
|
1010
1175
|
}
|
|
1011
1176
|
},
|
|
1012
1177
|
indexes: {
|
|
1013
|
-
/**
|
|
1178
|
+
/**
|
|
1179
|
+
* Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
|
|
1180
|
+
* SK = operation-supplied. A single
|
|
1181
|
+
* `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'CONFIGURATION#')`
|
|
1182
|
+
* returns the workspace's workspace-scoped Configurations sorted by
|
|
1183
|
+
* `<normalizedConfigName>` (then `<configurationId>` as the
|
|
1184
|
+
* tiebreaker).
|
|
1185
|
+
*/
|
|
1014
1186
|
record: {
|
|
1015
1187
|
pk: {
|
|
1016
1188
|
field: "PK",
|
|
1017
|
-
composite: ["
|
|
1018
|
-
template: "
|
|
1189
|
+
composite: ["tenantId", "workspaceId"],
|
|
1190
|
+
template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
|
|
1019
1191
|
},
|
|
1020
1192
|
sk: {
|
|
1021
1193
|
field: "SK",
|
|
1194
|
+
casing: "none",
|
|
1022
1195
|
composite: ["sk"],
|
|
1023
1196
|
template: "${sk}"
|
|
1024
1197
|
}
|
|
1025
|
-
},
|
|
1026
|
-
/**
|
|
1027
|
-
* GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
|
|
1028
|
-
* Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
|
|
1029
|
-
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
1030
|
-
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
1031
|
-
* normalized label and ISO-8601 `T`/`Z`.
|
|
1032
|
-
*/
|
|
1033
|
-
gsi1: {
|
|
1034
|
-
index: "GSI1",
|
|
1035
|
-
pk: {
|
|
1036
|
-
field: "GSI1PK",
|
|
1037
|
-
composite: ["gsi1Shard"],
|
|
1038
|
-
template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
|
|
1039
|
-
},
|
|
1040
|
-
sk: {
|
|
1041
|
-
field: "GSI1SK",
|
|
1042
|
-
casing: "none",
|
|
1043
|
-
composite: ["gsi1sk"],
|
|
1044
|
-
template: "${gsi1sk}"
|
|
1045
|
-
}
|
|
1046
1198
|
}
|
|
1047
1199
|
}
|
|
1048
1200
|
});
|
|
1049
1201
|
|
|
1050
|
-
// src/data/dynamo/entities/control/
|
|
1202
|
+
// src/data/dynamo/entities/control/membership-entity.ts
|
|
1051
1203
|
var import_electrodb4 = require("electrodb");
|
|
1052
|
-
var
|
|
1204
|
+
var MembershipEntity = new import_electrodb4.Entity({
|
|
1053
1205
|
model: {
|
|
1054
|
-
entity: "
|
|
1206
|
+
entity: "membership",
|
|
1055
1207
|
service: "control",
|
|
1056
1208
|
version: "01"
|
|
1057
1209
|
},
|
|
@@ -1062,17 +1214,17 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
1062
1214
|
required: true,
|
|
1063
1215
|
default: "CURRENT"
|
|
1064
1216
|
},
|
|
1065
|
-
/** Tenant in which the
|
|
1217
|
+
/** Tenant in which the user has membership (required). */
|
|
1066
1218
|
tenantId: {
|
|
1067
1219
|
type: "string",
|
|
1068
1220
|
required: true
|
|
1069
1221
|
},
|
|
1070
|
-
/** FHIR Resource.id;
|
|
1222
|
+
/** FHIR Resource.id; membership id. */
|
|
1071
1223
|
id: {
|
|
1072
1224
|
type: "string",
|
|
1073
1225
|
required: true
|
|
1074
1226
|
},
|
|
1075
|
-
/** Full
|
|
1227
|
+
/** Full Membership resource serialized as JSON string. */
|
|
1076
1228
|
resource: {
|
|
1077
1229
|
type: "string",
|
|
1078
1230
|
required: true
|
|
@@ -1095,8 +1247,14 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
1095
1247
|
required: true
|
|
1096
1248
|
},
|
|
1097
1249
|
gsi1Shard: gsi1ShardAttribute,
|
|
1098
|
-
/**
|
|
1099
|
-
|
|
1250
|
+
/**
|
|
1251
|
+
* Derived GSI1 sort key — `<normalizedUserName>#<id>` per ADR-018
|
|
1252
|
+
* pattern #1 so a GSI1 query partitioned on the tenant range-scans
|
|
1253
|
+
* by user-name prefix and returns memberships sorted by user name.
|
|
1254
|
+
* Falls back to `<lastUpdated>#<id>` when `denormalizedUserName`
|
|
1255
|
+
* is missing.
|
|
1256
|
+
*/
|
|
1257
|
+
gsi1sk: membershipGsi1skAttribute,
|
|
1100
1258
|
deleted: {
|
|
1101
1259
|
type: "boolean",
|
|
1102
1260
|
required: false
|
|
@@ -1108,28 +1266,566 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
1108
1266
|
msgId: {
|
|
1109
1267
|
type: "string",
|
|
1110
1268
|
required: false
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1269
|
+
},
|
|
1270
|
+
/**
|
|
1271
|
+
* Denormalized `linked-data-identity` Reference (e.g. `Practitioner/abc`).
|
|
1272
|
+
* Populated from the FHIR extension on the Membership resource at write
|
|
1273
|
+
* time so future GSIs can index data-plane identity lookups without
|
|
1274
|
+
* deserializing the full resource JSON. See ADR 2026-03-13-02 §6.
|
|
1275
|
+
*/
|
|
1276
|
+
linkedDataIdentityRef: {
|
|
1277
|
+
type: "string",
|
|
1278
|
+
required: false
|
|
1279
|
+
},
|
|
1280
|
+
/**
|
|
1281
|
+
* Denormalized display name of the linked Tenant, captured at row
|
|
1282
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1283
|
+
* adjacency-list projection SKs (pattern #3 — `MEMBERSHIP#TENANT#<normalizedTenantName>#…`)
|
|
1284
|
+
* can be composed from a top-level field instead of digging into the
|
|
1285
|
+
* `resource` JSON. Optional on the schema so pre-TR-024 rows do not
|
|
1286
|
+
* break; the operations-layer multi-write helper (#1010) makes the
|
|
1287
|
+
* field load-bearing at write time per TR-024 rule 2 (write-time
|
|
1288
|
+
* source = canonical Tenant.displayName).
|
|
1289
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1290
|
+
*/
|
|
1291
|
+
denormalizedTenantName: {
|
|
1292
|
+
type: "string",
|
|
1293
|
+
required: false
|
|
1294
|
+
},
|
|
1295
|
+
/**
|
|
1296
|
+
* Denormalized display name of the linked User, captured at row
|
|
1297
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1298
|
+
* adjacency-list canonical-record GSI1SK (pattern #1 —
|
|
1299
|
+
* `<normalizedUserName>#<id>`) and workspace-projection SK (pattern #2)
|
|
1300
|
+
* can be composed from a top-level field. Optional on the schema so
|
|
1301
|
+
* pre-TR-024 rows do not break; the operations-layer multi-write helper
|
|
1302
|
+
* (#1010) makes the field load-bearing at write time per TR-024 rule 2
|
|
1303
|
+
* (write-time source = canonical User.displayName).
|
|
1304
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1305
|
+
*/
|
|
1306
|
+
denormalizedUserName: {
|
|
1307
|
+
type: "string",
|
|
1308
|
+
required: false
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
indexes: {
|
|
1312
|
+
/** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
|
|
1313
|
+
record: {
|
|
1314
|
+
pk: {
|
|
1315
|
+
field: "PK",
|
|
1316
|
+
composite: ["tenantId", "id"],
|
|
1317
|
+
template: "TID#${tenantId}#MEMBERSHIP#ID#${id}"
|
|
1318
|
+
},
|
|
1319
|
+
sk: {
|
|
1320
|
+
field: "SK",
|
|
1321
|
+
composite: ["sk"],
|
|
1322
|
+
template: "${sk}"
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
/**
|
|
1326
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
|
|
1327
|
+
* four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
|
|
1328
|
+
* SK is derived via `membershipGsi1skAttribute` — composes
|
|
1329
|
+
* `<normalizedUserName>#<id>` per ADR-018 pattern #1 (users in a
|
|
1330
|
+
* tenant, sorted by user name); falls back to `<lastUpdated>#<id>`
|
|
1331
|
+
* when `denormalizedUserName` is missing. `casing: "none"` preserves
|
|
1332
|
+
* the normalized label and ISO-8601 `T`/`Z`.
|
|
1333
|
+
*/
|
|
1334
|
+
gsi1: {
|
|
1335
|
+
index: "GSI1",
|
|
1336
|
+
pk: {
|
|
1337
|
+
field: "GSI1PK",
|
|
1338
|
+
composite: ["tenantId", "gsi1Shard"],
|
|
1339
|
+
template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
|
|
1340
|
+
},
|
|
1341
|
+
sk: {
|
|
1342
|
+
field: "GSI1SK",
|
|
1343
|
+
casing: "none",
|
|
1344
|
+
composite: ["gsi1sk"],
|
|
1345
|
+
template: "${gsi1sk}"
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
// src/data/dynamo/entities/control/membership-user-projection-entity.ts
|
|
1352
|
+
var import_electrodb5 = require("electrodb");
|
|
1353
|
+
var MembershipUserProjectionEntity = new import_electrodb5.Entity({
|
|
1354
|
+
model: {
|
|
1355
|
+
entity: "membershipUserProjection",
|
|
1356
|
+
service: "control",
|
|
1357
|
+
version: "01"
|
|
1358
|
+
},
|
|
1359
|
+
attributes: {
|
|
1360
|
+
/**
|
|
1361
|
+
* User partition discriminator. Renders as `USER#ID#<userId>` on the
|
|
1362
|
+
* base-table PK. Always required — the projection has no meaning
|
|
1363
|
+
* outside a user partition.
|
|
1364
|
+
*/
|
|
1365
|
+
userId: {
|
|
1366
|
+
type: "string",
|
|
1367
|
+
required: true
|
|
1368
|
+
},
|
|
1369
|
+
/**
|
|
1370
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1371
|
+
* writer via `buildMembershipUserProjectionSk*` helpers. The entity
|
|
1372
|
+
* stores the value verbatim so the SK grammar (patterns #3 and #4)
|
|
1373
|
+
* is owned by the operations layer, not duplicated here.
|
|
1374
|
+
*/
|
|
1375
|
+
sk: {
|
|
1376
|
+
type: "string",
|
|
1377
|
+
required: true
|
|
1378
|
+
},
|
|
1379
|
+
/** Tenant in which the membership applies. Always required. */
|
|
1380
|
+
tenantId: {
|
|
1381
|
+
type: "string",
|
|
1382
|
+
required: true
|
|
1383
|
+
},
|
|
1384
|
+
/**
|
|
1385
|
+
* Workspace the membership scopes to. Present iff the projection
|
|
1386
|
+
* row is a pattern-#4 workspace sub-lane row; absent for pattern-#3
|
|
1387
|
+
* tenant sub-lane rows.
|
|
1388
|
+
*/
|
|
1389
|
+
workspaceId: {
|
|
1390
|
+
type: "string",
|
|
1391
|
+
required: false
|
|
1392
|
+
},
|
|
1393
|
+
/**
|
|
1394
|
+
* Membership canonical-record id. Stored as a discriminating field
|
|
1395
|
+
* so consumers can hydrate the canonical row via
|
|
1396
|
+
* `MembershipEntity.get({ tenantId, id: membershipId })` when the
|
|
1397
|
+
* projection's `summary` is insufficient.
|
|
1398
|
+
*/
|
|
1399
|
+
membershipId: {
|
|
1400
|
+
type: "string",
|
|
1401
|
+
required: true
|
|
1402
|
+
},
|
|
1403
|
+
/**
|
|
1404
|
+
* Summary projection (key display fields as JSON string: id,
|
|
1405
|
+
* displayName, status) — mirrored from the canonical Membership row
|
|
1406
|
+
* so user-partition queries do not need a BatchGet hop.
|
|
1407
|
+
*/
|
|
1408
|
+
summary: {
|
|
1409
|
+
type: "string",
|
|
1410
|
+
required: true
|
|
1411
|
+
},
|
|
1412
|
+
/** Version id mirrored from the canonical Membership row. */
|
|
1413
|
+
vid: {
|
|
1414
|
+
type: "string",
|
|
1415
|
+
required: true
|
|
1416
|
+
},
|
|
1417
|
+
/** Last-updated timestamp mirrored from the canonical Membership row. */
|
|
1418
|
+
lastUpdated: {
|
|
1419
|
+
type: "string",
|
|
1420
|
+
required: true
|
|
1421
|
+
},
|
|
1422
|
+
/**
|
|
1423
|
+
* Denormalized Tenant display name — required to compose pattern-#3
|
|
1424
|
+
* SK (`MEMBERSHIP#TENANT#<normalizedTenantName>#…`). Optional on the
|
|
1425
|
+
* schema because pre-TR-024 rows may not carry a display name; the
|
|
1426
|
+
* operations layer falls back gracefully when missing.
|
|
1427
|
+
*/
|
|
1428
|
+
denormalizedTenantName: {
|
|
1429
|
+
type: "string",
|
|
1430
|
+
required: false
|
|
1431
|
+
},
|
|
1432
|
+
/**
|
|
1433
|
+
* Denormalized User display name — mirrored from the canonical
|
|
1434
|
+
* Membership row per TR-024 rule 3 (canonical-record symmetry).
|
|
1435
|
+
* Carried on the projection so consumers can render the user's
|
|
1436
|
+
* display name without a hop to the User record.
|
|
1437
|
+
*/
|
|
1438
|
+
denormalizedUserName: {
|
|
1439
|
+
type: "string",
|
|
1440
|
+
required: false
|
|
1441
|
+
},
|
|
1442
|
+
/**
|
|
1443
|
+
* Denormalized Workspace display name — required to compose
|
|
1444
|
+
* pattern-#4 SK (`MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#…`).
|
|
1445
|
+
* Optional on the schema (TR-024 § Open Item #4 defers a formal
|
|
1446
|
+
* Workspace-rename cascade); the operations layer falls back to a
|
|
1447
|
+
* sentinel when missing so the SK still has a valid shape.
|
|
1448
|
+
*/
|
|
1449
|
+
denormalizedWorkspaceName: {
|
|
1450
|
+
type: "string",
|
|
1451
|
+
required: false
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
indexes: {
|
|
1455
|
+
/**
|
|
1456
|
+
* Base table: PK = USER#ID#<userId>, SK = operation-supplied.
|
|
1457
|
+
* Both pattern #3 and pattern #4 use this same index — the SK string
|
|
1458
|
+
* encodes the lane discriminator (`MEMBERSHIP#TENANT#…` vs
|
|
1459
|
+
* `MEMBERSHIP#WORKSPACE#…`) so a single `Query(PK = USER#ID#<userId>,
|
|
1460
|
+
* SK begins_with 'MEMBERSHIP#')` returns both lanes interleaved.
|
|
1461
|
+
*/
|
|
1462
|
+
record: {
|
|
1463
|
+
pk: {
|
|
1464
|
+
field: "PK",
|
|
1465
|
+
composite: ["userId"],
|
|
1466
|
+
template: "USER#ID#${userId}"
|
|
1467
|
+
},
|
|
1468
|
+
sk: {
|
|
1469
|
+
field: "SK",
|
|
1470
|
+
casing: "none",
|
|
1471
|
+
composite: ["sk"],
|
|
1472
|
+
template: "${sk}"
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
// src/data/dynamo/entities/control/membership-workspace-projection-entity.ts
|
|
1479
|
+
var import_electrodb6 = require("electrodb");
|
|
1480
|
+
var MembershipWorkspaceProjectionEntity = new import_electrodb6.Entity({
|
|
1481
|
+
model: {
|
|
1482
|
+
entity: "membershipWorkspaceProjection",
|
|
1483
|
+
service: "control",
|
|
1484
|
+
version: "01"
|
|
1485
|
+
},
|
|
1486
|
+
attributes: {
|
|
1487
|
+
/**
|
|
1488
|
+
* Tenant the workspace belongs to. Renders as the leading segment
|
|
1489
|
+
* of the base-table PK. Always required — the workspace partition
|
|
1490
|
+
* is tenant-scoped per ADR-011.
|
|
1491
|
+
*/
|
|
1492
|
+
tenantId: {
|
|
1493
|
+
type: "string",
|
|
1494
|
+
required: true
|
|
1495
|
+
},
|
|
1496
|
+
/**
|
|
1497
|
+
* Workspace partition discriminator. Renders as the trailing
|
|
1498
|
+
* segment of the base-table PK
|
|
1499
|
+
* (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
|
|
1500
|
+
* the projection has no meaning outside a workspace partition.
|
|
1501
|
+
*/
|
|
1502
|
+
workspaceId: {
|
|
1503
|
+
type: "string",
|
|
1504
|
+
required: true
|
|
1505
|
+
},
|
|
1506
|
+
/**
|
|
1507
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1508
|
+
* writer via `buildMembershipWorkspaceProjectionSk`. The entity
|
|
1509
|
+
* stores the value verbatim so the SK grammar (pattern #2) is
|
|
1510
|
+
* owned by the operations layer, not duplicated here.
|
|
1511
|
+
*/
|
|
1512
|
+
sk: {
|
|
1513
|
+
type: "string",
|
|
1514
|
+
required: true
|
|
1515
|
+
},
|
|
1516
|
+
/**
|
|
1517
|
+
* User the membership links. Stored as a discriminating field so
|
|
1518
|
+
* consumers can hydrate the canonical User row via
|
|
1519
|
+
* `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
|
|
1520
|
+
* projection's `summary` is insufficient.
|
|
1521
|
+
*/
|
|
1522
|
+
userId: {
|
|
1523
|
+
type: "string",
|
|
1524
|
+
required: true
|
|
1525
|
+
},
|
|
1526
|
+
/**
|
|
1527
|
+
* Membership canonical-record id. Stored as a discriminating field
|
|
1528
|
+
* so consumers can hydrate the canonical row via
|
|
1529
|
+
* `MembershipEntity.get({ tenantId, id: membershipId })` when the
|
|
1530
|
+
* projection's `summary` is insufficient.
|
|
1531
|
+
*/
|
|
1532
|
+
membershipId: {
|
|
1533
|
+
type: "string",
|
|
1534
|
+
required: true
|
|
1535
|
+
},
|
|
1536
|
+
/**
|
|
1537
|
+
* Summary projection (key display fields as JSON string: id,
|
|
1538
|
+
* displayName, status) — mirrored from the canonical Membership row
|
|
1539
|
+
* so workspace-partition queries do not need a BatchGet hop.
|
|
1540
|
+
*/
|
|
1541
|
+
summary: {
|
|
1542
|
+
type: "string",
|
|
1543
|
+
required: true
|
|
1544
|
+
},
|
|
1545
|
+
/** Version id mirrored from the canonical Membership row. */
|
|
1546
|
+
vid: {
|
|
1547
|
+
type: "string",
|
|
1548
|
+
required: true
|
|
1549
|
+
},
|
|
1550
|
+
/** Last-updated timestamp mirrored from the canonical Membership row. */
|
|
1551
|
+
lastUpdated: {
|
|
1552
|
+
type: "string",
|
|
1553
|
+
required: true
|
|
1554
|
+
},
|
|
1555
|
+
/**
|
|
1556
|
+
* Denormalized User display name — required to compose the
|
|
1557
|
+
* pattern-#2 SK (`MEMBERSHIP#<normalizedUserName>#…`). Optional on
|
|
1558
|
+
* the schema because pre-TR-024 rows may not carry a display name;
|
|
1559
|
+
* the operations layer falls back to a sentinel when missing so
|
|
1560
|
+
* the SK still has a valid shape. The TR-023 rename-cascade
|
|
1561
|
+
* pipeline rewrites the SK on a User rename.
|
|
1562
|
+
*/
|
|
1563
|
+
denormalizedUserName: {
|
|
1564
|
+
type: "string",
|
|
1565
|
+
required: false
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
indexes: {
|
|
1569
|
+
/**
|
|
1570
|
+
* Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
|
|
1571
|
+
* SK = operation-supplied. Pattern #2 uses this index — the SK
|
|
1572
|
+
* encodes the entity-type prefix (`MEMBERSHIP#…`) so a
|
|
1573
|
+
* `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`
|
|
1574
|
+
* returns every member projection for the workspace in normalized-
|
|
1575
|
+
* user-name sort order.
|
|
1576
|
+
*/
|
|
1577
|
+
record: {
|
|
1578
|
+
pk: {
|
|
1579
|
+
field: "PK",
|
|
1580
|
+
composite: ["tenantId", "workspaceId"],
|
|
1581
|
+
template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
|
|
1582
|
+
},
|
|
1583
|
+
sk: {
|
|
1584
|
+
field: "SK",
|
|
1585
|
+
casing: "none",
|
|
1586
|
+
composite: ["sk"],
|
|
1587
|
+
template: "${sk}"
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
// src/data/dynamo/entities/control/role-entity.ts
|
|
1594
|
+
var import_electrodb7 = require("electrodb");
|
|
1595
|
+
var RoleEntity = new import_electrodb7.Entity({
|
|
1596
|
+
model: {
|
|
1597
|
+
entity: "role",
|
|
1598
|
+
service: "control",
|
|
1599
|
+
version: "01"
|
|
1600
|
+
},
|
|
1601
|
+
attributes: {
|
|
1602
|
+
/** Sort key sentinel. Always "CURRENT". */
|
|
1603
|
+
sk: {
|
|
1604
|
+
type: "string",
|
|
1605
|
+
required: true,
|
|
1606
|
+
default: "CURRENT"
|
|
1607
|
+
},
|
|
1608
|
+
/** FHIR Resource.id; role id. */
|
|
1609
|
+
id: {
|
|
1610
|
+
type: "string",
|
|
1611
|
+
required: true
|
|
1612
|
+
},
|
|
1613
|
+
/** Full Role resource serialized as JSON string. */
|
|
1614
|
+
resource: {
|
|
1615
|
+
type: "string",
|
|
1616
|
+
required: true
|
|
1617
|
+
},
|
|
1618
|
+
/**
|
|
1619
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
1620
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
1621
|
+
*/
|
|
1622
|
+
summary: {
|
|
1623
|
+
type: "string",
|
|
1624
|
+
required: true
|
|
1625
|
+
},
|
|
1626
|
+
/** Version id (e.g. ULID). */
|
|
1627
|
+
vid: {
|
|
1628
|
+
type: "string",
|
|
1629
|
+
required: true
|
|
1630
|
+
},
|
|
1631
|
+
lastUpdated: {
|
|
1632
|
+
type: "string",
|
|
1633
|
+
required: true
|
|
1634
|
+
},
|
|
1635
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
1636
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
1637
|
+
gsi1sk: gsi1skAttribute,
|
|
1638
|
+
deleted: {
|
|
1639
|
+
type: "boolean",
|
|
1640
|
+
required: false
|
|
1641
|
+
},
|
|
1642
|
+
bundleId: {
|
|
1643
|
+
type: "string",
|
|
1644
|
+
required: false
|
|
1645
|
+
},
|
|
1646
|
+
msgId: {
|
|
1647
|
+
type: "string",
|
|
1648
|
+
required: false
|
|
1649
|
+
}
|
|
1650
|
+
},
|
|
1651
|
+
indexes: {
|
|
1652
|
+
/** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
|
|
1653
|
+
record: {
|
|
1654
|
+
pk: {
|
|
1655
|
+
field: "PK",
|
|
1656
|
+
composite: ["id"],
|
|
1657
|
+
template: "ROLE#ID#${id}"
|
|
1658
|
+
},
|
|
1659
|
+
sk: {
|
|
1660
|
+
field: "SK",
|
|
1661
|
+
composite: ["sk"],
|
|
1662
|
+
template: "${sk}"
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
/**
|
|
1666
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
|
|
1667
|
+
* Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
|
|
1668
|
+
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
1669
|
+
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
1670
|
+
* normalized label and ISO-8601 `T`/`Z`.
|
|
1671
|
+
*/
|
|
1672
|
+
gsi1: {
|
|
1673
|
+
index: "GSI1",
|
|
1674
|
+
pk: {
|
|
1675
|
+
field: "GSI1PK",
|
|
1676
|
+
composite: ["gsi1Shard"],
|
|
1677
|
+
template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
|
|
1678
|
+
},
|
|
1679
|
+
sk: {
|
|
1680
|
+
field: "GSI1SK",
|
|
1681
|
+
casing: "none",
|
|
1682
|
+
composite: ["gsi1sk"],
|
|
1683
|
+
template: "${gsi1sk}"
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
// src/data/dynamo/entities/control/roleassignment-entity.ts
|
|
1690
|
+
var import_electrodb8 = require("electrodb");
|
|
1691
|
+
var RoleAssignmentEntity = new import_electrodb8.Entity({
|
|
1692
|
+
model: {
|
|
1693
|
+
entity: "roleassignment",
|
|
1694
|
+
service: "control",
|
|
1695
|
+
version: "01"
|
|
1696
|
+
},
|
|
1697
|
+
attributes: {
|
|
1698
|
+
/** Sort key sentinel. Always "CURRENT". */
|
|
1699
|
+
sk: {
|
|
1700
|
+
type: "string",
|
|
1701
|
+
required: true,
|
|
1702
|
+
default: "CURRENT"
|
|
1703
|
+
},
|
|
1704
|
+
/** Tenant in which the role assignment applies (required). */
|
|
1705
|
+
tenantId: {
|
|
1706
|
+
type: "string",
|
|
1707
|
+
required: true
|
|
1708
|
+
},
|
|
1709
|
+
/** FHIR Resource.id; role assignment id. */
|
|
1710
|
+
id: {
|
|
1711
|
+
type: "string",
|
|
1712
|
+
required: true
|
|
1713
|
+
},
|
|
1714
|
+
/** Full RoleAssignment resource serialized as JSON string. */
|
|
1715
|
+
resource: {
|
|
1716
|
+
type: "string",
|
|
1717
|
+
required: true
|
|
1718
|
+
},
|
|
1719
|
+
/**
|
|
1720
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
1721
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
1722
|
+
*/
|
|
1723
|
+
summary: {
|
|
1724
|
+
type: "string",
|
|
1725
|
+
required: true
|
|
1726
|
+
},
|
|
1727
|
+
/** Version id (e.g. ULID). */
|
|
1728
|
+
vid: {
|
|
1729
|
+
type: "string",
|
|
1730
|
+
required: true
|
|
1731
|
+
},
|
|
1732
|
+
lastUpdated: {
|
|
1733
|
+
type: "string",
|
|
1734
|
+
required: true
|
|
1735
|
+
},
|
|
1736
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
1737
|
+
/**
|
|
1738
|
+
* Derived GSI1 sort key — discriminator-first
|
|
1739
|
+
* `<roleId>#<normalizedUserName>#<id>` per ADR-018 pattern #8 so a
|
|
1740
|
+
* GSI1 query partitioned on the tenant can `begins_with('<roleId>#')`
|
|
1741
|
+
* to enumerate every user assigned to a given role, sorted by user
|
|
1742
|
+
* name. Falls back to `<lastUpdated>#<id>` when either component is
|
|
1743
|
+
* missing.
|
|
1744
|
+
*/
|
|
1745
|
+
gsi1sk: roleAssignmentGsi1skAttribute,
|
|
1746
|
+
deleted: {
|
|
1747
|
+
type: "boolean",
|
|
1748
|
+
required: false
|
|
1749
|
+
},
|
|
1750
|
+
bundleId: {
|
|
1751
|
+
type: "string",
|
|
1752
|
+
required: false
|
|
1753
|
+
},
|
|
1754
|
+
msgId: {
|
|
1755
|
+
type: "string",
|
|
1756
|
+
required: false
|
|
1757
|
+
},
|
|
1758
|
+
/**
|
|
1759
|
+
* Denormalized display name of the linked Tenant, captured at row
|
|
1760
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1761
|
+
* adjacency-list user-projection SK (pattern #5 —
|
|
1762
|
+
* `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<id>`)
|
|
1763
|
+
* can be composed from a top-level field instead of digging into the
|
|
1764
|
+
* `resource` JSON. Optional on the schema so pre-TR-024 rows do not
|
|
1765
|
+
* break; the operations-layer multi-write helper (#1010) makes the
|
|
1766
|
+
* field load-bearing at write time per TR-024 rule 2 (write-time
|
|
1767
|
+
* source = canonical Tenant.displayName).
|
|
1768
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1769
|
+
*/
|
|
1770
|
+
denormalizedTenantName: {
|
|
1771
|
+
type: "string",
|
|
1772
|
+
required: false
|
|
1773
|
+
},
|
|
1774
|
+
/**
|
|
1775
|
+
* Denormalized display name of the linked User, captured at row
|
|
1776
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1777
|
+
* adjacency-list canonical-record GSI1SK (pattern #8 —
|
|
1778
|
+
* `<roleId>#<normalizedUserName>#<id>`) and workspace-projection SK
|
|
1779
|
+
* (pattern #9) can be composed from a top-level field. Optional on
|
|
1780
|
+
* the schema so pre-TR-024 rows do not break; the operations-layer
|
|
1781
|
+
* multi-write helper (#1010) makes the field load-bearing at write
|
|
1782
|
+
* time per TR-024 rule 2 (write-time source = canonical
|
|
1783
|
+
* User.displayName).
|
|
1784
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1785
|
+
*/
|
|
1786
|
+
denormalizedUserName: {
|
|
1787
|
+
type: "string",
|
|
1788
|
+
required: false
|
|
1789
|
+
},
|
|
1790
|
+
/**
|
|
1791
|
+
* Denormalized display name of the linked Role, captured at row
|
|
1792
|
+
* last-write time. Promoted to a top-level attribute so the ADR-018
|
|
1793
|
+
* adjacency-list user-projection SK (pattern #5 —
|
|
1794
|
+
* `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#…`) can be composed from
|
|
1795
|
+
* a top-level field. Optional on the schema so pre-TR-024 rows do not
|
|
1796
|
+
* break; the operations-layer multi-write helper (#1010) makes the
|
|
1797
|
+
* field load-bearing at write time per TR-024 rule 2 (write-time
|
|
1798
|
+
* source = canonical Role.displayName).
|
|
1799
|
+
* @see TR-024 — Denormalized display-name attributes
|
|
1800
|
+
*/
|
|
1801
|
+
denormalizedRoleName: {
|
|
1802
|
+
type: "string",
|
|
1803
|
+
required: false
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
indexes: {
|
|
1807
|
+
/** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
|
|
1808
|
+
record: {
|
|
1809
|
+
pk: {
|
|
1810
|
+
field: "PK",
|
|
1811
|
+
composite: ["tenantId", "id"],
|
|
1812
|
+
template: "TID#${tenantId}#ROLEASSIGNMENT#ID#${id}"
|
|
1813
|
+
},
|
|
1814
|
+
sk: {
|
|
1815
|
+
field: "SK",
|
|
1816
|
+
composite: ["sk"],
|
|
1817
|
+
template: "${sk}"
|
|
1125
1818
|
}
|
|
1126
1819
|
},
|
|
1127
1820
|
/**
|
|
1128
1821
|
* GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
|
|
1129
1822
|
* four shards. Tenant-scoped only, so `WID#-` is a sentinel.
|
|
1130
|
-
* SK is derived via `
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1823
|
+
* SK is derived via `roleAssignmentGsi1skAttribute` — composes the
|
|
1824
|
+
* discriminator-first `<roleId>#<normalizedUserName>#<id>` shape per
|
|
1825
|
+
* ADR-018 pattern #8 (users with a specific role in a tenant, sorted
|
|
1826
|
+
* by user name); falls back to `<lastUpdated>#<id>` when either
|
|
1827
|
+
* component is missing. `casing: "none"` preserves the normalized
|
|
1828
|
+
* label and ISO-8601 `T`/`Z`.
|
|
1133
1829
|
*/
|
|
1134
1830
|
gsi1: {
|
|
1135
1831
|
index: "GSI1",
|
|
@@ -1148,9 +1844,285 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
1148
1844
|
}
|
|
1149
1845
|
});
|
|
1150
1846
|
|
|
1847
|
+
// src/data/dynamo/entities/control/roleassignment-user-projection-entity.ts
|
|
1848
|
+
var import_electrodb9 = require("electrodb");
|
|
1849
|
+
var RoleAssignmentUserProjectionEntity = new import_electrodb9.Entity({
|
|
1850
|
+
model: {
|
|
1851
|
+
entity: "roleAssignmentUserProjection",
|
|
1852
|
+
service: "control",
|
|
1853
|
+
version: "01"
|
|
1854
|
+
},
|
|
1855
|
+
attributes: {
|
|
1856
|
+
/**
|
|
1857
|
+
* User partition discriminator. Renders as `USER#ID#<userId>` on the
|
|
1858
|
+
* base-table PK. Always required — the projection has no meaning
|
|
1859
|
+
* outside a user partition.
|
|
1860
|
+
*/
|
|
1861
|
+
userId: {
|
|
1862
|
+
type: "string",
|
|
1863
|
+
required: true
|
|
1864
|
+
},
|
|
1865
|
+
/**
|
|
1866
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
1867
|
+
* writer via `buildRoleAssignmentUserProjectionSk*` helpers. The
|
|
1868
|
+
* entity stores the value verbatim so the SK grammar (tenant-lane
|
|
1869
|
+
* vs workspace-lane) is owned by the operations layer, not
|
|
1870
|
+
* duplicated here.
|
|
1871
|
+
*/
|
|
1872
|
+
sk: {
|
|
1873
|
+
type: "string",
|
|
1874
|
+
required: true
|
|
1875
|
+
},
|
|
1876
|
+
/** Tenant in which the role assignment applies. Always required. */
|
|
1877
|
+
tenantId: {
|
|
1878
|
+
type: "string",
|
|
1879
|
+
required: true
|
|
1880
|
+
},
|
|
1881
|
+
/**
|
|
1882
|
+
* Workspace the role assignment scopes to. Present iff the
|
|
1883
|
+
* projection row is the workspace-level sub-lane; absent for
|
|
1884
|
+
* tenant-level sub-lane rows.
|
|
1885
|
+
*/
|
|
1886
|
+
workspaceId: {
|
|
1887
|
+
type: "string",
|
|
1888
|
+
required: false
|
|
1889
|
+
},
|
|
1890
|
+
/**
|
|
1891
|
+
* Role the assignment grants. Stored as a discriminating field so
|
|
1892
|
+
* `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#…')`
|
|
1893
|
+
* results carry the role id without a hop to the canonical row.
|
|
1894
|
+
*/
|
|
1895
|
+
roleId: {
|
|
1896
|
+
type: "string",
|
|
1897
|
+
required: true
|
|
1898
|
+
},
|
|
1899
|
+
/**
|
|
1900
|
+
* RoleAssignment canonical-record id. Stored as a discriminating
|
|
1901
|
+
* field so consumers can hydrate the canonical row via
|
|
1902
|
+
* `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
|
|
1903
|
+
* when the projection's `summary` is insufficient.
|
|
1904
|
+
*/
|
|
1905
|
+
roleAssignmentId: {
|
|
1906
|
+
type: "string",
|
|
1907
|
+
required: true
|
|
1908
|
+
},
|
|
1909
|
+
/**
|
|
1910
|
+
* Summary projection (key display fields as JSON string: id,
|
|
1911
|
+
* displayName, status) — mirrored from the canonical RoleAssignment
|
|
1912
|
+
* row so user-partition queries do not need a BatchGet hop.
|
|
1913
|
+
*/
|
|
1914
|
+
summary: {
|
|
1915
|
+
type: "string",
|
|
1916
|
+
required: true
|
|
1917
|
+
},
|
|
1918
|
+
/** Version id mirrored from the canonical RoleAssignment row. */
|
|
1919
|
+
vid: {
|
|
1920
|
+
type: "string",
|
|
1921
|
+
required: true
|
|
1922
|
+
},
|
|
1923
|
+
/** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
|
|
1924
|
+
lastUpdated: {
|
|
1925
|
+
type: "string",
|
|
1926
|
+
required: true
|
|
1927
|
+
},
|
|
1928
|
+
/**
|
|
1929
|
+
* Denormalized Tenant display name — mirrored from the canonical
|
|
1930
|
+
* RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
|
|
1931
|
+
* Optional on the schema because pre-TR-024 rows may not carry a
|
|
1932
|
+
* display name; the operations layer falls back gracefully when
|
|
1933
|
+
* missing.
|
|
1934
|
+
*/
|
|
1935
|
+
denormalizedTenantName: {
|
|
1936
|
+
type: "string",
|
|
1937
|
+
required: false
|
|
1938
|
+
},
|
|
1939
|
+
/**
|
|
1940
|
+
* Denormalized User display name — mirrored from the canonical
|
|
1941
|
+
* RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
|
|
1942
|
+
* Carried on the projection so consumers can render the user's
|
|
1943
|
+
* display name without a hop to the User record.
|
|
1944
|
+
*/
|
|
1945
|
+
denormalizedUserName: {
|
|
1946
|
+
type: "string",
|
|
1947
|
+
required: false
|
|
1948
|
+
},
|
|
1949
|
+
/**
|
|
1950
|
+
* Denormalized Role display name — required to compose the SK's
|
|
1951
|
+
* `<normalizedRoleName>` segment. Optional on the schema (pre-TR-024
|
|
1952
|
+
* rows fall back to a sentinel) but expected to be present at write
|
|
1953
|
+
* time per TR-024 rule 2 (write-time source =
|
|
1954
|
+
* canonical Role.displayName).
|
|
1955
|
+
*/
|
|
1956
|
+
denormalizedRoleName: {
|
|
1957
|
+
type: "string",
|
|
1958
|
+
required: false
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
indexes: {
|
|
1962
|
+
/**
|
|
1963
|
+
* Base table: PK = USER#ID#<userId>, SK = operation-supplied. Both
|
|
1964
|
+
* sub-lanes (tenant-level and workspace-level) use this same index —
|
|
1965
|
+
* the SK string encodes the lane discriminator
|
|
1966
|
+
* (`ROLEASSIGNMENT#TENANT#…` vs `ROLEASSIGNMENT#WORKSPACE#…`) so a
|
|
1967
|
+
* single `Query(PK = USER#ID#<userId>, SK begins_with
|
|
1968
|
+
* 'ROLEASSIGNMENT#')` returns both lanes interleaved.
|
|
1969
|
+
*/
|
|
1970
|
+
record: {
|
|
1971
|
+
pk: {
|
|
1972
|
+
field: "PK",
|
|
1973
|
+
composite: ["userId"],
|
|
1974
|
+
template: "USER#ID#${userId}"
|
|
1975
|
+
},
|
|
1976
|
+
sk: {
|
|
1977
|
+
field: "SK",
|
|
1978
|
+
casing: "none",
|
|
1979
|
+
composite: ["sk"],
|
|
1980
|
+
template: "${sk}"
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1986
|
+
// src/data/dynamo/entities/control/roleassignment-workspace-projection-entity.ts
|
|
1987
|
+
var import_electrodb10 = require("electrodb");
|
|
1988
|
+
var RoleAssignmentWorkspaceProjectionEntity = new import_electrodb10.Entity({
|
|
1989
|
+
model: {
|
|
1990
|
+
entity: "roleAssignmentWorkspaceProjection",
|
|
1991
|
+
service: "control",
|
|
1992
|
+
version: "01"
|
|
1993
|
+
},
|
|
1994
|
+
attributes: {
|
|
1995
|
+
/**
|
|
1996
|
+
* Tenant the workspace belongs to. Renders as the leading segment
|
|
1997
|
+
* of the base-table PK. Always required — the workspace partition
|
|
1998
|
+
* is tenant-scoped per ADR-011.
|
|
1999
|
+
*/
|
|
2000
|
+
tenantId: {
|
|
2001
|
+
type: "string",
|
|
2002
|
+
required: true
|
|
2003
|
+
},
|
|
2004
|
+
/**
|
|
2005
|
+
* Workspace partition discriminator. Renders as the trailing
|
|
2006
|
+
* segment of the base-table PK
|
|
2007
|
+
* (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
|
|
2008
|
+
* the projection has no meaning outside a workspace partition.
|
|
2009
|
+
*/
|
|
2010
|
+
workspaceId: {
|
|
2011
|
+
type: "string",
|
|
2012
|
+
required: true
|
|
2013
|
+
},
|
|
2014
|
+
/**
|
|
2015
|
+
* Pre-composed sort key — built by the operations-layer projection
|
|
2016
|
+
* writer via `buildRoleAssignmentWorkspaceProjectionSk`. The entity
|
|
2017
|
+
* stores the value verbatim so the SK grammar (pattern #9) is
|
|
2018
|
+
* owned by the operations layer, not duplicated here.
|
|
2019
|
+
*/
|
|
2020
|
+
sk: {
|
|
2021
|
+
type: "string",
|
|
2022
|
+
required: true
|
|
2023
|
+
},
|
|
2024
|
+
/**
|
|
2025
|
+
* User the role assignment grants the role to. Stored as a
|
|
2026
|
+
* discriminating field so consumers can hydrate the canonical User
|
|
2027
|
+
* row via `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
|
|
2028
|
+
* projection's `summary` is insufficient.
|
|
2029
|
+
*/
|
|
2030
|
+
userId: {
|
|
2031
|
+
type: "string",
|
|
2032
|
+
required: true
|
|
2033
|
+
},
|
|
2034
|
+
/**
|
|
2035
|
+
* Role the assignment grants. Stored as a discriminating field —
|
|
2036
|
+
* also rendered into the SK as the discriminator-first segment so
|
|
2037
|
+
* `begins_with('ROLEASSIGNMENT#<roleId>#')` filters one role.
|
|
2038
|
+
*/
|
|
2039
|
+
roleId: {
|
|
2040
|
+
type: "string",
|
|
2041
|
+
required: true
|
|
2042
|
+
},
|
|
2043
|
+
/**
|
|
2044
|
+
* RoleAssignment canonical-record id. Stored as a discriminating
|
|
2045
|
+
* field so consumers can hydrate the canonical row via
|
|
2046
|
+
* `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
|
|
2047
|
+
* when the projection's `summary` is insufficient.
|
|
2048
|
+
*/
|
|
2049
|
+
roleAssignmentId: {
|
|
2050
|
+
type: "string",
|
|
2051
|
+
required: true
|
|
2052
|
+
},
|
|
2053
|
+
/**
|
|
2054
|
+
* Summary projection (key display fields as JSON string: id,
|
|
2055
|
+
* displayName, status) — mirrored from the canonical RoleAssignment
|
|
2056
|
+
* row so workspace-partition queries do not need a BatchGet hop.
|
|
2057
|
+
*/
|
|
2058
|
+
summary: {
|
|
2059
|
+
type: "string",
|
|
2060
|
+
required: true
|
|
2061
|
+
},
|
|
2062
|
+
/** Version id mirrored from the canonical RoleAssignment row. */
|
|
2063
|
+
vid: {
|
|
2064
|
+
type: "string",
|
|
2065
|
+
required: true
|
|
2066
|
+
},
|
|
2067
|
+
/** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
|
|
2068
|
+
lastUpdated: {
|
|
2069
|
+
type: "string",
|
|
2070
|
+
required: true
|
|
2071
|
+
},
|
|
2072
|
+
/**
|
|
2073
|
+
* Denormalized User display name — required to compose the
|
|
2074
|
+
* pattern-#9 SK (`ROLEASSIGNMENT#<roleId>#<normalizedUserName>#…`).
|
|
2075
|
+
* Optional on the schema because pre-TR-024 rows may not carry a
|
|
2076
|
+
* display name; the operations layer falls back to a sentinel when
|
|
2077
|
+
* missing so the SK still has a valid shape. The TR-023 rename-
|
|
2078
|
+
* cascade pipeline rewrites the SK on a User rename.
|
|
2079
|
+
*/
|
|
2080
|
+
denormalizedUserName: {
|
|
2081
|
+
type: "string",
|
|
2082
|
+
required: false
|
|
2083
|
+
},
|
|
2084
|
+
/**
|
|
2085
|
+
* Denormalized Role display name — mirrored from the canonical
|
|
2086
|
+
* RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
|
|
2087
|
+
* Carried on the projection so consumers can render the role's
|
|
2088
|
+
* display name without a hop to the Role record. Not part of the
|
|
2089
|
+
* SK (pattern #9 sorts on `<normalizedUserName>`, not role name) —
|
|
2090
|
+
* a Role rename does NOT rewrite this SK.
|
|
2091
|
+
*/
|
|
2092
|
+
denormalizedRoleName: {
|
|
2093
|
+
type: "string",
|
|
2094
|
+
required: false
|
|
2095
|
+
}
|
|
2096
|
+
},
|
|
2097
|
+
indexes: {
|
|
2098
|
+
/**
|
|
2099
|
+
* Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
|
|
2100
|
+
* SK = operation-supplied. Pattern #9 uses this index — the SK
|
|
2101
|
+
* encodes the entity-type prefix and discriminator-first roleId
|
|
2102
|
+
* (`ROLEASSIGNMENT#<roleId>#…`) so
|
|
2103
|
+
* `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'ROLEASSIGNMENT#<roleId>#')`
|
|
2104
|
+
* returns every user-assignment for that role in the workspace, sorted
|
|
2105
|
+
* by normalized user name.
|
|
2106
|
+
*/
|
|
2107
|
+
record: {
|
|
2108
|
+
pk: {
|
|
2109
|
+
field: "PK",
|
|
2110
|
+
composite: ["tenantId", "workspaceId"],
|
|
2111
|
+
template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
|
|
2112
|
+
},
|
|
2113
|
+
sk: {
|
|
2114
|
+
field: "SK",
|
|
2115
|
+
casing: "none",
|
|
2116
|
+
composite: ["sk"],
|
|
2117
|
+
template: "${sk}"
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
|
|
1151
2123
|
// src/data/dynamo/entities/control/tenant-entity.ts
|
|
1152
|
-
var
|
|
1153
|
-
var TenantEntity = new
|
|
2124
|
+
var import_electrodb11 = require("electrodb");
|
|
2125
|
+
var TenantEntity = new import_electrodb11.Entity({
|
|
1154
2126
|
model: {
|
|
1155
2127
|
entity: "tenant",
|
|
1156
2128
|
service: "control",
|
|
@@ -1250,8 +2222,8 @@ var TenantEntity = new import_electrodb5.Entity({
|
|
|
1250
2222
|
});
|
|
1251
2223
|
|
|
1252
2224
|
// src/data/dynamo/entities/control/user-entity.ts
|
|
1253
|
-
var
|
|
1254
|
-
var UserEntity = new
|
|
2225
|
+
var import_electrodb12 = require("electrodb");
|
|
2226
|
+
var UserEntity = new import_electrodb12.Entity({
|
|
1255
2227
|
model: {
|
|
1256
2228
|
entity: "user",
|
|
1257
2229
|
service: "control",
|
|
@@ -1306,6 +2278,28 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
1306
2278
|
type: "boolean",
|
|
1307
2279
|
required: false
|
|
1308
2280
|
},
|
|
2281
|
+
/**
|
|
2282
|
+
* TR-022 / ADR-018 lifecycle state for the cascade pipeline.
|
|
2283
|
+
*
|
|
2284
|
+
* - `active` (or undefined) — normal, readable state.
|
|
2285
|
+
* - `deleting` — intermediate state set synchronously by the
|
|
2286
|
+
* hard-delete API entry point. The owning-delete cascade state
|
|
2287
|
+
* machine fans out from this transition (DynamoDB stream →
|
|
2288
|
+
* `control-plane.owning-delete.v1` → Step Functions). Readers MUST
|
|
2289
|
+
* short-circuit on `deleting` so partial cascades stay invisible.
|
|
2290
|
+
* - `deleted-failed` — terminal failure state set by the cascade
|
|
2291
|
+
* finalize Lambda when the cascade run fails irrecoverably.
|
|
2292
|
+
* Operators recover by re-running the cascade or by direct
|
|
2293
|
+
* intervention.
|
|
2294
|
+
*
|
|
2295
|
+
* The cascade finalize step deletes the canonical record conditional
|
|
2296
|
+
* on `lifecycleState = "deleting"`; on replay the conditional check
|
|
2297
|
+
* fails and the finalize step treats that as a no-op success.
|
|
2298
|
+
*/
|
|
2299
|
+
lifecycleState: {
|
|
2300
|
+
type: ["active", "deleting", "deleted-failed"],
|
|
2301
|
+
required: false
|
|
2302
|
+
},
|
|
1309
2303
|
bundleId: {
|
|
1310
2304
|
type: "string",
|
|
1311
2305
|
required: false
|
|
@@ -1375,8 +2369,8 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
1375
2369
|
});
|
|
1376
2370
|
|
|
1377
2371
|
// src/data/dynamo/entities/control/workspace-entity.ts
|
|
1378
|
-
var
|
|
1379
|
-
var WorkspaceEntity = new
|
|
2372
|
+
var import_electrodb13 = require("electrodb");
|
|
2373
|
+
var WorkspaceEntity = new import_electrodb13.Entity({
|
|
1380
2374
|
model: {
|
|
1381
2375
|
entity: "workspace",
|
|
1382
2376
|
service: "control",
|
|
@@ -1428,6 +2422,28 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
1428
2422
|
type: "boolean",
|
|
1429
2423
|
required: false
|
|
1430
2424
|
},
|
|
2425
|
+
/**
|
|
2426
|
+
* TR-022 / ADR-018 lifecycle state for the cascade pipeline.
|
|
2427
|
+
*
|
|
2428
|
+
* - `active` (or undefined) — normal, readable state.
|
|
2429
|
+
* - `deleting` — intermediate state set synchronously by the
|
|
2430
|
+
* hard-delete API entry point. The owning-delete cascade state
|
|
2431
|
+
* machine fans out from this transition (DynamoDB stream →
|
|
2432
|
+
* `control-plane.owning-delete.v1` → Step Functions). Readers MUST
|
|
2433
|
+
* short-circuit on `deleting` so partial cascades stay invisible.
|
|
2434
|
+
* - `deleted-failed` — terminal failure state set by the cascade
|
|
2435
|
+
* finalize Lambda when the cascade run fails irrecoverably.
|
|
2436
|
+
* Operators recover by re-running the cascade or by direct
|
|
2437
|
+
* intervention.
|
|
2438
|
+
*
|
|
2439
|
+
* The cascade finalize step deletes the canonical record conditional
|
|
2440
|
+
* on `lifecycleState = "deleting"`; on replay the conditional check
|
|
2441
|
+
* fails and the finalize step treats that as a no-op success.
|
|
2442
|
+
*/
|
|
2443
|
+
lifecycleState: {
|
|
2444
|
+
type: ["active", "deleting", "deleted-failed"],
|
|
2445
|
+
required: false
|
|
2446
|
+
},
|
|
1431
2447
|
bundleId: {
|
|
1432
2448
|
type: "string",
|
|
1433
2449
|
required: false
|
|
@@ -1478,28 +2494,36 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
1478
2494
|
// src/data/dynamo/dynamo-control-service.ts
|
|
1479
2495
|
var controlPlaneEntities = {
|
|
1480
2496
|
configuration: ConfigurationEntity,
|
|
2497
|
+
configurationUserProjection: ConfigurationUserProjectionEntity,
|
|
2498
|
+
configurationWorkspaceProjection: ConfigurationWorkspaceProjectionEntity,
|
|
1481
2499
|
membership: MembershipEntity,
|
|
2500
|
+
membershipUserProjection: MembershipUserProjectionEntity,
|
|
2501
|
+
membershipWorkspaceProjection: MembershipWorkspaceProjectionEntity,
|
|
1482
2502
|
role: RoleEntity,
|
|
1483
2503
|
roleAssignment: RoleAssignmentEntity,
|
|
2504
|
+
roleAssignmentUserProjection: RoleAssignmentUserProjectionEntity,
|
|
2505
|
+
roleAssignmentWorkspaceProjection: RoleAssignmentWorkspaceProjectionEntity,
|
|
1484
2506
|
tenant: TenantEntity,
|
|
1485
2507
|
user: UserEntity,
|
|
1486
2508
|
workspace: WorkspaceEntity
|
|
1487
2509
|
};
|
|
1488
|
-
var controlPlaneService = new
|
|
2510
|
+
var controlPlaneService = new import_electrodb14.Service(controlPlaneEntities, {
|
|
1489
2511
|
table: defaultTableName,
|
|
1490
2512
|
client: dynamoClient
|
|
1491
2513
|
});
|
|
1492
2514
|
var DynamoControlService = {
|
|
1493
|
-
entities: controlPlaneService.entities
|
|
2515
|
+
entities: controlPlaneService.entities,
|
|
2516
|
+
transaction: controlPlaneService.transaction
|
|
1494
2517
|
};
|
|
1495
2518
|
function getDynamoControlService(tableName) {
|
|
1496
2519
|
const resolved = tableName ?? defaultTableName;
|
|
1497
|
-
const service = new
|
|
2520
|
+
const service = new import_electrodb14.Service(controlPlaneEntities, {
|
|
1498
2521
|
table: resolved,
|
|
1499
2522
|
client: dynamoClient
|
|
1500
2523
|
});
|
|
1501
2524
|
return {
|
|
1502
|
-
entities: service.entities
|
|
2525
|
+
entities: service.entities,
|
|
2526
|
+
transaction: service.transaction
|
|
1503
2527
|
};
|
|
1504
2528
|
}
|
|
1505
2529
|
|