@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.
Files changed (111) hide show
  1. package/lib/chunk-23PUSHBV.mjs +24 -0
  2. package/lib/chunk-23PUSHBV.mjs.map +1 -0
  3. package/lib/{chunk-7FUAMZOF.mjs → chunk-53OHXLIL.mjs} +3 -3
  4. package/lib/chunk-6NBGYGFL.mjs +1803 -0
  5. package/lib/chunk-6NBGYGFL.mjs.map +1 -0
  6. package/lib/chunk-7RZHFI77.mjs +22 -0
  7. package/lib/chunk-7RZHFI77.mjs.map +1 -0
  8. package/lib/{chunk-7Q2IJ2J5.mjs → chunk-CUUKXDB2.mjs} +6 -6
  9. package/lib/chunk-FYHBHHWK.mjs +47 -0
  10. package/lib/chunk-FYHBHHWK.mjs.map +1 -0
  11. package/lib/{chunk-MULKGFIJ.mjs → chunk-GBDIGTNV.mjs} +165 -10
  12. package/lib/chunk-GBDIGTNV.mjs.map +1 -0
  13. package/lib/chunk-HQ67J7BP.mjs +199 -0
  14. package/lib/chunk-HQ67J7BP.mjs.map +1 -0
  15. package/lib/{chunk-AJ3G3THO.mjs → chunk-KO64HPWQ.mjs} +2 -2
  16. package/lib/{chunk-BB5MK4L3.mjs → chunk-KSFC72TT.mjs} +3 -3
  17. package/lib/{chunk-2TPJ6HOF.mjs → chunk-NZRW7ROK.mjs} +72 -54
  18. package/lib/chunk-NZRW7ROK.mjs.map +1 -0
  19. package/lib/chunk-QJDHVMKT.mjs +117 -0
  20. package/lib/chunk-QJDHVMKT.mjs.map +1 -0
  21. package/lib/{chunk-IS4VQRI4.mjs → chunk-QMBJ4VHC.mjs} +12 -47
  22. package/lib/chunk-QMBJ4VHC.mjs.map +1 -0
  23. package/lib/chunk-TRY7JGWO.mjs +16 -0
  24. package/lib/chunk-TRY7JGWO.mjs.map +1 -0
  25. package/lib/chunk-W4KR4CSL.mjs +236 -0
  26. package/lib/chunk-W4KR4CSL.mjs.map +1 -0
  27. package/lib/{chunk-AGF3RAAZ.mjs → chunk-WPCBVDFZ.mjs} +2 -2
  28. package/lib/chunk-WQWFVEVX.mjs +66 -0
  29. package/lib/chunk-WQWFVEVX.mjs.map +1 -0
  30. package/lib/{chunk-SYBADQXI.mjs → chunk-ZM4GDHHC.mjs} +77 -2
  31. package/lib/chunk-ZM4GDHHC.mjs.map +1 -0
  32. package/lib/delete-chunk.handler.d.mts +29 -0
  33. package/lib/delete-chunk.handler.d.ts +29 -0
  34. package/lib/delete-chunk.handler.js +2716 -0
  35. package/lib/delete-chunk.handler.js.map +1 -0
  36. package/lib/delete-chunk.handler.mjs +47 -0
  37. package/lib/delete-chunk.handler.mjs.map +1 -0
  38. package/lib/events-CjS-sm0W.d.mts +107 -0
  39. package/lib/events-CjS-sm0W.d.ts +107 -0
  40. package/lib/events-Da_cFgtc.d.mts +208 -0
  41. package/lib/events-Da_cFgtc.d.ts +208 -0
  42. package/lib/finalize.handler.d.mts +35 -0
  43. package/lib/finalize.handler.d.ts +35 -0
  44. package/lib/finalize.handler.js +875 -0
  45. package/lib/finalize.handler.js.map +1 -0
  46. package/lib/finalize.handler.mjs +166 -0
  47. package/lib/finalize.handler.mjs.map +1 -0
  48. package/lib/index.d.mts +189 -2
  49. package/lib/index.d.ts +500 -3
  50. package/lib/index.js +1753 -174
  51. package/lib/index.js.map +1 -1
  52. package/lib/index.mjs +571 -17
  53. package/lib/index.mjs.map +1 -1
  54. package/lib/list-chunks.handler.d.mts +28 -0
  55. package/lib/list-chunks.handler.d.ts +28 -0
  56. package/lib/list-chunks.handler.js +2746 -0
  57. package/lib/list-chunks.handler.js.map +1 -0
  58. package/lib/list-chunks.handler.mjs +54 -0
  59. package/lib/list-chunks.handler.mjs.map +1 -0
  60. package/lib/platform-deploy-bridge.handler.js +76 -1
  61. package/lib/platform-deploy-bridge.handler.js.map +1 -1
  62. package/lib/platform-deploy-bridge.handler.mjs +1 -1
  63. package/lib/pre-token-generation.handler.js +1106 -155
  64. package/lib/pre-token-generation.handler.js.map +1 -1
  65. package/lib/pre-token-generation.handler.mjs +6 -4
  66. package/lib/pre-token-generation.handler.mjs.map +1 -1
  67. package/lib/provision-default-workspace.handler.js +1529 -142
  68. package/lib/provision-default-workspace.handler.js.map +1 -1
  69. package/lib/provision-default-workspace.handler.mjs +8 -4
  70. package/lib/provision-default-workspace.handler.mjs.map +1 -1
  71. package/lib/rename-finalize.handler.d.mts +30 -0
  72. package/lib/rename-finalize.handler.d.ts +30 -0
  73. package/lib/rename-finalize.handler.js +795 -0
  74. package/lib/rename-finalize.handler.js.map +1 -0
  75. package/lib/rename-finalize.handler.mjs +90 -0
  76. package/lib/rename-finalize.handler.mjs.map +1 -0
  77. package/lib/rename-list-targets.handler.d.mts +26 -0
  78. package/lib/rename-list-targets.handler.d.ts +26 -0
  79. package/lib/rename-list-targets.handler.js +2985 -0
  80. package/lib/rename-list-targets.handler.js.map +1 -0
  81. package/lib/rename-list-targets.handler.mjs +431 -0
  82. package/lib/rename-list-targets.handler.mjs.map +1 -0
  83. package/lib/rename-rewrite-chunk.handler.d.mts +35 -0
  84. package/lib/rename-rewrite-chunk.handler.d.ts +35 -0
  85. package/lib/rename-rewrite-chunk.handler.js +2021 -0
  86. package/lib/rename-rewrite-chunk.handler.js.map +1 -0
  87. package/lib/rename-rewrite-chunk.handler.mjs +27 -0
  88. package/lib/rename-rewrite-chunk.handler.mjs.map +1 -0
  89. package/lib/rest-api-lambda.handler.js +4021 -932
  90. package/lib/rest-api-lambda.handler.js.map +1 -1
  91. package/lib/rest-api-lambda.handler.mjs +1786 -80
  92. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  93. package/lib/seed-demo-data.handler.js +1588 -124
  94. package/lib/seed-demo-data.handler.js.map +1 -1
  95. package/lib/seed-demo-data.handler.mjs +10 -6
  96. package/lib/seed-system-data.handler.js +1179 -155
  97. package/lib/seed-system-data.handler.js.map +1 -1
  98. package/lib/seed-system-data.handler.mjs +5 -4
  99. package/lib/seed-system-data.handler.mjs.map +1 -1
  100. package/package.json +3 -3
  101. package/lib/chunk-2TPJ6HOF.mjs.map +0 -1
  102. package/lib/chunk-IS4VQRI4.mjs.map +0 -1
  103. package/lib/chunk-MULKGFIJ.mjs.map +0 -1
  104. package/lib/chunk-QR5JVSCF.mjs +0 -862
  105. package/lib/chunk-QR5JVSCF.mjs.map +0 -1
  106. package/lib/chunk-SYBADQXI.mjs.map +0 -1
  107. /package/lib/{chunk-7FUAMZOF.mjs.map → chunk-53OHXLIL.mjs.map} +0 -0
  108. /package/lib/{chunk-7Q2IJ2J5.mjs.map → chunk-CUUKXDB2.mjs.map} +0 -0
  109. /package/lib/{chunk-AJ3G3THO.mjs.map → chunk-KO64HPWQ.mjs.map} +0 -0
  110. /package/lib/{chunk-BB5MK4L3.mjs.map → chunk-KSFC72TT.mjs.map} +0 -0
  111. /package/lib/{chunk-AGF3RAAZ.mjs.map → chunk-WPCBVDFZ.mjs.map} +0 -0
@@ -0,0 +1,2746 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../workflows/lib/envelope-version.js
34
+ var require_envelope_version = __commonJS({
35
+ "../workflows/lib/envelope-version.js"(exports2) {
36
+ "use strict";
37
+ Object.defineProperty(exports2, "__esModule", { value: true });
38
+ exports2.ENVELOPE_VERSION = void 0;
39
+ exports2.isSupportedEnvelopeVersion = isSupportedEnvelopeVersion;
40
+ exports2.ENVELOPE_VERSION = "1.0";
41
+ var ENVELOPE_VERSION_PATTERN = /^\d+\.\d+$/;
42
+ var MIN_SUPPORTED_MAJOR = 1;
43
+ var MAX_SUPPORTED_MAJOR = 1;
44
+ function isSupportedEnvelopeVersion(version) {
45
+ if (!ENVELOPE_VERSION_PATTERN.test(version)) {
46
+ return false;
47
+ }
48
+ const major = Number.parseInt(version.split(".")[0], 10);
49
+ return major >= MIN_SUPPORTED_MAJOR && major <= MAX_SUPPORTED_MAJOR;
50
+ }
51
+ }
52
+ });
53
+
54
+ // ../workflows/lib/envelope.js
55
+ var require_envelope = __commonJS({
56
+ "../workflows/lib/envelope.js"(exports2) {
57
+ "use strict";
58
+ Object.defineProperty(exports2, "__esModule", { value: true });
59
+ exports2.MissingActorContextError = void 0;
60
+ exports2.isWorkflowUserActor = isWorkflowUserActor;
61
+ exports2.isWorkflowSystemActor = isWorkflowSystemActor;
62
+ exports2.workflowUserActorFromClaims = workflowUserActorFromClaims;
63
+ function isWorkflowUserActor(actor) {
64
+ return actor.ohi_uid !== void 0;
65
+ }
66
+ function isWorkflowSystemActor(actor) {
67
+ return actor.system !== void 0;
68
+ }
69
+ function workflowUserActorFromClaims(claims) {
70
+ if (claims.ohi_tid === void 0 || claims.ohi_wid === void 0) {
71
+ throw new MissingActorContextError("workflowUserActorFromClaims: ohi_tid and ohi_wid are required on the workflow user-actor; the caller's JWT is missing one or both. Use a system-actor for pre-provisioning bootstrap workflows.");
72
+ }
73
+ return {
74
+ ohi_tid: claims.ohi_tid,
75
+ ohi_wid: claims.ohi_wid,
76
+ ohi_uid: claims.ohi_uid,
77
+ ohi_uname: claims.ohi_uname
78
+ };
79
+ }
80
+ var MissingActorContextError = class extends Error {
81
+ /** @param message - human-readable description of the missing claim. */
82
+ constructor(message) {
83
+ super(message);
84
+ this.name = "MissingActorContextError";
85
+ }
86
+ };
87
+ exports2.MissingActorContextError = MissingActorContextError;
88
+ }
89
+ });
90
+
91
+ // ../workflows/lib/sources.js
92
+ var require_sources = __commonJS({
93
+ "../workflows/lib/sources.js"(exports2) {
94
+ "use strict";
95
+ Object.defineProperty(exports2, "__esModule", { value: true });
96
+ exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = void 0;
97
+ exports2.OPENHI_CONTROL_SOURCE = "openhi.control";
98
+ exports2.OPENHI_DATA_SOURCE = "openhi.data";
99
+ exports2.OPENHI_OPS_SOURCE = "openhi.ops";
100
+ exports2.DEFAULT_BUS_NAME_BY_SOURCE = {
101
+ [exports2.OPENHI_CONTROL_SOURCE]: "openhi-control-event-bus",
102
+ [exports2.OPENHI_DATA_SOURCE]: "openhi-data-event-bus",
103
+ [exports2.OPENHI_OPS_SOURCE]: "openhi-ops-event-bus"
104
+ };
105
+ }
106
+ });
107
+
108
+ // ../workflows/lib/detail-types/registry.js
109
+ var require_registry = __commonJS({
110
+ "../workflows/lib/detail-types/registry.js"(exports2) {
111
+ "use strict";
112
+ Object.defineProperty(exports2, "__esModule", { value: true });
113
+ exports2.InvalidDetailTypeRegistrationError = void 0;
114
+ exports2.defineDetailType = defineDetailType;
115
+ exports2.isWellFormedDetailType = isWellFormedDetailType;
116
+ function defineDetailType(entry) {
117
+ if (!isWellFormedDetailType(entry.detailType)) {
118
+ throw new InvalidDetailTypeRegistrationError(`Detail-type "${entry.detailType}" does not match the platform-wide format <area>.<event>.v<integer>. See TR-016 \xA7Open Items #2.`);
119
+ }
120
+ return entry;
121
+ }
122
+ var DETAIL_TYPE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*\.v\d+$/;
123
+ function isWellFormedDetailType(detailType) {
124
+ return DETAIL_TYPE_PATTERN.test(detailType);
125
+ }
126
+ var InvalidDetailTypeRegistrationError = class extends Error {
127
+ /** @param message - human-readable description of the violation. */
128
+ constructor(message) {
129
+ super(message);
130
+ this.name = "InvalidDetailTypeRegistrationError";
131
+ }
132
+ };
133
+ exports2.InvalidDetailTypeRegistrationError = InvalidDetailTypeRegistrationError;
134
+ }
135
+ });
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
+
187
+ // ../workflows/lib/detail-types/platform.js
188
+ var require_platform = __commonJS({
189
+ "../workflows/lib/detail-types/platform.js"(exports2) {
190
+ "use strict";
191
+ Object.defineProperty(exports2, "__esModule", { value: true });
192
+ exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = void 0;
193
+ var sources_1 = require_sources();
194
+ var registry_1 = require_registry();
195
+ exports2.PlatformDeploymentCompletedV1 = (0, registry_1.defineDetailType)({
196
+ detailType: "platform.deployment-completed.v1",
197
+ source: sources_1.OPENHI_CONTROL_SOURCE,
198
+ dedupRequired: true
199
+ });
200
+ exports2.PlatformSystemDataSeededV1 = (0, registry_1.defineDetailType)({
201
+ detailType: "platform.system-data-seeded.v1",
202
+ source: sources_1.OPENHI_CONTROL_SOURCE,
203
+ dedupRequired: true
204
+ });
205
+ }
206
+ });
207
+
208
+ // ../workflows/lib/detail-types/index.js
209
+ var require_detail_types = __commonJS({
210
+ "../workflows/lib/detail-types/index.js"(exports2) {
211
+ "use strict";
212
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
213
+ if (k2 === void 0) k2 = k;
214
+ var desc = Object.getOwnPropertyDescriptor(m, k);
215
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
216
+ desc = { enumerable: true, get: function() {
217
+ return m[k];
218
+ } };
219
+ }
220
+ Object.defineProperty(o, k2, desc);
221
+ }) : (function(o, m, k, k2) {
222
+ if (k2 === void 0) k2 = k;
223
+ o[k2] = m[k];
224
+ }));
225
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
226
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
227
+ };
228
+ Object.defineProperty(exports2, "__esModule", { value: true });
229
+ __exportStar(require_control_plane(), exports2);
230
+ __exportStar(require_platform(), exports2);
231
+ __exportStar(require_registry(), exports2);
232
+ }
233
+ });
234
+
235
+ // ../workflows/lib/publisher.js
236
+ var require_publisher = __commonJS({
237
+ "../workflows/lib/publisher.js"(exports2) {
238
+ "use strict";
239
+ Object.defineProperty(exports2, "__esModule", { value: true });
240
+ exports2.WorkflowPublishError = void 0;
241
+ exports2.workflowsClient = workflowsClient;
242
+ exports2.publishWorkflowEvent = publishWorkflowEvent;
243
+ var node_crypto_1 = require("crypto");
244
+ var client_eventbridge_1 = require("@aws-sdk/client-eventbridge");
245
+ var envelope_version_1 = require_envelope_version();
246
+ var sources_1 = require_sources();
247
+ function workflowsClient(bridge, options = {}) {
248
+ return {
249
+ publish: (entry, payload, ctx) => publishWorkflowEvent(bridge, entry, payload, ctx, options)
250
+ };
251
+ }
252
+ async function publishWorkflowEvent(bridge, entry, payload, ctx, options = {}) {
253
+ const eventIdGenerator = options.eventIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
254
+ const correlationIdGenerator = options.correlationIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
255
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
256
+ const envelope = {
257
+ eventId: eventIdGenerator(),
258
+ attempt: 1,
259
+ correlationId: ctx.correlationId ?? correlationIdGenerator(),
260
+ causationId: ctx.causationId ?? null,
261
+ actor: ctx.actor,
262
+ occurredAt: now().toISOString(),
263
+ envelopeVersion: envelope_version_1.ENVELOPE_VERSION,
264
+ payload
265
+ };
266
+ const busName = options.busNameByPlane?.[entry.source] ?? sources_1.DEFAULT_BUS_NAME_BY_SOURCE[entry.source];
267
+ const result = await bridge.send(new client_eventbridge_1.PutEventsCommand({
268
+ Entries: [
269
+ {
270
+ EventBusName: busName,
271
+ Source: entry.source,
272
+ DetailType: entry.detailType,
273
+ Detail: JSON.stringify(envelope)
274
+ }
275
+ ]
276
+ }));
277
+ if ((result.FailedEntryCount ?? 0) > 0) {
278
+ const first = result.Entries?.[0];
279
+ throw new WorkflowPublishError(`EventBridge rejected ${entry.detailType} publish on bus ${busName}: ${first?.ErrorCode ?? "unknown"} \u2014 ${first?.ErrorMessage ?? "no error message"}`);
280
+ }
281
+ return { eventId: envelope.eventId };
282
+ }
283
+ var WorkflowPublishError = class extends Error {
284
+ /** @param message - human-readable description of the failed publish. */
285
+ constructor(message) {
286
+ super(message);
287
+ this.name = "WorkflowPublishError";
288
+ }
289
+ };
290
+ exports2.WorkflowPublishError = WorkflowPublishError;
291
+ }
292
+ });
293
+
294
+ // ../workflows/lib/consumer.js
295
+ var require_consumer = __commonJS({
296
+ "../workflows/lib/consumer.js"(exports2) {
297
+ "use strict";
298
+ Object.defineProperty(exports2, "__esModule", { value: true });
299
+ exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = void 0;
300
+ exports2.parseWorkflowEvent = parseWorkflowEvent;
301
+ var envelope_version_1 = require_envelope_version();
302
+ function parseWorkflowEvent(event, expected) {
303
+ if (event.source !== expected.source) {
304
+ throw new InvalidWorkflowEventError(`EventBridge source "${event.source}" does not match expected detail-type's source "${expected.source}".`);
305
+ }
306
+ if (event["detail-type"] !== expected.detailType) {
307
+ throw new InvalidWorkflowEventError(`EventBridge detail-type "${event["detail-type"]}" does not match expected "${expected.detailType}".`);
308
+ }
309
+ const candidate = asEnvelopeCandidate(event.detail);
310
+ if (!(0, envelope_version_1.isSupportedEnvelopeVersion)(candidate.envelopeVersion)) {
311
+ throw new UnsupportedEnvelopeVersionError(`Envelope version "${candidate.envelopeVersion}" is outside the SDK's supported range.`);
312
+ }
313
+ const envelope = {
314
+ eventId: candidate.eventId,
315
+ attempt: candidate.attempt,
316
+ correlationId: candidate.correlationId,
317
+ causationId: candidate.causationId,
318
+ actor: candidate.actor,
319
+ occurredAt: candidate.occurredAt,
320
+ envelopeVersion: candidate.envelopeVersion,
321
+ payload: candidate.payload
322
+ };
323
+ return {
324
+ envelope,
325
+ dedupKey: { eventId: envelope.eventId, attempt: envelope.attempt }
326
+ };
327
+ }
328
+ function asEnvelopeCandidate(detail) {
329
+ if (detail === null || typeof detail !== "object") {
330
+ throw new InvalidWorkflowEventError("EventBridge detail is not a non-null object.");
331
+ }
332
+ const obj = detail;
333
+ assertString(obj, "eventId");
334
+ assertPositiveInteger(obj, "attempt");
335
+ assertString(obj, "correlationId");
336
+ assertCausationId(obj);
337
+ assertActor(obj);
338
+ assertString(obj, "occurredAt");
339
+ assertString(obj, "envelopeVersion");
340
+ if (!("payload" in obj)) {
341
+ throw new InvalidWorkflowEventError("Envelope is missing required field: payload.");
342
+ }
343
+ return obj;
344
+ }
345
+ function assertString(obj, field) {
346
+ const value = obj[field];
347
+ if (typeof value !== "string" || value.length === 0) {
348
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a non-empty string.`);
349
+ }
350
+ }
351
+ function assertPositiveInteger(obj, field) {
352
+ const value = obj[field];
353
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
354
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a 1-indexed integer.`);
355
+ }
356
+ }
357
+ function assertCausationId(obj) {
358
+ if (!("causationId" in obj)) {
359
+ throw new InvalidWorkflowEventError("Envelope is missing required field: causationId.");
360
+ }
361
+ const value = obj.causationId;
362
+ if (value !== null && (typeof value !== "string" || value.length === 0)) {
363
+ throw new InvalidWorkflowEventError('Envelope field "causationId" must be a non-empty string or null.');
364
+ }
365
+ }
366
+ function assertActor(obj) {
367
+ const actor = obj.actor;
368
+ if (actor === null || typeof actor !== "object") {
369
+ throw new InvalidWorkflowEventError('Envelope field "actor" must be an object.');
370
+ }
371
+ const actorObj = actor;
372
+ const isUserActor = typeof actorObj.ohi_uid === "string" && typeof actorObj.ohi_uname === "string" && typeof actorObj.ohi_tid === "string" && typeof actorObj.ohi_wid === "string";
373
+ const isSystemActor = typeof actorObj.system === "string";
374
+ if (!isUserActor && !isSystemActor) {
375
+ throw new InvalidWorkflowEventError('Envelope field "actor" must be either a user-actor (ohi_tid, ohi_wid, ohi_uid, ohi_uname) or a system-actor ({ system: string }).');
376
+ }
377
+ }
378
+ var InvalidWorkflowEventError = class extends Error {
379
+ /** @param message - human-readable description of the validation failure. */
380
+ constructor(message) {
381
+ super(message);
382
+ this.name = "InvalidWorkflowEventError";
383
+ }
384
+ };
385
+ exports2.InvalidWorkflowEventError = InvalidWorkflowEventError;
386
+ var UnsupportedEnvelopeVersionError = class extends Error {
387
+ /** @param message - human-readable description of the unsupported version. */
388
+ constructor(message) {
389
+ super(message);
390
+ this.name = "UnsupportedEnvelopeVersionError";
391
+ }
392
+ };
393
+ exports2.UnsupportedEnvelopeVersionError = UnsupportedEnvelopeVersionError;
394
+ }
395
+ });
396
+
397
+ // ../workflows/lib/dedup/env.js
398
+ var require_env = __commonJS({
399
+ "../workflows/lib/dedup/env.js"(exports2) {
400
+ "use strict";
401
+ Object.defineProperty(exports2, "__esModule", { value: true });
402
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = void 0;
403
+ exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = "OPENHI_WORKFLOW_DEDUP_TABLE_NAME";
404
+ exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = 14 * 24 * 60 * 60;
405
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = 64;
406
+ }
407
+ });
408
+
409
+ // ../workflows/lib/dedup/workflow-dedup-client.js
410
+ var require_workflow_dedup_client = __commonJS({
411
+ "../workflows/lib/dedup/workflow-dedup-client.js"(exports2) {
412
+ "use strict";
413
+ Object.defineProperty(exports2, "__esModule", { value: true });
414
+ exports2.WorkflowDedupInvalidInputError = exports2.WorkflowDedupTableNameMissingError = void 0;
415
+ exports2.workflowDedupClient = workflowDedupClient;
416
+ exports2.recordIfAbsent = recordIfAbsent;
417
+ exports2.markFailed = markFailed;
418
+ exports2.encodeSortKey = encodeSortKey;
419
+ var client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
420
+ var env_1 = require_env();
421
+ function workflowDedupClient(dynamodb, options = {}) {
422
+ return {
423
+ recordIfAbsent: (input) => recordIfAbsent(dynamodb, input, options),
424
+ markFailed: (input) => markFailed(dynamodb, input, options)
425
+ };
426
+ }
427
+ async function recordIfAbsent(dynamodb, input, options = {}) {
428
+ assertConsumerName(input.consumerName);
429
+ assertPositiveInteger(input.attempt, "attempt");
430
+ const ttlSeconds = input.ttlSeconds ?? options.defaultTtlSeconds ?? env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
431
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
432
+ throw new WorkflowDedupInvalidInputError(`ttlSeconds must be a positive integer; got ${ttlSeconds}.`);
433
+ }
434
+ const tableName = resolveTableName(options.tableName);
435
+ const now = (options.now ?? defaultNow)();
436
+ const sk = encodeSortKey(input.eventId, input.attempt);
437
+ const expiresAt = Math.floor(now.getTime() / 1e3) + ttlSeconds;
438
+ try {
439
+ await dynamodb.send(new client_dynamodb_1.PutItemCommand({
440
+ TableName: tableName,
441
+ Item: {
442
+ consumerName: { S: input.consumerName },
443
+ sk: { S: sk },
444
+ eventId: { S: input.eventId },
445
+ attempt: { N: String(input.attempt) },
446
+ recordedAt: { S: now.toISOString() },
447
+ expiresAt: { N: String(expiresAt) }
448
+ },
449
+ ConditionExpression: "attribute_not_exists(consumerName) AND attribute_not_exists(sk)"
450
+ }));
451
+ return { recorded: true };
452
+ } catch (err) {
453
+ if (err instanceof client_dynamodb_1.ConditionalCheckFailedException) {
454
+ return { recorded: false, alreadyProcessed: true };
455
+ }
456
+ throw err;
457
+ }
458
+ }
459
+ async function markFailed(dynamodb, input, options = {}) {
460
+ assertConsumerName(input.consumerName);
461
+ assertPositiveInteger(input.attempt, "attempt");
462
+ if (input.reason.length === 0) {
463
+ throw new WorkflowDedupInvalidInputError("reason must be non-empty.");
464
+ }
465
+ const tableName = resolveTableName(options.tableName);
466
+ const now = (options.now ?? defaultNow)();
467
+ const sk = encodeSortKey(input.eventId, input.attempt);
468
+ await dynamodb.send(new client_dynamodb_1.UpdateItemCommand({
469
+ TableName: tableName,
470
+ Key: {
471
+ consumerName: { S: input.consumerName },
472
+ sk: { S: sk }
473
+ },
474
+ UpdateExpression: "SET #failed = :failed, #failureReason = :reason, #failedAt = :failedAt",
475
+ ExpressionAttributeNames: {
476
+ "#failed": "failed",
477
+ "#failureReason": "failureReason",
478
+ "#failedAt": "failedAt"
479
+ },
480
+ ExpressionAttributeValues: {
481
+ ":failed": { BOOL: true },
482
+ ":reason": { S: input.reason },
483
+ ":failedAt": { S: now.toISOString() }
484
+ }
485
+ }));
486
+ }
487
+ function encodeSortKey(eventId, attempt) {
488
+ if (eventId.length === 0) {
489
+ throw new WorkflowDedupInvalidInputError("eventId must be non-empty.");
490
+ }
491
+ return `${eventId}#${attempt}`;
492
+ }
493
+ function resolveTableName(explicit) {
494
+ const name = explicit ?? process.env[env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR];
495
+ if (!name) {
496
+ throw new WorkflowDedupTableNameMissingError(`Workflow dedup table name not set. Pass options.tableName or set ${env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR}.`);
497
+ }
498
+ return name;
499
+ }
500
+ function assertConsumerName(consumerName) {
501
+ if (consumerName.length === 0) {
502
+ throw new WorkflowDedupInvalidInputError("consumerName must be non-empty.");
503
+ }
504
+ if (consumerName.length > env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {
505
+ throw new WorkflowDedupInvalidInputError(`consumerName must be \u2264${env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`);
506
+ }
507
+ if (/\s/.test(consumerName)) {
508
+ throw new WorkflowDedupInvalidInputError("consumerName must not contain whitespace.");
509
+ }
510
+ }
511
+ function assertPositiveInteger(value, field) {
512
+ if (!Number.isInteger(value) || value < 1) {
513
+ throw new WorkflowDedupInvalidInputError(`${field} must be a 1-indexed integer; got ${value}.`);
514
+ }
515
+ }
516
+ function defaultNow() {
517
+ return /* @__PURE__ */ new Date();
518
+ }
519
+ var WorkflowDedupTableNameMissingError = class extends Error {
520
+ /** @param message - human-readable description. */
521
+ constructor(message) {
522
+ super(message);
523
+ this.name = "WorkflowDedupTableNameMissingError";
524
+ }
525
+ };
526
+ exports2.WorkflowDedupTableNameMissingError = WorkflowDedupTableNameMissingError;
527
+ var WorkflowDedupInvalidInputError = class extends Error {
528
+ /** @param message - human-readable description. */
529
+ constructor(message) {
530
+ super(message);
531
+ this.name = "WorkflowDedupInvalidInputError";
532
+ }
533
+ };
534
+ exports2.WorkflowDedupInvalidInputError = WorkflowDedupInvalidInputError;
535
+ }
536
+ });
537
+
538
+ // ../workflows/lib/dedup/index.js
539
+ var require_dedup = __commonJS({
540
+ "../workflows/lib/dedup/index.js"(exports2) {
541
+ "use strict";
542
+ Object.defineProperty(exports2, "__esModule", { value: true });
543
+ 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 = void 0;
544
+ var env_1 = require_env();
545
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
546
+ return env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
547
+ } });
548
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
549
+ return env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
550
+ } });
551
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
552
+ return env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
553
+ } });
554
+ var workflow_dedup_client_1 = require_workflow_dedup_client();
555
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
556
+ return workflow_dedup_client_1.WorkflowDedupInvalidInputError;
557
+ } });
558
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
559
+ return workflow_dedup_client_1.WorkflowDedupTableNameMissingError;
560
+ } });
561
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
562
+ return workflow_dedup_client_1.encodeSortKey;
563
+ } });
564
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
565
+ return workflow_dedup_client_1.markFailed;
566
+ } });
567
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
568
+ return workflow_dedup_client_1.recordIfAbsent;
569
+ } });
570
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
571
+ return workflow_dedup_client_1.workflowDedupClient;
572
+ } });
573
+ }
574
+ });
575
+
576
+ // ../workflows/lib/index.js
577
+ var require_lib = __commonJS({
578
+ "../workflows/lib/index.js"(exports2) {
579
+ "use strict";
580
+ Object.defineProperty(exports2, "__esModule", { value: true });
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;
582
+ var envelope_version_1 = require_envelope_version();
583
+ Object.defineProperty(exports2, "ENVELOPE_VERSION", { enumerable: true, get: function() {
584
+ return envelope_version_1.ENVELOPE_VERSION;
585
+ } });
586
+ Object.defineProperty(exports2, "isSupportedEnvelopeVersion", { enumerable: true, get: function() {
587
+ return envelope_version_1.isSupportedEnvelopeVersion;
588
+ } });
589
+ var envelope_1 = require_envelope();
590
+ Object.defineProperty(exports2, "MissingActorContextError", { enumerable: true, get: function() {
591
+ return envelope_1.MissingActorContextError;
592
+ } });
593
+ Object.defineProperty(exports2, "isWorkflowSystemActor", { enumerable: true, get: function() {
594
+ return envelope_1.isWorkflowSystemActor;
595
+ } });
596
+ Object.defineProperty(exports2, "isWorkflowUserActor", { enumerable: true, get: function() {
597
+ return envelope_1.isWorkflowUserActor;
598
+ } });
599
+ Object.defineProperty(exports2, "workflowUserActorFromClaims", { enumerable: true, get: function() {
600
+ return envelope_1.workflowUserActorFromClaims;
601
+ } });
602
+ var sources_1 = require_sources();
603
+ Object.defineProperty(exports2, "DEFAULT_BUS_NAME_BY_SOURCE", { enumerable: true, get: function() {
604
+ return sources_1.DEFAULT_BUS_NAME_BY_SOURCE;
605
+ } });
606
+ Object.defineProperty(exports2, "OPENHI_CONTROL_SOURCE", { enumerable: true, get: function() {
607
+ return sources_1.OPENHI_CONTROL_SOURCE;
608
+ } });
609
+ Object.defineProperty(exports2, "OPENHI_DATA_SOURCE", { enumerable: true, get: function() {
610
+ return sources_1.OPENHI_DATA_SOURCE;
611
+ } });
612
+ Object.defineProperty(exports2, "OPENHI_OPS_SOURCE", { enumerable: true, get: function() {
613
+ return sources_1.OPENHI_OPS_SOURCE;
614
+ } });
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
+ } });
634
+ Object.defineProperty(exports2, "InvalidDetailTypeRegistrationError", { enumerable: true, get: function() {
635
+ return detail_types_1.InvalidDetailTypeRegistrationError;
636
+ } });
637
+ Object.defineProperty(exports2, "OWNING_ENTITY_TYPE", { enumerable: true, get: function() {
638
+ return detail_types_1.OWNING_ENTITY_TYPE;
639
+ } });
640
+ Object.defineProperty(exports2, "PlatformDeploymentCompletedV1", { enumerable: true, get: function() {
641
+ return detail_types_1.PlatformDeploymentCompletedV1;
642
+ } });
643
+ Object.defineProperty(exports2, "PlatformSystemDataSeededV1", { enumerable: true, get: function() {
644
+ return detail_types_1.PlatformSystemDataSeededV1;
645
+ } });
646
+ Object.defineProperty(exports2, "RENAMABLE_ENTITY_TYPE", { enumerable: true, get: function() {
647
+ return detail_types_1.RENAMABLE_ENTITY_TYPE;
648
+ } });
649
+ Object.defineProperty(exports2, "defineDetailType", { enumerable: true, get: function() {
650
+ return detail_types_1.defineDetailType;
651
+ } });
652
+ Object.defineProperty(exports2, "isWellFormedDetailType", { enumerable: true, get: function() {
653
+ return detail_types_1.isWellFormedDetailType;
654
+ } });
655
+ var publisher_1 = require_publisher();
656
+ Object.defineProperty(exports2, "WorkflowPublishError", { enumerable: true, get: function() {
657
+ return publisher_1.WorkflowPublishError;
658
+ } });
659
+ Object.defineProperty(exports2, "publishWorkflowEvent", { enumerable: true, get: function() {
660
+ return publisher_1.publishWorkflowEvent;
661
+ } });
662
+ Object.defineProperty(exports2, "workflowsClient", { enumerable: true, get: function() {
663
+ return publisher_1.workflowsClient;
664
+ } });
665
+ var consumer_1 = require_consumer();
666
+ Object.defineProperty(exports2, "InvalidWorkflowEventError", { enumerable: true, get: function() {
667
+ return consumer_1.InvalidWorkflowEventError;
668
+ } });
669
+ Object.defineProperty(exports2, "UnsupportedEnvelopeVersionError", { enumerable: true, get: function() {
670
+ return consumer_1.UnsupportedEnvelopeVersionError;
671
+ } });
672
+ Object.defineProperty(exports2, "parseWorkflowEvent", { enumerable: true, get: function() {
673
+ return consumer_1.parseWorkflowEvent;
674
+ } });
675
+ var dedup_1 = require_dedup();
676
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
677
+ return dedup_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
678
+ } });
679
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
680
+ return dedup_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
681
+ } });
682
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
683
+ return dedup_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
684
+ } });
685
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
686
+ return dedup_1.WorkflowDedupInvalidInputError;
687
+ } });
688
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
689
+ return dedup_1.WorkflowDedupTableNameMissingError;
690
+ } });
691
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
692
+ return dedup_1.encodeSortKey;
693
+ } });
694
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
695
+ return dedup_1.markFailed;
696
+ } });
697
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
698
+ return dedup_1.recordIfAbsent;
699
+ } });
700
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
701
+ return dedup_1.workflowDedupClient;
702
+ } });
703
+ }
704
+ });
705
+
706
+ // src/workflows/control-plane/owning-delete-cascade/list-chunks.handler.ts
707
+ var list_chunks_handler_exports = {};
708
+ __export(list_chunks_handler_exports, {
709
+ handler: () => handler
710
+ });
711
+ module.exports = __toCommonJS(list_chunks_handler_exports);
712
+ var import_node_crypto = require("crypto");
713
+
714
+ // src/data/dynamo/dynamo-control-service.ts
715
+ var import_electrodb14 = require("electrodb");
716
+
717
+ // src/data/dynamo/dynamo-client.ts
718
+ var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
719
+ var defaultTableName = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
720
+ var dynamoClient = new import_client_dynamodb.DynamoDBClient({
721
+ ...process.env.MOCK_DYNAMODB_ENDPOINT && {
722
+ endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
723
+ sslEnabled: false,
724
+ region: "local"
725
+ }
726
+ });
727
+
728
+ // src/data/dynamo/entities/control/configuration-entity.ts
729
+ var import_electrodb = require("electrodb");
730
+
731
+ // src/data/dynamo/entities/control/control-entity-common.ts
732
+ var import_types = require("@openhi/types");
733
+
734
+ // src/data/dynamo/shard.ts
735
+ var SHARD_COUNT = 4;
736
+ function computeShard(id) {
737
+ let hash = 2166136261;
738
+ for (let i = 0; i < id.length; i++) {
739
+ hash ^= id.charCodeAt(i);
740
+ hash = Math.imul(hash, 16777619);
741
+ }
742
+ return (hash >>> 0) % SHARD_COUNT;
743
+ }
744
+
745
+ // src/data/dynamo/entities/control/control-entity-common.ts
746
+ var gsi1ShardAttribute = {
747
+ type: "string",
748
+ watch: ["id"],
749
+ set: (_val, item) => {
750
+ if (typeof item?.id !== "string" || item.id.length === 0) {
751
+ return void 0;
752
+ }
753
+ return String(computeShard(item.id));
754
+ }
755
+ };
756
+ var gsi1skAttribute = {
757
+ type: "string",
758
+ watch: ["resource", "lastUpdated", "id"],
759
+ set: (_val, item) => {
760
+ const id = typeof item?.id === "string" ? item.id : "";
761
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
762
+ const fallback = `${lastUpdated}#${id}`;
763
+ if (typeof item?.resource !== "string" || item.resource.length === 0) {
764
+ return fallback;
765
+ }
766
+ let parsed;
767
+ try {
768
+ parsed = JSON.parse(item.resource);
769
+ } catch {
770
+ return fallback;
771
+ }
772
+ if (!parsed || typeof parsed !== "object") return fallback;
773
+ const resourceType = parsed.resourceType;
774
+ if (typeof resourceType !== "string") return fallback;
775
+ const label = (0, import_types.extractLabel)(parsed);
776
+ return label !== void 0 ? `${label}#${id}` : fallback;
777
+ }
778
+ };
779
+ function extractRoleId(resource) {
780
+ const flat = resource.roleId;
781
+ if (typeof flat === "string" && flat.length > 0) return flat;
782
+ const role = resource.role;
783
+ if (role && typeof role === "object") {
784
+ const reference = role.reference;
785
+ if (typeof reference === "string" && reference.length > 0) {
786
+ const slash = reference.lastIndexOf("/");
787
+ const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
788
+ if (tail.length > 0) return tail;
789
+ }
790
+ }
791
+ return void 0;
792
+ }
793
+ var roleAssignmentGsi1skAttribute = {
794
+ type: "string",
795
+ watch: ["resource", "denormalizedUserName", "lastUpdated", "id"],
796
+ set: (_val, item) => {
797
+ const id = typeof item?.id === "string" ? item.id : "";
798
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
799
+ const fallback = `${lastUpdated}#${id}`;
800
+ if (typeof item?.resource !== "string" || item.resource.length === 0) {
801
+ return fallback;
802
+ }
803
+ let parsed;
804
+ try {
805
+ parsed = JSON.parse(item.resource);
806
+ } catch {
807
+ return fallback;
808
+ }
809
+ if (!parsed || typeof parsed !== "object") return fallback;
810
+ const roleId = extractRoleId(parsed);
811
+ if (roleId === void 0) return fallback;
812
+ const denormalizedUserName = typeof item.denormalizedUserName === "string" ? item.denormalizedUserName : "";
813
+ const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types.normalizeLabel)(denormalizedUserName) : "";
814
+ if (normalizedUserName.length === 0) return fallback;
815
+ return `${roleId}#${normalizedUserName}#${id}`;
816
+ }
817
+ };
818
+ var membershipGsi1skAttribute = {
819
+ type: "string",
820
+ watch: ["denormalizedUserName", "lastUpdated", "id"],
821
+ set: (_val, item) => {
822
+ const id = typeof item?.id === "string" ? item.id : "";
823
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
824
+ const fallback = `${lastUpdated}#${id}`;
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) {
828
+ return fallback;
829
+ }
830
+ return `${normalizedUserName}#${id}`;
831
+ }
832
+ };
833
+
834
+ // src/data/dynamo/entities/control/configuration-entity.ts
835
+ var ConfigurationEntity = new import_electrodb.Entity({
836
+ model: {
837
+ entity: "configuration",
838
+ service: "control",
839
+ version: "01"
840
+ },
841
+ attributes: {
842
+ /** Sort key. "CURRENT" for current version; version history in S3. */
843
+ sk: {
844
+ type: "string",
845
+ required: true,
846
+ default: "CURRENT"
847
+ },
848
+ /** Tenant scope. Use "BASELINE" when the config is baseline default (no tenant). */
849
+ tenantId: {
850
+ type: "string",
851
+ required: true,
852
+ default: "BASELINE"
853
+ },
854
+ /** Workspace scope. Use "-" when absent. */
855
+ workspaceId: {
856
+ type: "string",
857
+ required: true,
858
+ default: "-"
859
+ },
860
+ /** User scope. Use "-" when absent. */
861
+ userId: {
862
+ type: "string",
863
+ required: true,
864
+ default: "-"
865
+ },
866
+ /** Role scope. Use "-" when absent. */
867
+ roleId: {
868
+ type: "string",
869
+ required: true,
870
+ default: "-"
871
+ },
872
+ /** Config type (category), e.g. endpoints, branding, display. */
873
+ key: {
874
+ type: "string",
875
+ required: true
876
+ },
877
+ /** FHIR Resource.id; logical id in URL and for the Configuration resource. */
878
+ id: {
879
+ type: "string",
880
+ required: true
881
+ },
882
+ /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
883
+ resource: {
884
+ type: "string",
885
+ required: true
886
+ },
887
+ /**
888
+ * Summary projection (key display fields as JSON string: id, key, status).
889
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
890
+ */
891
+ summary: {
892
+ type: "string",
893
+ required: true
894
+ },
895
+ /** Version id (e.g. ULID). Tracks current version; S3 history key. */
896
+ vid: {
897
+ type: "string",
898
+ required: true
899
+ },
900
+ lastUpdated: {
901
+ type: "string",
902
+ required: true
903
+ },
904
+ gsi1Shard: gsi1ShardAttribute,
905
+ deleted: {
906
+ type: "boolean",
907
+ required: false
908
+ },
909
+ bundleId: {
910
+ type: "string",
911
+ required: false
912
+ },
913
+ msgId: {
914
+ type: "string",
915
+ required: false
916
+ }
917
+ },
918
+ indexes: {
919
+ /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */
920
+ record: {
921
+ pk: {
922
+ field: "PK",
923
+ composite: ["tenantId", "workspaceId", "userId", "roleId"],
924
+ template: "CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
925
+ },
926
+ sk: {
927
+ field: "SK",
928
+ composite: ["key", "sk"],
929
+ template: "KEY#${key}#SK#${sk}"
930
+ }
931
+ },
932
+ /**
933
+ * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a
934
+ * (tenant, workspace) across the four shards. Use for "list configs scoped to this tenant"
935
+ * (workspaceId = "-") or "list configs scoped to this workspace". Does not support
936
+ * hierarchical resolution in one query; use base table GetItem in fallback order
937
+ * (user → workspace → tenant → baseline) for that.
938
+ * SK is `<key>#<id>` — Configuration's `key` is a required entity attribute (the
939
+ * config category: endpoints, branding, display, …) and the natural sort/lookup
940
+ * dimension. `casing: "none"` preserves the literal key value.
941
+ */
942
+ gsi1: {
943
+ index: "GSI1",
944
+ pk: {
945
+ field: "GSI1PK",
946
+ composite: ["tenantId", "workspaceId", "gsi1Shard"],
947
+ template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}"
948
+ },
949
+ sk: {
950
+ field: "GSI1SK",
951
+ casing: "none",
952
+ composite: ["key", "id"],
953
+ template: "${key}#${id}"
954
+ }
955
+ }
956
+ }
957
+ });
958
+
959
+ // src/data/dynamo/entities/control/configuration-user-projection-entity.ts
960
+ var import_electrodb2 = require("electrodb");
961
+ var ConfigurationUserProjectionEntity = new import_electrodb2.Entity({
962
+ model: {
963
+ entity: "configurationUserProjection",
964
+ service: "control",
965
+ version: "01"
966
+ },
967
+ attributes: {
968
+ /**
969
+ * User partition discriminator. Renders as `USER#ID#<userId>` on the
970
+ * base-table PK. Always required — the projection has no meaning
971
+ * outside a user partition.
972
+ */
973
+ userId: {
974
+ type: "string",
975
+ required: true
976
+ },
977
+ /**
978
+ * Pre-composed sort key — built by the operations-layer projection
979
+ * writer via `buildConfigurationUserProjectionSk`. The entity stores
980
+ * the value verbatim so the SK grammar (pattern #10 user-scope) is
981
+ * owned by the operations layer, not duplicated here.
982
+ */
983
+ sk: {
984
+ type: "string",
985
+ required: true
986
+ },
987
+ /**
988
+ * Configuration canonical-record id. Stored as a discriminating
989
+ * field so consumers can hydrate the canonical row via the
990
+ * Configuration get-by-id operation when the projection's `summary`
991
+ * is insufficient.
992
+ */
993
+ configurationId: {
994
+ type: "string",
995
+ required: true
996
+ },
997
+ /**
998
+ * Tenant the Configuration is associated with. The canonical row
999
+ * keys off `(tenantId, workspaceId, userId, roleId)`; the projection
1000
+ * carries `tenantId` so consumers reconstructing the canonical PK
1001
+ * have the tenant segment without a hop.
1002
+ */
1003
+ tenantId: {
1004
+ type: "string",
1005
+ required: true
1006
+ },
1007
+ /**
1008
+ * Scope marker. Always `"user"` on this projection — recorded
1009
+ * explicitly so future scope-bearing projections (workspace,
1010
+ * tenant, role) can share filter semantics in a unified
1011
+ * cross-projection list query if one ever lands.
1012
+ */
1013
+ scope: {
1014
+ type: "string",
1015
+ required: true,
1016
+ default: "user"
1017
+ },
1018
+ /**
1019
+ * Configuration's `key` attribute (config category, e.g. endpoints,
1020
+ * branding, display). Mirrored from the canonical row so consumers
1021
+ * reading the projection get the natural display label without a
1022
+ * BatchGet hop. Doubles as the source of `<normalizedConfigName>` in
1023
+ * the SK.
1024
+ */
1025
+ displayName: {
1026
+ type: "string",
1027
+ required: false
1028
+ },
1029
+ /**
1030
+ * Summary projection (key display fields as JSON string) — mirrored
1031
+ * from the canonical Configuration row so user-partition queries do
1032
+ * not need a BatchGet hop.
1033
+ */
1034
+ summary: {
1035
+ type: "string",
1036
+ required: true
1037
+ },
1038
+ /** Version id mirrored from the canonical Configuration row. */
1039
+ vid: {
1040
+ type: "string",
1041
+ required: true
1042
+ },
1043
+ /** Last-updated timestamp mirrored from the canonical Configuration row. */
1044
+ lastUpdated: {
1045
+ type: "string",
1046
+ required: true
1047
+ }
1048
+ },
1049
+ indexes: {
1050
+ /**
1051
+ * Base table: PK = USER#ID#<userId>, SK = operation-supplied. A
1052
+ * single `Query(PK = USER#ID#<userId>, SK begins_with
1053
+ * 'CONFIGURATION#')` returns the user's user-scoped Configurations
1054
+ * sorted by `<normalizedConfigName>` (then `<configurationId>` as
1055
+ * the tiebreaker).
1056
+ */
1057
+ record: {
1058
+ pk: {
1059
+ field: "PK",
1060
+ composite: ["userId"],
1061
+ template: "USER#ID#${userId}"
1062
+ },
1063
+ sk: {
1064
+ field: "SK",
1065
+ casing: "none",
1066
+ composite: ["sk"],
1067
+ template: "${sk}"
1068
+ }
1069
+ }
1070
+ }
1071
+ });
1072
+
1073
+ // src/data/dynamo/entities/control/configuration-workspace-projection-entity.ts
1074
+ var import_electrodb3 = require("electrodb");
1075
+ var ConfigurationWorkspaceProjectionEntity = new import_electrodb3.Entity({
1076
+ model: {
1077
+ entity: "configurationWorkspaceProjection",
1078
+ service: "control",
1079
+ version: "01"
1080
+ },
1081
+ attributes: {
1082
+ /**
1083
+ * Tenant the workspace belongs to. Renders as the leading segment
1084
+ * of the base-table PK. Always required — the workspace partition
1085
+ * is tenant-scoped per ADR-011.
1086
+ */
1087
+ tenantId: {
1088
+ type: "string",
1089
+ required: true
1090
+ },
1091
+ /**
1092
+ * Workspace partition discriminator. Renders as the trailing
1093
+ * segment of the base-table PK
1094
+ * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
1095
+ * the projection has no meaning outside a workspace partition.
1096
+ */
1097
+ workspaceId: {
1098
+ type: "string",
1099
+ required: true
1100
+ },
1101
+ /**
1102
+ * Pre-composed sort key — built by the operations-layer projection
1103
+ * writer via `buildConfigurationWorkspaceProjectionSk`. The entity
1104
+ * stores the value verbatim so the SK grammar (pattern #10
1105
+ * workspace-scope) is owned by the operations layer, not
1106
+ * duplicated here.
1107
+ */
1108
+ sk: {
1109
+ type: "string",
1110
+ required: true
1111
+ },
1112
+ /**
1113
+ * Configuration canonical-record id. Stored as a discriminating
1114
+ * field so consumers can hydrate the canonical row via the
1115
+ * Configuration get-by-id operation when the projection's `summary`
1116
+ * is insufficient.
1117
+ */
1118
+ configurationId: {
1119
+ type: "string",
1120
+ required: true
1121
+ },
1122
+ /**
1123
+ * Scope marker. Always `"workspace"` on this projection — recorded
1124
+ * explicitly so future scope-bearing projections (user, tenant,
1125
+ * role) can share filter semantics in a unified cross-projection
1126
+ * list query if one ever lands.
1127
+ */
1128
+ scope: {
1129
+ type: "string",
1130
+ required: true,
1131
+ default: "workspace"
1132
+ },
1133
+ /**
1134
+ * Configuration's `key` attribute (config category, e.g. endpoints,
1135
+ * branding, display). Mirrored from the canonical row so consumers
1136
+ * reading the projection get the natural display label without a
1137
+ * BatchGet hop. Doubles as the source of `<normalizedConfigName>`
1138
+ * in the SK.
1139
+ */
1140
+ displayName: {
1141
+ type: "string",
1142
+ required: false
1143
+ },
1144
+ /**
1145
+ * Summary projection (key display fields as JSON string) — mirrored
1146
+ * from the canonical Configuration row so workspace-partition
1147
+ * queries do not need a BatchGet hop.
1148
+ */
1149
+ summary: {
1150
+ type: "string",
1151
+ required: true
1152
+ },
1153
+ /** Version id mirrored from the canonical Configuration row. */
1154
+ vid: {
1155
+ type: "string",
1156
+ required: true
1157
+ },
1158
+ /** Last-updated timestamp mirrored from the canonical Configuration row. */
1159
+ lastUpdated: {
1160
+ type: "string",
1161
+ required: true
1162
+ }
1163
+ },
1164
+ indexes: {
1165
+ /**
1166
+ * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
1167
+ * SK = operation-supplied. A single
1168
+ * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'CONFIGURATION#')`
1169
+ * returns the workspace's workspace-scoped Configurations sorted by
1170
+ * `<normalizedConfigName>` (then `<configurationId>` as the
1171
+ * tiebreaker).
1172
+ */
1173
+ record: {
1174
+ pk: {
1175
+ field: "PK",
1176
+ composite: ["tenantId", "workspaceId"],
1177
+ template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
1178
+ },
1179
+ sk: {
1180
+ field: "SK",
1181
+ casing: "none",
1182
+ composite: ["sk"],
1183
+ template: "${sk}"
1184
+ }
1185
+ }
1186
+ }
1187
+ });
1188
+
1189
+ // src/data/dynamo/entities/control/membership-entity.ts
1190
+ var import_electrodb4 = require("electrodb");
1191
+ var MembershipEntity = new import_electrodb4.Entity({
1192
+ model: {
1193
+ entity: "membership",
1194
+ service: "control",
1195
+ version: "01"
1196
+ },
1197
+ attributes: {
1198
+ /** Sort key sentinel. Always "CURRENT". */
1199
+ sk: {
1200
+ type: "string",
1201
+ required: true,
1202
+ default: "CURRENT"
1203
+ },
1204
+ /** Tenant in which the user has membership (required). */
1205
+ tenantId: {
1206
+ type: "string",
1207
+ required: true
1208
+ },
1209
+ /** FHIR Resource.id; membership id. */
1210
+ id: {
1211
+ type: "string",
1212
+ required: true
1213
+ },
1214
+ /** Full Membership resource serialized as JSON string. */
1215
+ resource: {
1216
+ type: "string",
1217
+ required: true
1218
+ },
1219
+ /**
1220
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1221
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1222
+ */
1223
+ summary: {
1224
+ type: "string",
1225
+ required: true
1226
+ },
1227
+ /** Version id (e.g. ULID). */
1228
+ vid: {
1229
+ type: "string",
1230
+ required: true
1231
+ },
1232
+ lastUpdated: {
1233
+ type: "string",
1234
+ required: true
1235
+ },
1236
+ gsi1Shard: gsi1ShardAttribute,
1237
+ /**
1238
+ * Derived GSI1 sort key — `<normalizedUserName>#<id>` per ADR-018
1239
+ * pattern #1 so a GSI1 query partitioned on the tenant range-scans
1240
+ * by user-name prefix and returns memberships sorted by user name.
1241
+ * Falls back to `<lastUpdated>#<id>` when `denormalizedUserName`
1242
+ * is missing.
1243
+ */
1244
+ gsi1sk: membershipGsi1skAttribute,
1245
+ deleted: {
1246
+ type: "boolean",
1247
+ required: false
1248
+ },
1249
+ bundleId: {
1250
+ type: "string",
1251
+ required: false
1252
+ },
1253
+ msgId: {
1254
+ type: "string",
1255
+ required: false
1256
+ },
1257
+ /**
1258
+ * Denormalized `linked-data-identity` Reference (e.g. `Practitioner/abc`).
1259
+ * Populated from the FHIR extension on the Membership resource at write
1260
+ * time so future GSIs can index data-plane identity lookups without
1261
+ * deserializing the full resource JSON. See ADR 2026-03-13-02 §6.
1262
+ */
1263
+ linkedDataIdentityRef: {
1264
+ type: "string",
1265
+ required: false
1266
+ },
1267
+ /**
1268
+ * Denormalized display name of the linked Tenant, captured at row
1269
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1270
+ * adjacency-list projection SKs (pattern #3 — `MEMBERSHIP#TENANT#<normalizedTenantName>#…`)
1271
+ * can be composed from a top-level field instead of digging into the
1272
+ * `resource` JSON. Optional on the schema so pre-TR-024 rows do not
1273
+ * break; the operations-layer multi-write helper (#1010) makes the
1274
+ * field load-bearing at write time per TR-024 rule 2 (write-time
1275
+ * source = canonical Tenant.displayName).
1276
+ * @see TR-024 — Denormalized display-name attributes
1277
+ */
1278
+ denormalizedTenantName: {
1279
+ type: "string",
1280
+ required: false
1281
+ },
1282
+ /**
1283
+ * Denormalized display name of the linked User, captured at row
1284
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1285
+ * adjacency-list canonical-record GSI1SK (pattern #1 —
1286
+ * `<normalizedUserName>#<id>`) and workspace-projection SK (pattern #2)
1287
+ * can be composed from a top-level field. Optional on the schema so
1288
+ * pre-TR-024 rows do not break; the operations-layer multi-write helper
1289
+ * (#1010) makes the field load-bearing at write time per TR-024 rule 2
1290
+ * (write-time source = canonical User.displayName).
1291
+ * @see TR-024 — Denormalized display-name attributes
1292
+ */
1293
+ denormalizedUserName: {
1294
+ type: "string",
1295
+ required: false
1296
+ }
1297
+ },
1298
+ indexes: {
1299
+ /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1300
+ record: {
1301
+ pk: {
1302
+ field: "PK",
1303
+ composite: ["tenantId", "id"],
1304
+ template: "TID#${tenantId}#MEMBERSHIP#ID#${id}"
1305
+ },
1306
+ sk: {
1307
+ field: "SK",
1308
+ composite: ["sk"],
1309
+ template: "${sk}"
1310
+ }
1311
+ },
1312
+ /**
1313
+ * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
1314
+ * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
1315
+ * SK is derived via `membershipGsi1skAttribute` — composes
1316
+ * `<normalizedUserName>#<id>` per ADR-018 pattern #1 (users in a
1317
+ * tenant, sorted by user name); falls back to `<lastUpdated>#<id>`
1318
+ * when `denormalizedUserName` is missing. `casing: "none"` preserves
1319
+ * the normalized label and ISO-8601 `T`/`Z`.
1320
+ */
1321
+ gsi1: {
1322
+ index: "GSI1",
1323
+ pk: {
1324
+ field: "GSI1PK",
1325
+ composite: ["tenantId", "gsi1Shard"],
1326
+ template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
1327
+ },
1328
+ sk: {
1329
+ field: "GSI1SK",
1330
+ casing: "none",
1331
+ composite: ["gsi1sk"],
1332
+ template: "${gsi1sk}"
1333
+ }
1334
+ }
1335
+ }
1336
+ });
1337
+
1338
+ // src/data/dynamo/entities/control/membership-user-projection-entity.ts
1339
+ var import_electrodb5 = require("electrodb");
1340
+ var MembershipUserProjectionEntity = new import_electrodb5.Entity({
1341
+ model: {
1342
+ entity: "membershipUserProjection",
1343
+ service: "control",
1344
+ version: "01"
1345
+ },
1346
+ attributes: {
1347
+ /**
1348
+ * User partition discriminator. Renders as `USER#ID#<userId>` on the
1349
+ * base-table PK. Always required — the projection has no meaning
1350
+ * outside a user partition.
1351
+ */
1352
+ userId: {
1353
+ type: "string",
1354
+ required: true
1355
+ },
1356
+ /**
1357
+ * Pre-composed sort key — built by the operations-layer projection
1358
+ * writer via `buildMembershipUserProjectionSk*` helpers. The entity
1359
+ * stores the value verbatim so the SK grammar (patterns #3 and #4)
1360
+ * is owned by the operations layer, not duplicated here.
1361
+ */
1362
+ sk: {
1363
+ type: "string",
1364
+ required: true
1365
+ },
1366
+ /** Tenant in which the membership applies. Always required. */
1367
+ tenantId: {
1368
+ type: "string",
1369
+ required: true
1370
+ },
1371
+ /**
1372
+ * Workspace the membership scopes to. Present iff the projection
1373
+ * row is a pattern-#4 workspace sub-lane row; absent for pattern-#3
1374
+ * tenant sub-lane rows.
1375
+ */
1376
+ workspaceId: {
1377
+ type: "string",
1378
+ required: false
1379
+ },
1380
+ /**
1381
+ * Membership canonical-record id. Stored as a discriminating field
1382
+ * so consumers can hydrate the canonical row via
1383
+ * `MembershipEntity.get({ tenantId, id: membershipId })` when the
1384
+ * projection's `summary` is insufficient.
1385
+ */
1386
+ membershipId: {
1387
+ type: "string",
1388
+ required: true
1389
+ },
1390
+ /**
1391
+ * Summary projection (key display fields as JSON string: id,
1392
+ * displayName, status) — mirrored from the canonical Membership row
1393
+ * so user-partition queries do not need a BatchGet hop.
1394
+ */
1395
+ summary: {
1396
+ type: "string",
1397
+ required: true
1398
+ },
1399
+ /** Version id mirrored from the canonical Membership row. */
1400
+ vid: {
1401
+ type: "string",
1402
+ required: true
1403
+ },
1404
+ /** Last-updated timestamp mirrored from the canonical Membership row. */
1405
+ lastUpdated: {
1406
+ type: "string",
1407
+ required: true
1408
+ },
1409
+ /**
1410
+ * Denormalized Tenant display name — required to compose pattern-#3
1411
+ * SK (`MEMBERSHIP#TENANT#<normalizedTenantName>#…`). Optional on the
1412
+ * schema because pre-TR-024 rows may not carry a display name; the
1413
+ * operations layer falls back gracefully when missing.
1414
+ */
1415
+ denormalizedTenantName: {
1416
+ type: "string",
1417
+ required: false
1418
+ },
1419
+ /**
1420
+ * Denormalized User display name — mirrored from the canonical
1421
+ * Membership row per TR-024 rule 3 (canonical-record symmetry).
1422
+ * Carried on the projection so consumers can render the user's
1423
+ * display name without a hop to the User record.
1424
+ */
1425
+ denormalizedUserName: {
1426
+ type: "string",
1427
+ required: false
1428
+ },
1429
+ /**
1430
+ * Denormalized Workspace display name — required to compose
1431
+ * pattern-#4 SK (`MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#…`).
1432
+ * Optional on the schema (TR-024 § Open Item #4 defers a formal
1433
+ * Workspace-rename cascade); the operations layer falls back to a
1434
+ * sentinel when missing so the SK still has a valid shape.
1435
+ */
1436
+ denormalizedWorkspaceName: {
1437
+ type: "string",
1438
+ required: false
1439
+ }
1440
+ },
1441
+ indexes: {
1442
+ /**
1443
+ * Base table: PK = USER#ID#<userId>, SK = operation-supplied.
1444
+ * Both pattern #3 and pattern #4 use this same index — the SK string
1445
+ * encodes the lane discriminator (`MEMBERSHIP#TENANT#…` vs
1446
+ * `MEMBERSHIP#WORKSPACE#…`) so a single `Query(PK = USER#ID#<userId>,
1447
+ * SK begins_with 'MEMBERSHIP#')` returns both lanes interleaved.
1448
+ */
1449
+ record: {
1450
+ pk: {
1451
+ field: "PK",
1452
+ composite: ["userId"],
1453
+ template: "USER#ID#${userId}"
1454
+ },
1455
+ sk: {
1456
+ field: "SK",
1457
+ casing: "none",
1458
+ composite: ["sk"],
1459
+ template: "${sk}"
1460
+ }
1461
+ }
1462
+ }
1463
+ });
1464
+
1465
+ // src/data/dynamo/entities/control/membership-workspace-projection-entity.ts
1466
+ var import_electrodb6 = require("electrodb");
1467
+ var MembershipWorkspaceProjectionEntity = new import_electrodb6.Entity({
1468
+ model: {
1469
+ entity: "membershipWorkspaceProjection",
1470
+ service: "control",
1471
+ version: "01"
1472
+ },
1473
+ attributes: {
1474
+ /**
1475
+ * Tenant the workspace belongs to. Renders as the leading segment
1476
+ * of the base-table PK. Always required — the workspace partition
1477
+ * is tenant-scoped per ADR-011.
1478
+ */
1479
+ tenantId: {
1480
+ type: "string",
1481
+ required: true
1482
+ },
1483
+ /**
1484
+ * Workspace partition discriminator. Renders as the trailing
1485
+ * segment of the base-table PK
1486
+ * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
1487
+ * the projection has no meaning outside a workspace partition.
1488
+ */
1489
+ workspaceId: {
1490
+ type: "string",
1491
+ required: true
1492
+ },
1493
+ /**
1494
+ * Pre-composed sort key — built by the operations-layer projection
1495
+ * writer via `buildMembershipWorkspaceProjectionSk`. The entity
1496
+ * stores the value verbatim so the SK grammar (pattern #2) is
1497
+ * owned by the operations layer, not duplicated here.
1498
+ */
1499
+ sk: {
1500
+ type: "string",
1501
+ required: true
1502
+ },
1503
+ /**
1504
+ * User the membership links. Stored as a discriminating field so
1505
+ * consumers can hydrate the canonical User row via
1506
+ * `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
1507
+ * projection's `summary` is insufficient.
1508
+ */
1509
+ userId: {
1510
+ type: "string",
1511
+ required: true
1512
+ },
1513
+ /**
1514
+ * Membership canonical-record id. Stored as a discriminating field
1515
+ * so consumers can hydrate the canonical row via
1516
+ * `MembershipEntity.get({ tenantId, id: membershipId })` when the
1517
+ * projection's `summary` is insufficient.
1518
+ */
1519
+ membershipId: {
1520
+ type: "string",
1521
+ required: true
1522
+ },
1523
+ /**
1524
+ * Summary projection (key display fields as JSON string: id,
1525
+ * displayName, status) — mirrored from the canonical Membership row
1526
+ * so workspace-partition queries do not need a BatchGet hop.
1527
+ */
1528
+ summary: {
1529
+ type: "string",
1530
+ required: true
1531
+ },
1532
+ /** Version id mirrored from the canonical Membership row. */
1533
+ vid: {
1534
+ type: "string",
1535
+ required: true
1536
+ },
1537
+ /** Last-updated timestamp mirrored from the canonical Membership row. */
1538
+ lastUpdated: {
1539
+ type: "string",
1540
+ required: true
1541
+ },
1542
+ /**
1543
+ * Denormalized User display name — required to compose the
1544
+ * pattern-#2 SK (`MEMBERSHIP#<normalizedUserName>#…`). Optional on
1545
+ * the schema because pre-TR-024 rows may not carry a display name;
1546
+ * the operations layer falls back to a sentinel when missing so
1547
+ * the SK still has a valid shape. The TR-023 rename-cascade
1548
+ * pipeline rewrites the SK on a User rename.
1549
+ */
1550
+ denormalizedUserName: {
1551
+ type: "string",
1552
+ required: false
1553
+ }
1554
+ },
1555
+ indexes: {
1556
+ /**
1557
+ * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
1558
+ * SK = operation-supplied. Pattern #2 uses this index — the SK
1559
+ * encodes the entity-type prefix (`MEMBERSHIP#…`) so a
1560
+ * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`
1561
+ * returns every member projection for the workspace in normalized-
1562
+ * user-name sort order.
1563
+ */
1564
+ record: {
1565
+ pk: {
1566
+ field: "PK",
1567
+ composite: ["tenantId", "workspaceId"],
1568
+ template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
1569
+ },
1570
+ sk: {
1571
+ field: "SK",
1572
+ casing: "none",
1573
+ composite: ["sk"],
1574
+ template: "${sk}"
1575
+ }
1576
+ }
1577
+ }
1578
+ });
1579
+
1580
+ // src/data/dynamo/entities/control/role-entity.ts
1581
+ var import_electrodb7 = require("electrodb");
1582
+ var RoleEntity = new import_electrodb7.Entity({
1583
+ model: {
1584
+ entity: "role",
1585
+ service: "control",
1586
+ version: "01"
1587
+ },
1588
+ attributes: {
1589
+ /** Sort key sentinel. Always "CURRENT". */
1590
+ sk: {
1591
+ type: "string",
1592
+ required: true,
1593
+ default: "CURRENT"
1594
+ },
1595
+ /** FHIR Resource.id; role id. */
1596
+ id: {
1597
+ type: "string",
1598
+ required: true
1599
+ },
1600
+ /** Full Role resource serialized as JSON string. */
1601
+ resource: {
1602
+ type: "string",
1603
+ required: true
1604
+ },
1605
+ /**
1606
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1607
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1608
+ */
1609
+ summary: {
1610
+ type: "string",
1611
+ required: true
1612
+ },
1613
+ /** Version id (e.g. ULID). */
1614
+ vid: {
1615
+ type: "string",
1616
+ required: true
1617
+ },
1618
+ lastUpdated: {
1619
+ type: "string",
1620
+ required: true
1621
+ },
1622
+ gsi1Shard: gsi1ShardAttribute,
1623
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1624
+ gsi1sk: gsi1skAttribute,
1625
+ deleted: {
1626
+ type: "boolean",
1627
+ required: false
1628
+ },
1629
+ bundleId: {
1630
+ type: "string",
1631
+ required: false
1632
+ },
1633
+ msgId: {
1634
+ type: "string",
1635
+ required: false
1636
+ }
1637
+ },
1638
+ indexes: {
1639
+ /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1640
+ record: {
1641
+ pk: {
1642
+ field: "PK",
1643
+ composite: ["id"],
1644
+ template: "ROLE#ID#${id}"
1645
+ },
1646
+ sk: {
1647
+ field: "SK",
1648
+ composite: ["sk"],
1649
+ template: "${sk}"
1650
+ }
1651
+ },
1652
+ /**
1653
+ * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
1654
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
1655
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
1656
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
1657
+ * normalized label and ISO-8601 `T`/`Z`.
1658
+ */
1659
+ gsi1: {
1660
+ index: "GSI1",
1661
+ pk: {
1662
+ field: "GSI1PK",
1663
+ composite: ["gsi1Shard"],
1664
+ template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
1665
+ },
1666
+ sk: {
1667
+ field: "GSI1SK",
1668
+ casing: "none",
1669
+ composite: ["gsi1sk"],
1670
+ template: "${gsi1sk}"
1671
+ }
1672
+ }
1673
+ }
1674
+ });
1675
+
1676
+ // src/data/dynamo/entities/control/roleassignment-entity.ts
1677
+ var import_electrodb8 = require("electrodb");
1678
+ var RoleAssignmentEntity = new import_electrodb8.Entity({
1679
+ model: {
1680
+ entity: "roleassignment",
1681
+ service: "control",
1682
+ version: "01"
1683
+ },
1684
+ attributes: {
1685
+ /** Sort key sentinel. Always "CURRENT". */
1686
+ sk: {
1687
+ type: "string",
1688
+ required: true,
1689
+ default: "CURRENT"
1690
+ },
1691
+ /** Tenant in which the role assignment applies (required). */
1692
+ tenantId: {
1693
+ type: "string",
1694
+ required: true
1695
+ },
1696
+ /** FHIR Resource.id; role assignment id. */
1697
+ id: {
1698
+ type: "string",
1699
+ required: true
1700
+ },
1701
+ /** Full RoleAssignment resource serialized as JSON string. */
1702
+ resource: {
1703
+ type: "string",
1704
+ required: true
1705
+ },
1706
+ /**
1707
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1708
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1709
+ */
1710
+ summary: {
1711
+ type: "string",
1712
+ required: true
1713
+ },
1714
+ /** Version id (e.g. ULID). */
1715
+ vid: {
1716
+ type: "string",
1717
+ required: true
1718
+ },
1719
+ lastUpdated: {
1720
+ type: "string",
1721
+ required: true
1722
+ },
1723
+ gsi1Shard: gsi1ShardAttribute,
1724
+ /**
1725
+ * Derived GSI1 sort key — discriminator-first
1726
+ * `<roleId>#<normalizedUserName>#<id>` per ADR-018 pattern #8 so a
1727
+ * GSI1 query partitioned on the tenant can `begins_with('<roleId>#')`
1728
+ * to enumerate every user assigned to a given role, sorted by user
1729
+ * name. Falls back to `<lastUpdated>#<id>` when either component is
1730
+ * missing.
1731
+ */
1732
+ gsi1sk: roleAssignmentGsi1skAttribute,
1733
+ deleted: {
1734
+ type: "boolean",
1735
+ required: false
1736
+ },
1737
+ bundleId: {
1738
+ type: "string",
1739
+ required: false
1740
+ },
1741
+ msgId: {
1742
+ type: "string",
1743
+ required: false
1744
+ },
1745
+ /**
1746
+ * Denormalized display name of the linked Tenant, captured at row
1747
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1748
+ * adjacency-list user-projection SK (pattern #5 —
1749
+ * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<id>`)
1750
+ * can be composed from a top-level field instead of digging into the
1751
+ * `resource` JSON. Optional on the schema so pre-TR-024 rows do not
1752
+ * break; the operations-layer multi-write helper (#1010) makes the
1753
+ * field load-bearing at write time per TR-024 rule 2 (write-time
1754
+ * source = canonical Tenant.displayName).
1755
+ * @see TR-024 — Denormalized display-name attributes
1756
+ */
1757
+ denormalizedTenantName: {
1758
+ type: "string",
1759
+ required: false
1760
+ },
1761
+ /**
1762
+ * Denormalized display name of the linked User, captured at row
1763
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1764
+ * adjacency-list canonical-record GSI1SK (pattern #8 —
1765
+ * `<roleId>#<normalizedUserName>#<id>`) and workspace-projection SK
1766
+ * (pattern #9) can be composed from a top-level field. Optional on
1767
+ * the schema so pre-TR-024 rows do not break; the operations-layer
1768
+ * multi-write helper (#1010) makes the field load-bearing at write
1769
+ * time per TR-024 rule 2 (write-time source = canonical
1770
+ * User.displayName).
1771
+ * @see TR-024 — Denormalized display-name attributes
1772
+ */
1773
+ denormalizedUserName: {
1774
+ type: "string",
1775
+ required: false
1776
+ },
1777
+ /**
1778
+ * Denormalized display name of the linked Role, captured at row
1779
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1780
+ * adjacency-list user-projection SK (pattern #5 —
1781
+ * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#…`) can be composed from
1782
+ * a top-level field. Optional on the schema so pre-TR-024 rows do not
1783
+ * break; the operations-layer multi-write helper (#1010) makes the
1784
+ * field load-bearing at write time per TR-024 rule 2 (write-time
1785
+ * source = canonical Role.displayName).
1786
+ * @see TR-024 — Denormalized display-name attributes
1787
+ */
1788
+ denormalizedRoleName: {
1789
+ type: "string",
1790
+ required: false
1791
+ }
1792
+ },
1793
+ indexes: {
1794
+ /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1795
+ record: {
1796
+ pk: {
1797
+ field: "PK",
1798
+ composite: ["tenantId", "id"],
1799
+ template: "TID#${tenantId}#ROLEASSIGNMENT#ID#${id}"
1800
+ },
1801
+ sk: {
1802
+ field: "SK",
1803
+ composite: ["sk"],
1804
+ template: "${sk}"
1805
+ }
1806
+ },
1807
+ /**
1808
+ * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
1809
+ * four shards. Tenant-scoped only, so `WID#-` is a sentinel.
1810
+ * SK is derived via `roleAssignmentGsi1skAttribute` — composes the
1811
+ * discriminator-first `<roleId>#<normalizedUserName>#<id>` shape per
1812
+ * ADR-018 pattern #8 (users with a specific role in a tenant, sorted
1813
+ * by user name); falls back to `<lastUpdated>#<id>` when either
1814
+ * component is missing. `casing: "none"` preserves the normalized
1815
+ * label and ISO-8601 `T`/`Z`.
1816
+ */
1817
+ gsi1: {
1818
+ index: "GSI1",
1819
+ pk: {
1820
+ field: "GSI1PK",
1821
+ composite: ["tenantId", "gsi1Shard"],
1822
+ template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
1823
+ },
1824
+ sk: {
1825
+ field: "GSI1SK",
1826
+ casing: "none",
1827
+ composite: ["gsi1sk"],
1828
+ template: "${gsi1sk}"
1829
+ }
1830
+ }
1831
+ }
1832
+ });
1833
+
1834
+ // src/data/dynamo/entities/control/roleassignment-user-projection-entity.ts
1835
+ var import_electrodb9 = require("electrodb");
1836
+ var RoleAssignmentUserProjectionEntity = new import_electrodb9.Entity({
1837
+ model: {
1838
+ entity: "roleAssignmentUserProjection",
1839
+ service: "control",
1840
+ version: "01"
1841
+ },
1842
+ attributes: {
1843
+ /**
1844
+ * User partition discriminator. Renders as `USER#ID#<userId>` on the
1845
+ * base-table PK. Always required — the projection has no meaning
1846
+ * outside a user partition.
1847
+ */
1848
+ userId: {
1849
+ type: "string",
1850
+ required: true
1851
+ },
1852
+ /**
1853
+ * Pre-composed sort key — built by the operations-layer projection
1854
+ * writer via `buildRoleAssignmentUserProjectionSk*` helpers. The
1855
+ * entity stores the value verbatim so the SK grammar (tenant-lane
1856
+ * vs workspace-lane) is owned by the operations layer, not
1857
+ * duplicated here.
1858
+ */
1859
+ sk: {
1860
+ type: "string",
1861
+ required: true
1862
+ },
1863
+ /** Tenant in which the role assignment applies. Always required. */
1864
+ tenantId: {
1865
+ type: "string",
1866
+ required: true
1867
+ },
1868
+ /**
1869
+ * Workspace the role assignment scopes to. Present iff the
1870
+ * projection row is the workspace-level sub-lane; absent for
1871
+ * tenant-level sub-lane rows.
1872
+ */
1873
+ workspaceId: {
1874
+ type: "string",
1875
+ required: false
1876
+ },
1877
+ /**
1878
+ * Role the assignment grants. Stored as a discriminating field so
1879
+ * `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#…')`
1880
+ * results carry the role id without a hop to the canonical row.
1881
+ */
1882
+ roleId: {
1883
+ type: "string",
1884
+ required: true
1885
+ },
1886
+ /**
1887
+ * RoleAssignment canonical-record id. Stored as a discriminating
1888
+ * field so consumers can hydrate the canonical row via
1889
+ * `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
1890
+ * when the projection's `summary` is insufficient.
1891
+ */
1892
+ roleAssignmentId: {
1893
+ type: "string",
1894
+ required: true
1895
+ },
1896
+ /**
1897
+ * Summary projection (key display fields as JSON string: id,
1898
+ * displayName, status) — mirrored from the canonical RoleAssignment
1899
+ * row so user-partition queries do not need a BatchGet hop.
1900
+ */
1901
+ summary: {
1902
+ type: "string",
1903
+ required: true
1904
+ },
1905
+ /** Version id mirrored from the canonical RoleAssignment row. */
1906
+ vid: {
1907
+ type: "string",
1908
+ required: true
1909
+ },
1910
+ /** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
1911
+ lastUpdated: {
1912
+ type: "string",
1913
+ required: true
1914
+ },
1915
+ /**
1916
+ * Denormalized Tenant display name — mirrored from the canonical
1917
+ * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
1918
+ * Optional on the schema because pre-TR-024 rows may not carry a
1919
+ * display name; the operations layer falls back gracefully when
1920
+ * missing.
1921
+ */
1922
+ denormalizedTenantName: {
1923
+ type: "string",
1924
+ required: false
1925
+ },
1926
+ /**
1927
+ * Denormalized User display name — mirrored from the canonical
1928
+ * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
1929
+ * Carried on the projection so consumers can render the user's
1930
+ * display name without a hop to the User record.
1931
+ */
1932
+ denormalizedUserName: {
1933
+ type: "string",
1934
+ required: false
1935
+ },
1936
+ /**
1937
+ * Denormalized Role display name — required to compose the SK's
1938
+ * `<normalizedRoleName>` segment. Optional on the schema (pre-TR-024
1939
+ * rows fall back to a sentinel) but expected to be present at write
1940
+ * time per TR-024 rule 2 (write-time source =
1941
+ * canonical Role.displayName).
1942
+ */
1943
+ denormalizedRoleName: {
1944
+ type: "string",
1945
+ required: false
1946
+ }
1947
+ },
1948
+ indexes: {
1949
+ /**
1950
+ * Base table: PK = USER#ID#<userId>, SK = operation-supplied. Both
1951
+ * sub-lanes (tenant-level and workspace-level) use this same index —
1952
+ * the SK string encodes the lane discriminator
1953
+ * (`ROLEASSIGNMENT#TENANT#…` vs `ROLEASSIGNMENT#WORKSPACE#…`) so a
1954
+ * single `Query(PK = USER#ID#<userId>, SK begins_with
1955
+ * 'ROLEASSIGNMENT#')` returns both lanes interleaved.
1956
+ */
1957
+ record: {
1958
+ pk: {
1959
+ field: "PK",
1960
+ composite: ["userId"],
1961
+ template: "USER#ID#${userId}"
1962
+ },
1963
+ sk: {
1964
+ field: "SK",
1965
+ casing: "none",
1966
+ composite: ["sk"],
1967
+ template: "${sk}"
1968
+ }
1969
+ }
1970
+ }
1971
+ });
1972
+
1973
+ // src/data/dynamo/entities/control/roleassignment-workspace-projection-entity.ts
1974
+ var import_electrodb10 = require("electrodb");
1975
+ var RoleAssignmentWorkspaceProjectionEntity = new import_electrodb10.Entity({
1976
+ model: {
1977
+ entity: "roleAssignmentWorkspaceProjection",
1978
+ service: "control",
1979
+ version: "01"
1980
+ },
1981
+ attributes: {
1982
+ /**
1983
+ * Tenant the workspace belongs to. Renders as the leading segment
1984
+ * of the base-table PK. Always required — the workspace partition
1985
+ * is tenant-scoped per ADR-011.
1986
+ */
1987
+ tenantId: {
1988
+ type: "string",
1989
+ required: true
1990
+ },
1991
+ /**
1992
+ * Workspace partition discriminator. Renders as the trailing
1993
+ * segment of the base-table PK
1994
+ * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
1995
+ * the projection has no meaning outside a workspace partition.
1996
+ */
1997
+ workspaceId: {
1998
+ type: "string",
1999
+ required: true
2000
+ },
2001
+ /**
2002
+ * Pre-composed sort key — built by the operations-layer projection
2003
+ * writer via `buildRoleAssignmentWorkspaceProjectionSk`. The entity
2004
+ * stores the value verbatim so the SK grammar (pattern #9) is
2005
+ * owned by the operations layer, not duplicated here.
2006
+ */
2007
+ sk: {
2008
+ type: "string",
2009
+ required: true
2010
+ },
2011
+ /**
2012
+ * User the role assignment grants the role to. Stored as a
2013
+ * discriminating field so consumers can hydrate the canonical User
2014
+ * row via `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
2015
+ * projection's `summary` is insufficient.
2016
+ */
2017
+ userId: {
2018
+ type: "string",
2019
+ required: true
2020
+ },
2021
+ /**
2022
+ * Role the assignment grants. Stored as a discriminating field —
2023
+ * also rendered into the SK as the discriminator-first segment so
2024
+ * `begins_with('ROLEASSIGNMENT#<roleId>#')` filters one role.
2025
+ */
2026
+ roleId: {
2027
+ type: "string",
2028
+ required: true
2029
+ },
2030
+ /**
2031
+ * RoleAssignment canonical-record id. Stored as a discriminating
2032
+ * field so consumers can hydrate the canonical row via
2033
+ * `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
2034
+ * when the projection's `summary` is insufficient.
2035
+ */
2036
+ roleAssignmentId: {
2037
+ type: "string",
2038
+ required: true
2039
+ },
2040
+ /**
2041
+ * Summary projection (key display fields as JSON string: id,
2042
+ * displayName, status) — mirrored from the canonical RoleAssignment
2043
+ * row so workspace-partition queries do not need a BatchGet hop.
2044
+ */
2045
+ summary: {
2046
+ type: "string",
2047
+ required: true
2048
+ },
2049
+ /** Version id mirrored from the canonical RoleAssignment row. */
2050
+ vid: {
2051
+ type: "string",
2052
+ required: true
2053
+ },
2054
+ /** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
2055
+ lastUpdated: {
2056
+ type: "string",
2057
+ required: true
2058
+ },
2059
+ /**
2060
+ * Denormalized User display name — required to compose the
2061
+ * pattern-#9 SK (`ROLEASSIGNMENT#<roleId>#<normalizedUserName>#…`).
2062
+ * Optional on the schema because pre-TR-024 rows may not carry a
2063
+ * display name; the operations layer falls back to a sentinel when
2064
+ * missing so the SK still has a valid shape. The TR-023 rename-
2065
+ * cascade pipeline rewrites the SK on a User rename.
2066
+ */
2067
+ denormalizedUserName: {
2068
+ type: "string",
2069
+ required: false
2070
+ },
2071
+ /**
2072
+ * Denormalized Role display name — mirrored from the canonical
2073
+ * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
2074
+ * Carried on the projection so consumers can render the role's
2075
+ * display name without a hop to the Role record. Not part of the
2076
+ * SK (pattern #9 sorts on `<normalizedUserName>`, not role name) —
2077
+ * a Role rename does NOT rewrite this SK.
2078
+ */
2079
+ denormalizedRoleName: {
2080
+ type: "string",
2081
+ required: false
2082
+ }
2083
+ },
2084
+ indexes: {
2085
+ /**
2086
+ * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
2087
+ * SK = operation-supplied. Pattern #9 uses this index — the SK
2088
+ * encodes the entity-type prefix and discriminator-first roleId
2089
+ * (`ROLEASSIGNMENT#<roleId>#…`) so
2090
+ * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'ROLEASSIGNMENT#<roleId>#')`
2091
+ * returns every user-assignment for that role in the workspace, sorted
2092
+ * by normalized user name.
2093
+ */
2094
+ record: {
2095
+ pk: {
2096
+ field: "PK",
2097
+ composite: ["tenantId", "workspaceId"],
2098
+ template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
2099
+ },
2100
+ sk: {
2101
+ field: "SK",
2102
+ casing: "none",
2103
+ composite: ["sk"],
2104
+ template: "${sk}"
2105
+ }
2106
+ }
2107
+ }
2108
+ });
2109
+
2110
+ // src/data/dynamo/entities/control/tenant-entity.ts
2111
+ var import_electrodb11 = require("electrodb");
2112
+ var TenantEntity = new import_electrodb11.Entity({
2113
+ model: {
2114
+ entity: "tenant",
2115
+ service: "control",
2116
+ version: "01"
2117
+ },
2118
+ attributes: {
2119
+ /** Sort key sentinel. Always "CURRENT". */
2120
+ sk: {
2121
+ type: "string",
2122
+ required: true,
2123
+ default: "CURRENT"
2124
+ },
2125
+ /** The tenant's own id (= resource id). Drives the partition key. */
2126
+ tenantId: {
2127
+ type: "string",
2128
+ required: true
2129
+ },
2130
+ /** FHIR Resource.id; logical id in URL. Equals tenantId. */
2131
+ id: {
2132
+ type: "string",
2133
+ required: true
2134
+ },
2135
+ /** Full Tenant resource serialized as JSON string. */
2136
+ resource: {
2137
+ type: "string",
2138
+ required: true
2139
+ },
2140
+ /**
2141
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2142
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2143
+ */
2144
+ summary: {
2145
+ type: "string",
2146
+ required: true
2147
+ },
2148
+ /** Version id (e.g. ULID). */
2149
+ vid: {
2150
+ type: "string",
2151
+ required: true
2152
+ },
2153
+ lastUpdated: {
2154
+ type: "string",
2155
+ required: true
2156
+ },
2157
+ gsi1Shard: gsi1ShardAttribute,
2158
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2159
+ gsi1sk: gsi1skAttribute,
2160
+ deleted: {
2161
+ type: "boolean",
2162
+ required: false
2163
+ },
2164
+ bundleId: {
2165
+ type: "string",
2166
+ required: false
2167
+ },
2168
+ msgId: {
2169
+ type: "string",
2170
+ required: false
2171
+ }
2172
+ },
2173
+ indexes: {
2174
+ /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */
2175
+ record: {
2176
+ pk: {
2177
+ field: "PK",
2178
+ composite: ["tenantId"],
2179
+ template: "TENANT#ID#${tenantId}"
2180
+ },
2181
+ sk: {
2182
+ field: "SK",
2183
+ composite: ["sk"],
2184
+ template: "${sk}"
2185
+ }
2186
+ },
2187
+ /**
2188
+ * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.
2189
+ * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`
2190
+ * sentinels precede `RT#Tenant#SHARD#<n>`. SK is derived via `gsi1skAttribute` —
2191
+ * `<normalizedName>#<id>` when the resource carries a `name`, else `<lastUpdated>#<id>`
2192
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
2193
+ */
2194
+ gsi1: {
2195
+ index: "GSI1",
2196
+ pk: {
2197
+ field: "GSI1PK",
2198
+ composite: ["gsi1Shard"],
2199
+ template: "TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}"
2200
+ },
2201
+ sk: {
2202
+ field: "GSI1SK",
2203
+ casing: "none",
2204
+ composite: ["gsi1sk"],
2205
+ template: "${gsi1sk}"
2206
+ }
2207
+ }
2208
+ }
2209
+ });
2210
+
2211
+ // src/data/dynamo/entities/control/user-entity.ts
2212
+ var import_electrodb12 = require("electrodb");
2213
+ var UserEntity = new import_electrodb12.Entity({
2214
+ model: {
2215
+ entity: "user",
2216
+ service: "control",
2217
+ version: "01"
2218
+ },
2219
+ attributes: {
2220
+ /** Sort key sentinel. Always "CURRENT". */
2221
+ sk: {
2222
+ type: "string",
2223
+ required: true,
2224
+ default: "CURRENT"
2225
+ },
2226
+ /** FHIR Resource.id; platform user id (ohi_uid). */
2227
+ id: {
2228
+ type: "string",
2229
+ required: true
2230
+ },
2231
+ /** Full User resource serialized as JSON string. */
2232
+ resource: {
2233
+ type: "string",
2234
+ required: true
2235
+ },
2236
+ /**
2237
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2238
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2239
+ */
2240
+ summary: {
2241
+ type: "string",
2242
+ required: true
2243
+ },
2244
+ /**
2245
+ * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the
2246
+ * Post Confirmation Lambda (#770) lands; required thereafter.
2247
+ */
2248
+ cognitoSub: {
2249
+ type: "string",
2250
+ required: false
2251
+ },
2252
+ /** Version id (e.g. ULID). */
2253
+ vid: {
2254
+ type: "string",
2255
+ required: true
2256
+ },
2257
+ lastUpdated: {
2258
+ type: "string",
2259
+ required: true
2260
+ },
2261
+ gsi1Shard: gsi1ShardAttribute,
2262
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2263
+ gsi1sk: gsi1skAttribute,
2264
+ deleted: {
2265
+ type: "boolean",
2266
+ required: false
2267
+ },
2268
+ /**
2269
+ * TR-022 / ADR-018 lifecycle state for the cascade pipeline.
2270
+ *
2271
+ * - `active` (or undefined) — normal, readable state.
2272
+ * - `deleting` — intermediate state set synchronously by the
2273
+ * hard-delete API entry point. The owning-delete cascade state
2274
+ * machine fans out from this transition (DynamoDB stream →
2275
+ * `control-plane.owning-delete.v1` → Step Functions). Readers MUST
2276
+ * short-circuit on `deleting` so partial cascades stay invisible.
2277
+ * - `deleted-failed` — terminal failure state set by the cascade
2278
+ * finalize Lambda when the cascade run fails irrecoverably.
2279
+ * Operators recover by re-running the cascade or by direct
2280
+ * intervention.
2281
+ *
2282
+ * The cascade finalize step deletes the canonical record conditional
2283
+ * on `lifecycleState = "deleting"`; on replay the conditional check
2284
+ * fails and the finalize step treats that as a no-op success.
2285
+ */
2286
+ lifecycleState: {
2287
+ type: ["active", "deleting", "deleted-failed"],
2288
+ required: false
2289
+ },
2290
+ bundleId: {
2291
+ type: "string",
2292
+ required: false
2293
+ },
2294
+ msgId: {
2295
+ type: "string",
2296
+ required: false
2297
+ }
2298
+ },
2299
+ indexes: {
2300
+ /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
2301
+ record: {
2302
+ pk: {
2303
+ field: "PK",
2304
+ composite: ["id"],
2305
+ template: "USER#ID#${id}"
2306
+ },
2307
+ sk: {
2308
+ field: "SK",
2309
+ composite: ["sk"],
2310
+ template: "${sk}"
2311
+ }
2312
+ },
2313
+ /**
2314
+ * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.
2315
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.
2316
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
2317
+ * extractable (string `name`/`title` via introspection), else `<lastUpdated>#<id>`
2318
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
2319
+ */
2320
+ gsi1: {
2321
+ index: "GSI1",
2322
+ pk: {
2323
+ field: "GSI1PK",
2324
+ composite: ["gsi1Shard"],
2325
+ template: "TID#-#WID#-#RT#User#SHARD#${gsi1Shard}"
2326
+ },
2327
+ sk: {
2328
+ field: "GSI1SK",
2329
+ casing: "none",
2330
+ composite: ["gsi1sk"],
2331
+ template: "${gsi1sk}"
2332
+ }
2333
+ },
2334
+ /**
2335
+ * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.
2336
+ * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are
2337
+ * not indexed.
2338
+ */
2339
+ gsi2: {
2340
+ index: "GSI2",
2341
+ condition: (attrs) => typeof attrs.cognitoSub === "string" && attrs.cognitoSub.length > 0,
2342
+ pk: {
2343
+ field: "GSI2PK",
2344
+ casing: "none",
2345
+ composite: ["cognitoSub"],
2346
+ template: "USER#SUB#${cognitoSub}"
2347
+ },
2348
+ sk: {
2349
+ field: "GSI2SK",
2350
+ casing: "none",
2351
+ composite: [],
2352
+ template: "CURRENT"
2353
+ }
2354
+ }
2355
+ }
2356
+ });
2357
+
2358
+ // src/data/dynamo/entities/control/workspace-entity.ts
2359
+ var import_electrodb13 = require("electrodb");
2360
+ var WorkspaceEntity = new import_electrodb13.Entity({
2361
+ model: {
2362
+ entity: "workspace",
2363
+ service: "control",
2364
+ version: "01"
2365
+ },
2366
+ attributes: {
2367
+ /** Sort key sentinel. Always "CURRENT". */
2368
+ sk: {
2369
+ type: "string",
2370
+ required: true,
2371
+ default: "CURRENT"
2372
+ },
2373
+ /** Tenant that contains this workspace (required). */
2374
+ tenantId: {
2375
+ type: "string",
2376
+ required: true
2377
+ },
2378
+ /** FHIR Resource.id; logical id in URL. */
2379
+ id: {
2380
+ type: "string",
2381
+ required: true
2382
+ },
2383
+ /** Full Workspace resource serialized as JSON string. */
2384
+ resource: {
2385
+ type: "string",
2386
+ required: true
2387
+ },
2388
+ /**
2389
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2390
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2391
+ */
2392
+ summary: {
2393
+ type: "string",
2394
+ required: true
2395
+ },
2396
+ /** Version id (e.g. ULID). */
2397
+ vid: {
2398
+ type: "string",
2399
+ required: true
2400
+ },
2401
+ lastUpdated: {
2402
+ type: "string",
2403
+ required: true
2404
+ },
2405
+ gsi1Shard: gsi1ShardAttribute,
2406
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2407
+ gsi1sk: gsi1skAttribute,
2408
+ deleted: {
2409
+ type: "boolean",
2410
+ required: false
2411
+ },
2412
+ /**
2413
+ * TR-022 / ADR-018 lifecycle state for the cascade pipeline.
2414
+ *
2415
+ * - `active` (or undefined) — normal, readable state.
2416
+ * - `deleting` — intermediate state set synchronously by the
2417
+ * hard-delete API entry point. The owning-delete cascade state
2418
+ * machine fans out from this transition (DynamoDB stream →
2419
+ * `control-plane.owning-delete.v1` → Step Functions). Readers MUST
2420
+ * short-circuit on `deleting` so partial cascades stay invisible.
2421
+ * - `deleted-failed` — terminal failure state set by the cascade
2422
+ * finalize Lambda when the cascade run fails irrecoverably.
2423
+ * Operators recover by re-running the cascade or by direct
2424
+ * intervention.
2425
+ *
2426
+ * The cascade finalize step deletes the canonical record conditional
2427
+ * on `lifecycleState = "deleting"`; on replay the conditional check
2428
+ * fails and the finalize step treats that as a no-op success.
2429
+ */
2430
+ lifecycleState: {
2431
+ type: ["active", "deleting", "deleted-failed"],
2432
+ required: false
2433
+ },
2434
+ bundleId: {
2435
+ type: "string",
2436
+ required: false
2437
+ },
2438
+ msgId: {
2439
+ type: "string",
2440
+ required: false
2441
+ }
2442
+ },
2443
+ indexes: {
2444
+ /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
2445
+ record: {
2446
+ pk: {
2447
+ field: "PK",
2448
+ composite: ["tenantId", "id"],
2449
+ template: "TID#${tenantId}#WORKSPACE#ID#${id}"
2450
+ },
2451
+ sk: {
2452
+ field: "SK",
2453
+ composite: ["sk"],
2454
+ template: "${sk}"
2455
+ }
2456
+ },
2457
+ /**
2458
+ * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the
2459
+ * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.
2460
+ * SK is derived via `gsi1skAttribute` — `<normalizedName>#<id>` when the resource
2461
+ * carries a `name`, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves
2462
+ * the normalized label and ISO-8601 `T`/`Z`.
2463
+ */
2464
+ gsi1: {
2465
+ index: "GSI1",
2466
+ pk: {
2467
+ field: "GSI1PK",
2468
+ composite: ["tenantId", "gsi1Shard"],
2469
+ template: "TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}"
2470
+ },
2471
+ sk: {
2472
+ field: "GSI1SK",
2473
+ casing: "none",
2474
+ composite: ["gsi1sk"],
2475
+ template: "${gsi1sk}"
2476
+ }
2477
+ }
2478
+ }
2479
+ });
2480
+
2481
+ // src/data/dynamo/dynamo-control-service.ts
2482
+ var controlPlaneEntities = {
2483
+ configuration: ConfigurationEntity,
2484
+ configurationUserProjection: ConfigurationUserProjectionEntity,
2485
+ configurationWorkspaceProjection: ConfigurationWorkspaceProjectionEntity,
2486
+ membership: MembershipEntity,
2487
+ membershipUserProjection: MembershipUserProjectionEntity,
2488
+ membershipWorkspaceProjection: MembershipWorkspaceProjectionEntity,
2489
+ role: RoleEntity,
2490
+ roleAssignment: RoleAssignmentEntity,
2491
+ roleAssignmentUserProjection: RoleAssignmentUserProjectionEntity,
2492
+ roleAssignmentWorkspaceProjection: RoleAssignmentWorkspaceProjectionEntity,
2493
+ tenant: TenantEntity,
2494
+ user: UserEntity,
2495
+ workspace: WorkspaceEntity
2496
+ };
2497
+ var controlPlaneService = new import_electrodb14.Service(controlPlaneEntities, {
2498
+ table: defaultTableName,
2499
+ client: dynamoClient
2500
+ });
2501
+ var DynamoControlService = {
2502
+ entities: controlPlaneService.entities,
2503
+ transaction: controlPlaneService.transaction
2504
+ };
2505
+ function getDynamoControlService(tableName) {
2506
+ const resolved = tableName ?? defaultTableName;
2507
+ const service = new import_electrodb14.Service(controlPlaneEntities, {
2508
+ table: resolved,
2509
+ client: dynamoClient
2510
+ });
2511
+ return {
2512
+ entities: service.entities,
2513
+ transaction: service.transaction
2514
+ };
2515
+ }
2516
+
2517
+ // src/data/operations/control/multi-write-operation.ts
2518
+ var TRANSACT_WRITE_ITEM_LIMIT = 100;
2519
+
2520
+ // src/data/operations/control/owning-delete/owning-delete-batch-delete-operation.ts
2521
+ var OWNING_DELETE_CASCADE_CHUNK_SIZE = TRANSACT_WRITE_ITEM_LIMIT;
2522
+ function chunkOwningChildRows(rows) {
2523
+ const chunks = [];
2524
+ for (let i = 0; i < rows.length; i += OWNING_DELETE_CASCADE_CHUNK_SIZE) {
2525
+ chunks.push(rows.slice(i, i + OWNING_DELETE_CASCADE_CHUNK_SIZE));
2526
+ }
2527
+ return chunks;
2528
+ }
2529
+
2530
+ // src/data/operations/control/owning-delete/owning-delete-list-child-projections-operation.ts
2531
+ var import_workflows = __toESM(require_lib());
2532
+ var DEFAULT_PAGE_SIZE = 100;
2533
+ var ENTITY_SK_PREFIXES = {
2534
+ membershipUserProjection: "MEMBERSHIP#",
2535
+ membershipWorkspaceProjection: "MEMBERSHIP#",
2536
+ roleAssignmentUserProjection: "ROLEASSIGNMENT#",
2537
+ roleAssignmentWorkspaceProjection: "ROLEASSIGNMENT#",
2538
+ configurationUserProjection: "CONFIGURATION#",
2539
+ configurationWorkspaceProjection: "CONFIGURATION#"
2540
+ };
2541
+ var ENTITIES_FOR_OWNER = {
2542
+ Workspace: [
2543
+ "membershipWorkspaceProjection",
2544
+ "roleAssignmentWorkspaceProjection",
2545
+ "configurationWorkspaceProjection"
2546
+ ],
2547
+ User: [
2548
+ "membershipUserProjection",
2549
+ "roleAssignmentUserProjection",
2550
+ "configurationUserProjection"
2551
+ ]
2552
+ };
2553
+ async function listOwningChildProjectionsOperation(params) {
2554
+ const {
2555
+ ownerType,
2556
+ ownerId,
2557
+ tenantId,
2558
+ cursors = {},
2559
+ limit = DEFAULT_PAGE_SIZE,
2560
+ tableName
2561
+ } = params;
2562
+ if (!ownerId || ownerId.length === 0) {
2563
+ throw new Error("listOwningChildProjectionsOperation: ownerId is required");
2564
+ }
2565
+ if (ownerType === import_workflows.OWNING_ENTITY_TYPE.Workspace && (!tenantId || tenantId.length === 0)) {
2566
+ throw new Error(
2567
+ "listOwningChildProjectionsOperation: tenantId is required for Workspace owners"
2568
+ );
2569
+ }
2570
+ const service = getDynamoControlService(tableName);
2571
+ const entities = ENTITIES_FOR_OWNER[ownerType];
2572
+ const nextCursors = {};
2573
+ const items = [];
2574
+ for (const entityName of entities) {
2575
+ const priorCursor = cursors[entityName];
2576
+ if (priorCursor === null) {
2577
+ nextCursors[entityName] = null;
2578
+ continue;
2579
+ }
2580
+ const prefix = ENTITY_SK_PREFIXES[entityName];
2581
+ const page = await queryProjectionEntity({
2582
+ service,
2583
+ entityName,
2584
+ ownerType,
2585
+ ownerId,
2586
+ tenantId,
2587
+ prefix,
2588
+ cursor: priorCursor ?? null,
2589
+ limit
2590
+ });
2591
+ for (const row of page.items) {
2592
+ items.push(row);
2593
+ }
2594
+ nextCursors[entityName] = page.cursor;
2595
+ }
2596
+ const exhausted = Object.values(nextCursors).every((value) => value === null);
2597
+ return { items, cursors: nextCursors, exhausted };
2598
+ }
2599
+ async function queryProjectionEntity(params) {
2600
+ const {
2601
+ service,
2602
+ entityName,
2603
+ ownerType,
2604
+ ownerId,
2605
+ tenantId,
2606
+ prefix,
2607
+ cursor,
2608
+ limit
2609
+ } = params;
2610
+ const goOptions = { cursor, limit };
2611
+ switch (entityName) {
2612
+ case "membershipUserProjection": {
2613
+ const result = await service.entities.membershipUserProjection.query.record({ userId: ownerId }).begins({ sk: prefix }).go(goOptions);
2614
+ const items = (result.data ?? []).map(
2615
+ (row) => ({
2616
+ entity: entityName,
2617
+ key: { userId: row.userId, sk: row.sk }
2618
+ })
2619
+ );
2620
+ return { items, cursor: result.cursor ?? null };
2621
+ }
2622
+ case "membershipWorkspaceProjection": {
2623
+ if (ownerType !== import_workflows.OWNING_ENTITY_TYPE.Workspace || !tenantId) {
2624
+ return { items: [], cursor: null };
2625
+ }
2626
+ const result = await service.entities.membershipWorkspaceProjection.query.record({ tenantId, workspaceId: ownerId }).begins({ sk: prefix }).go(goOptions);
2627
+ const items = (result.data ?? []).map(
2628
+ (row) => ({
2629
+ entity: entityName,
2630
+ key: {
2631
+ tenantId: row.tenantId,
2632
+ workspaceId: row.workspaceId,
2633
+ sk: row.sk
2634
+ }
2635
+ })
2636
+ );
2637
+ return { items, cursor: result.cursor ?? null };
2638
+ }
2639
+ case "roleAssignmentUserProjection": {
2640
+ const result = await service.entities.roleAssignmentUserProjection.query.record({ userId: ownerId }).begins({ sk: prefix }).go(goOptions);
2641
+ const items = (result.data ?? []).map(
2642
+ (row) => ({
2643
+ entity: entityName,
2644
+ key: { userId: row.userId, sk: row.sk }
2645
+ })
2646
+ );
2647
+ return { items, cursor: result.cursor ?? null };
2648
+ }
2649
+ case "roleAssignmentWorkspaceProjection": {
2650
+ if (ownerType !== import_workflows.OWNING_ENTITY_TYPE.Workspace || !tenantId) {
2651
+ return { items: [], cursor: null };
2652
+ }
2653
+ const result = await service.entities.roleAssignmentWorkspaceProjection.query.record({ tenantId, workspaceId: ownerId }).begins({ sk: prefix }).go(goOptions);
2654
+ const items = (result.data ?? []).map(
2655
+ (row) => ({
2656
+ entity: entityName,
2657
+ key: {
2658
+ tenantId: row.tenantId,
2659
+ workspaceId: row.workspaceId,
2660
+ sk: row.sk
2661
+ }
2662
+ })
2663
+ );
2664
+ return { items, cursor: result.cursor ?? null };
2665
+ }
2666
+ case "configurationUserProjection": {
2667
+ const result = await service.entities.configurationUserProjection.query.record({ userId: ownerId }).begins({ sk: prefix }).go(goOptions);
2668
+ const items = (result.data ?? []).map(
2669
+ (row) => ({
2670
+ entity: entityName,
2671
+ key: { userId: row.userId, sk: row.sk }
2672
+ })
2673
+ );
2674
+ return { items, cursor: result.cursor ?? null };
2675
+ }
2676
+ case "configurationWorkspaceProjection": {
2677
+ if (ownerType !== import_workflows.OWNING_ENTITY_TYPE.Workspace || !tenantId) {
2678
+ return { items: [], cursor: null };
2679
+ }
2680
+ const result = await service.entities.configurationWorkspaceProjection.query.record({ tenantId, workspaceId: ownerId }).begins({ sk: prefix }).go(goOptions);
2681
+ const items = (result.data ?? []).map(
2682
+ (row) => ({
2683
+ entity: entityName,
2684
+ key: {
2685
+ tenantId: row.tenantId,
2686
+ workspaceId: row.workspaceId,
2687
+ sk: row.sk
2688
+ }
2689
+ })
2690
+ );
2691
+ return { items, cursor: result.cursor ?? null };
2692
+ }
2693
+ default: {
2694
+ const exhaustive = entityName;
2695
+ throw new Error(
2696
+ `listOwningChildProjectionsOperation: unsupported entity '${String(
2697
+ exhaustive
2698
+ )}'`
2699
+ );
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ // src/workflows/control-plane/owning-delete-cascade/list-chunks.handler.ts
2705
+ var handler = async (input) => {
2706
+ const cursors = {};
2707
+ if (input.cursors) {
2708
+ for (const [key, value] of Object.entries(input.cursors)) {
2709
+ cursors[key] = value;
2710
+ }
2711
+ }
2712
+ const page = await listOwningChildProjectionsOperation({
2713
+ ownerType: input.ownerType,
2714
+ ownerId: input.ownerId,
2715
+ tenantId: input.tenantId,
2716
+ cursors
2717
+ });
2718
+ const chunks = chunkOwningChildRows(page.items).map(
2719
+ (rows) => ({
2720
+ ownerType: input.ownerType,
2721
+ ownerId: input.ownerId,
2722
+ tenantId: input.tenantId,
2723
+ rows: rows.map((row) => ({ entity: row.entity, key: row.key })),
2724
+ chunkToken: (0, import_node_crypto.randomUUID)()
2725
+ })
2726
+ );
2727
+ const priorRemoved = input.projectionsRemoved ?? 0;
2728
+ const priorChunks = input.chunkCount ?? 0;
2729
+ const projectionsRemoved = priorRemoved + page.items.length;
2730
+ const chunkCount = priorChunks + chunks.length;
2731
+ return {
2732
+ ownerType: input.ownerType,
2733
+ ownerId: input.ownerId,
2734
+ tenantId: input.tenantId,
2735
+ cursors: page.cursors,
2736
+ chunks,
2737
+ exhausted: page.exhausted,
2738
+ projectionsRemoved,
2739
+ chunkCount
2740
+ };
2741
+ };
2742
+ // Annotate the CommonJS export names for ESM import in node:
2743
+ 0 && (module.exports = {
2744
+ handler
2745
+ });
2746
+ //# sourceMappingURL=list-chunks.handler.js.map