@openhi/constructs 0.0.105 → 0.0.106

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 (73) hide show
  1. package/lib/chunk-AGF3RAAZ.mjs +20 -0
  2. package/lib/chunk-AGF3RAAZ.mjs.map +1 -0
  3. package/lib/{chunk-BXEG7IOZ.mjs → chunk-AO3E22CS.mjs} +2 -2
  4. package/lib/{chunk-WNUH2WDZ.mjs → chunk-CHPEQRXU.mjs} +2 -2
  5. package/lib/chunk-JUNL76HF.mjs +428 -0
  6. package/lib/chunk-JUNL76HF.mjs.map +1 -0
  7. package/lib/chunk-L6UAP4KP.mjs +27 -0
  8. package/lib/chunk-L6UAP4KP.mjs.map +1 -0
  9. package/lib/{chunk-3QS3WKRC.mjs → chunk-LZOMFHX3.mjs} +9 -2
  10. package/lib/chunk-QMIOLLAS.mjs +531 -0
  11. package/lib/chunk-QMIOLLAS.mjs.map +1 -0
  12. package/lib/chunk-SYBADQXI.mjs +607 -0
  13. package/lib/chunk-SYBADQXI.mjs.map +1 -0
  14. package/lib/chunk-VXX4I3EF.mjs +19 -0
  15. package/lib/chunk-VXX4I3EF.mjs.map +1 -0
  16. package/lib/{chunk-36YCDLLA.mjs → chunk-VYDIGFIX.mjs} +75 -481
  17. package/lib/chunk-VYDIGFIX.mjs.map +1 -0
  18. package/lib/chunk-YU2HRNUP.mjs +33 -0
  19. package/lib/chunk-YU2HRNUP.mjs.map +1 -0
  20. package/lib/chunk-YZZDUJHI.mjs +37 -0
  21. package/lib/chunk-YZZDUJHI.mjs.map +1 -0
  22. package/lib/cors-options-lambda.handler.mjs +1 -1
  23. package/lib/data-store-postgres-replication.handler.mjs +1 -1
  24. package/lib/events-BfrkMoBD.d.mts +44 -0
  25. package/lib/events-BfrkMoBD.d.ts +44 -0
  26. package/lib/events-DGep6C7w.d.mts +207 -0
  27. package/lib/events-DGep6C7w.d.ts +207 -0
  28. package/lib/firehose-archive-transform.handler.mjs +1 -1
  29. package/lib/index.d.mts +417 -9
  30. package/lib/index.d.ts +663 -10
  31. package/lib/index.js +2400 -111
  32. package/lib/index.js.map +1 -1
  33. package/lib/index.mjs +781 -104
  34. package/lib/index.mjs.map +1 -1
  35. package/lib/openhi-context-CaBH8SFo.d.mts +39 -0
  36. package/lib/openhi-context-CaBH8SFo.d.ts +39 -0
  37. package/lib/platform-deploy-bridge.handler.d.mts +14 -0
  38. package/lib/platform-deploy-bridge.handler.d.ts +14 -0
  39. package/lib/platform-deploy-bridge.handler.js +762 -0
  40. package/lib/platform-deploy-bridge.handler.js.map +1 -0
  41. package/lib/platform-deploy-bridge.handler.mjs +134 -0
  42. package/lib/platform-deploy-bridge.handler.mjs.map +1 -0
  43. package/lib/post-authentication.handler.mjs +1 -1
  44. package/lib/post-confirmation.handler.mjs +1 -1
  45. package/lib/pre-token-generation.handler.js +76 -31
  46. package/lib/pre-token-generation.handler.js.map +1 -1
  47. package/lib/pre-token-generation.handler.mjs +5 -3
  48. package/lib/pre-token-generation.handler.mjs.map +1 -1
  49. package/lib/provision-default-workspace.handler.js +86 -41
  50. package/lib/provision-default-workspace.handler.js.map +1 -1
  51. package/lib/provision-default-workspace.handler.mjs +6 -4
  52. package/lib/provision-default-workspace.handler.mjs.map +1 -1
  53. package/lib/rest-api-lambda.handler.js +114 -59
  54. package/lib/rest-api-lambda.handler.js.map +1 -1
  55. package/lib/rest-api-lambda.handler.mjs +40 -61
  56. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  57. package/lib/seed-demo-data.handler.d.mts +107 -0
  58. package/lib/seed-demo-data.handler.d.ts +107 -0
  59. package/lib/seed-demo-data.handler.js +2037 -0
  60. package/lib/seed-demo-data.handler.js.map +1 -0
  61. package/lib/seed-demo-data.handler.mjs +23 -0
  62. package/lib/seed-demo-data.handler.mjs.map +1 -0
  63. package/lib/seed-system-data.handler.d.mts +64 -0
  64. package/lib/seed-system-data.handler.d.ts +64 -0
  65. package/lib/seed-system-data.handler.js +1631 -0
  66. package/lib/seed-system-data.handler.js.map +1 -0
  67. package/lib/seed-system-data.handler.mjs +135 -0
  68. package/lib/seed-system-data.handler.mjs.map +1 -0
  69. package/package.json +4 -2
  70. package/lib/chunk-36YCDLLA.mjs.map +0 -1
  71. /package/lib/{chunk-BXEG7IOZ.mjs.map → chunk-AO3E22CS.mjs.map} +0 -0
  72. /package/lib/{chunk-WNUH2WDZ.mjs.map → chunk-CHPEQRXU.mjs.map} +0 -0
  73. /package/lib/{chunk-3QS3WKRC.mjs.map → chunk-LZOMFHX3.mjs.map} +0 -0
@@ -0,0 +1,2037 @@
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/platform.js
138
+ var require_platform = __commonJS({
139
+ "../workflows/lib/detail-types/platform.js"(exports2) {
140
+ "use strict";
141
+ Object.defineProperty(exports2, "__esModule", { value: true });
142
+ exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = void 0;
143
+ var sources_1 = require_sources();
144
+ var registry_1 = require_registry();
145
+ exports2.PlatformDeploymentCompletedV1 = (0, registry_1.defineDetailType)({
146
+ detailType: "platform.deployment-completed.v1",
147
+ source: sources_1.OPENHI_CONTROL_SOURCE,
148
+ dedupRequired: true
149
+ });
150
+ exports2.PlatformSystemDataSeededV1 = (0, registry_1.defineDetailType)({
151
+ detailType: "platform.system-data-seeded.v1",
152
+ source: sources_1.OPENHI_CONTROL_SOURCE,
153
+ dedupRequired: true
154
+ });
155
+ }
156
+ });
157
+
158
+ // ../workflows/lib/detail-types/index.js
159
+ var require_detail_types = __commonJS({
160
+ "../workflows/lib/detail-types/index.js"(exports2) {
161
+ "use strict";
162
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
163
+ if (k2 === void 0) k2 = k;
164
+ var desc = Object.getOwnPropertyDescriptor(m, k);
165
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
166
+ desc = { enumerable: true, get: function() {
167
+ return m[k];
168
+ } };
169
+ }
170
+ Object.defineProperty(o, k2, desc);
171
+ }) : (function(o, m, k, k2) {
172
+ if (k2 === void 0) k2 = k;
173
+ o[k2] = m[k];
174
+ }));
175
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
176
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
177
+ };
178
+ Object.defineProperty(exports2, "__esModule", { value: true });
179
+ __exportStar(require_platform(), exports2);
180
+ __exportStar(require_registry(), exports2);
181
+ }
182
+ });
183
+
184
+ // ../workflows/lib/publisher.js
185
+ var require_publisher = __commonJS({
186
+ "../workflows/lib/publisher.js"(exports2) {
187
+ "use strict";
188
+ Object.defineProperty(exports2, "__esModule", { value: true });
189
+ exports2.WorkflowPublishError = void 0;
190
+ exports2.workflowsClient = workflowsClient;
191
+ exports2.publishWorkflowEvent = publishWorkflowEvent;
192
+ var node_crypto_1 = require("crypto");
193
+ var client_eventbridge_1 = require("@aws-sdk/client-eventbridge");
194
+ var envelope_version_1 = require_envelope_version();
195
+ var sources_1 = require_sources();
196
+ function workflowsClient(bridge, options = {}) {
197
+ return {
198
+ publish: (entry, payload, ctx) => publishWorkflowEvent(bridge, entry, payload, ctx, options)
199
+ };
200
+ }
201
+ async function publishWorkflowEvent(bridge, entry, payload, ctx, options = {}) {
202
+ const eventIdGenerator = options.eventIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
203
+ const correlationIdGenerator = options.correlationIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
204
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
205
+ const envelope = {
206
+ eventId: eventIdGenerator(),
207
+ attempt: 1,
208
+ correlationId: ctx.correlationId ?? correlationIdGenerator(),
209
+ causationId: ctx.causationId ?? null,
210
+ actor: ctx.actor,
211
+ occurredAt: now().toISOString(),
212
+ envelopeVersion: envelope_version_1.ENVELOPE_VERSION,
213
+ payload
214
+ };
215
+ const busName = options.busNameByPlane?.[entry.source] ?? sources_1.DEFAULT_BUS_NAME_BY_SOURCE[entry.source];
216
+ const result = await bridge.send(new client_eventbridge_1.PutEventsCommand({
217
+ Entries: [
218
+ {
219
+ EventBusName: busName,
220
+ Source: entry.source,
221
+ DetailType: entry.detailType,
222
+ Detail: JSON.stringify(envelope)
223
+ }
224
+ ]
225
+ }));
226
+ if ((result.FailedEntryCount ?? 0) > 0) {
227
+ const first = result.Entries?.[0];
228
+ throw new WorkflowPublishError(`EventBridge rejected ${entry.detailType} publish on bus ${busName}: ${first?.ErrorCode ?? "unknown"} \u2014 ${first?.ErrorMessage ?? "no error message"}`);
229
+ }
230
+ return { eventId: envelope.eventId };
231
+ }
232
+ var WorkflowPublishError = class extends Error {
233
+ /** @param message - human-readable description of the failed publish. */
234
+ constructor(message) {
235
+ super(message);
236
+ this.name = "WorkflowPublishError";
237
+ }
238
+ };
239
+ exports2.WorkflowPublishError = WorkflowPublishError;
240
+ }
241
+ });
242
+
243
+ // ../workflows/lib/consumer.js
244
+ var require_consumer = __commonJS({
245
+ "../workflows/lib/consumer.js"(exports2) {
246
+ "use strict";
247
+ Object.defineProperty(exports2, "__esModule", { value: true });
248
+ exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = void 0;
249
+ exports2.parseWorkflowEvent = parseWorkflowEvent2;
250
+ var envelope_version_1 = require_envelope_version();
251
+ function parseWorkflowEvent2(event, expected) {
252
+ if (event.source !== expected.source) {
253
+ throw new InvalidWorkflowEventError(`EventBridge source "${event.source}" does not match expected detail-type's source "${expected.source}".`);
254
+ }
255
+ if (event["detail-type"] !== expected.detailType) {
256
+ throw new InvalidWorkflowEventError(`EventBridge detail-type "${event["detail-type"]}" does not match expected "${expected.detailType}".`);
257
+ }
258
+ const candidate = asEnvelopeCandidate(event.detail);
259
+ if (!(0, envelope_version_1.isSupportedEnvelopeVersion)(candidate.envelopeVersion)) {
260
+ throw new UnsupportedEnvelopeVersionError(`Envelope version "${candidate.envelopeVersion}" is outside the SDK's supported range.`);
261
+ }
262
+ const envelope = {
263
+ eventId: candidate.eventId,
264
+ attempt: candidate.attempt,
265
+ correlationId: candidate.correlationId,
266
+ causationId: candidate.causationId,
267
+ actor: candidate.actor,
268
+ occurredAt: candidate.occurredAt,
269
+ envelopeVersion: candidate.envelopeVersion,
270
+ payload: candidate.payload
271
+ };
272
+ return {
273
+ envelope,
274
+ dedupKey: { eventId: envelope.eventId, attempt: envelope.attempt }
275
+ };
276
+ }
277
+ function asEnvelopeCandidate(detail) {
278
+ if (detail === null || typeof detail !== "object") {
279
+ throw new InvalidWorkflowEventError("EventBridge detail is not a non-null object.");
280
+ }
281
+ const obj = detail;
282
+ assertString(obj, "eventId");
283
+ assertPositiveInteger(obj, "attempt");
284
+ assertString(obj, "correlationId");
285
+ assertCausationId(obj);
286
+ assertActor(obj);
287
+ assertString(obj, "occurredAt");
288
+ assertString(obj, "envelopeVersion");
289
+ if (!("payload" in obj)) {
290
+ throw new InvalidWorkflowEventError("Envelope is missing required field: payload.");
291
+ }
292
+ return obj;
293
+ }
294
+ function assertString(obj, field) {
295
+ const value = obj[field];
296
+ if (typeof value !== "string" || value.length === 0) {
297
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a non-empty string.`);
298
+ }
299
+ }
300
+ function assertPositiveInteger(obj, field) {
301
+ const value = obj[field];
302
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
303
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a 1-indexed integer.`);
304
+ }
305
+ }
306
+ function assertCausationId(obj) {
307
+ if (!("causationId" in obj)) {
308
+ throw new InvalidWorkflowEventError("Envelope is missing required field: causationId.");
309
+ }
310
+ const value = obj.causationId;
311
+ if (value !== null && (typeof value !== "string" || value.length === 0)) {
312
+ throw new InvalidWorkflowEventError('Envelope field "causationId" must be a non-empty string or null.');
313
+ }
314
+ }
315
+ function assertActor(obj) {
316
+ const actor = obj.actor;
317
+ if (actor === null || typeof actor !== "object") {
318
+ throw new InvalidWorkflowEventError('Envelope field "actor" must be an object.');
319
+ }
320
+ const actorObj = actor;
321
+ const isUserActor = typeof actorObj.ohi_uid === "string" && typeof actorObj.ohi_uname === "string" && typeof actorObj.ohi_tid === "string" && typeof actorObj.ohi_wid === "string";
322
+ const isSystemActor = typeof actorObj.system === "string";
323
+ if (!isUserActor && !isSystemActor) {
324
+ 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 }).');
325
+ }
326
+ }
327
+ var InvalidWorkflowEventError = class extends Error {
328
+ /** @param message - human-readable description of the validation failure. */
329
+ constructor(message) {
330
+ super(message);
331
+ this.name = "InvalidWorkflowEventError";
332
+ }
333
+ };
334
+ exports2.InvalidWorkflowEventError = InvalidWorkflowEventError;
335
+ var UnsupportedEnvelopeVersionError = class extends Error {
336
+ /** @param message - human-readable description of the unsupported version. */
337
+ constructor(message) {
338
+ super(message);
339
+ this.name = "UnsupportedEnvelopeVersionError";
340
+ }
341
+ };
342
+ exports2.UnsupportedEnvelopeVersionError = UnsupportedEnvelopeVersionError;
343
+ }
344
+ });
345
+
346
+ // ../workflows/lib/dedup/env.js
347
+ var require_env = __commonJS({
348
+ "../workflows/lib/dedup/env.js"(exports2) {
349
+ "use strict";
350
+ Object.defineProperty(exports2, "__esModule", { value: true });
351
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = void 0;
352
+ exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = "OPENHI_WORKFLOW_DEDUP_TABLE_NAME";
353
+ exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = 14 * 24 * 60 * 60;
354
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = 64;
355
+ }
356
+ });
357
+
358
+ // ../workflows/lib/dedup/workflow-dedup-client.js
359
+ var require_workflow_dedup_client = __commonJS({
360
+ "../workflows/lib/dedup/workflow-dedup-client.js"(exports2) {
361
+ "use strict";
362
+ Object.defineProperty(exports2, "__esModule", { value: true });
363
+ exports2.WorkflowDedupInvalidInputError = exports2.WorkflowDedupTableNameMissingError = void 0;
364
+ exports2.workflowDedupClient = workflowDedupClient2;
365
+ exports2.recordIfAbsent = recordIfAbsent;
366
+ exports2.markFailed = markFailed;
367
+ exports2.encodeSortKey = encodeSortKey;
368
+ var client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
369
+ var env_1 = require_env();
370
+ function workflowDedupClient2(dynamodb, options = {}) {
371
+ return {
372
+ recordIfAbsent: (input) => recordIfAbsent(dynamodb, input, options),
373
+ markFailed: (input) => markFailed(dynamodb, input, options)
374
+ };
375
+ }
376
+ async function recordIfAbsent(dynamodb, input, options = {}) {
377
+ assertConsumerName(input.consumerName);
378
+ assertPositiveInteger(input.attempt, "attempt");
379
+ const ttlSeconds = input.ttlSeconds ?? options.defaultTtlSeconds ?? env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
380
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
381
+ throw new WorkflowDedupInvalidInputError(`ttlSeconds must be a positive integer; got ${ttlSeconds}.`);
382
+ }
383
+ const tableName = resolveTableName(options.tableName);
384
+ const now = (options.now ?? defaultNow)();
385
+ const sk = encodeSortKey(input.eventId, input.attempt);
386
+ const expiresAt = Math.floor(now.getTime() / 1e3) + ttlSeconds;
387
+ try {
388
+ await dynamodb.send(new client_dynamodb_1.PutItemCommand({
389
+ TableName: tableName,
390
+ Item: {
391
+ consumerName: { S: input.consumerName },
392
+ sk: { S: sk },
393
+ eventId: { S: input.eventId },
394
+ attempt: { N: String(input.attempt) },
395
+ recordedAt: { S: now.toISOString() },
396
+ expiresAt: { N: String(expiresAt) }
397
+ },
398
+ ConditionExpression: "attribute_not_exists(consumerName) AND attribute_not_exists(sk)"
399
+ }));
400
+ return { recorded: true };
401
+ } catch (err) {
402
+ if (err instanceof client_dynamodb_1.ConditionalCheckFailedException) {
403
+ return { recorded: false, alreadyProcessed: true };
404
+ }
405
+ throw err;
406
+ }
407
+ }
408
+ async function markFailed(dynamodb, input, options = {}) {
409
+ assertConsumerName(input.consumerName);
410
+ assertPositiveInteger(input.attempt, "attempt");
411
+ if (input.reason.length === 0) {
412
+ throw new WorkflowDedupInvalidInputError("reason must be non-empty.");
413
+ }
414
+ const tableName = resolveTableName(options.tableName);
415
+ const now = (options.now ?? defaultNow)();
416
+ const sk = encodeSortKey(input.eventId, input.attempt);
417
+ await dynamodb.send(new client_dynamodb_1.UpdateItemCommand({
418
+ TableName: tableName,
419
+ Key: {
420
+ consumerName: { S: input.consumerName },
421
+ sk: { S: sk }
422
+ },
423
+ UpdateExpression: "SET #failed = :failed, #failureReason = :reason, #failedAt = :failedAt",
424
+ ExpressionAttributeNames: {
425
+ "#failed": "failed",
426
+ "#failureReason": "failureReason",
427
+ "#failedAt": "failedAt"
428
+ },
429
+ ExpressionAttributeValues: {
430
+ ":failed": { BOOL: true },
431
+ ":reason": { S: input.reason },
432
+ ":failedAt": { S: now.toISOString() }
433
+ }
434
+ }));
435
+ }
436
+ function encodeSortKey(eventId, attempt) {
437
+ if (eventId.length === 0) {
438
+ throw new WorkflowDedupInvalidInputError("eventId must be non-empty.");
439
+ }
440
+ return `${eventId}#${attempt}`;
441
+ }
442
+ function resolveTableName(explicit) {
443
+ const name = explicit ?? process.env[env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR];
444
+ if (!name) {
445
+ throw new WorkflowDedupTableNameMissingError(`Workflow dedup table name not set. Pass options.tableName or set ${env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR}.`);
446
+ }
447
+ return name;
448
+ }
449
+ function assertConsumerName(consumerName) {
450
+ if (consumerName.length === 0) {
451
+ throw new WorkflowDedupInvalidInputError("consumerName must be non-empty.");
452
+ }
453
+ if (consumerName.length > env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {
454
+ throw new WorkflowDedupInvalidInputError(`consumerName must be \u2264${env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`);
455
+ }
456
+ if (/\s/.test(consumerName)) {
457
+ throw new WorkflowDedupInvalidInputError("consumerName must not contain whitespace.");
458
+ }
459
+ }
460
+ function assertPositiveInteger(value, field) {
461
+ if (!Number.isInteger(value) || value < 1) {
462
+ throw new WorkflowDedupInvalidInputError(`${field} must be a 1-indexed integer; got ${value}.`);
463
+ }
464
+ }
465
+ function defaultNow() {
466
+ return /* @__PURE__ */ new Date();
467
+ }
468
+ var WorkflowDedupTableNameMissingError = class extends Error {
469
+ /** @param message - human-readable description. */
470
+ constructor(message) {
471
+ super(message);
472
+ this.name = "WorkflowDedupTableNameMissingError";
473
+ }
474
+ };
475
+ exports2.WorkflowDedupTableNameMissingError = WorkflowDedupTableNameMissingError;
476
+ var WorkflowDedupInvalidInputError = class extends Error {
477
+ /** @param message - human-readable description. */
478
+ constructor(message) {
479
+ super(message);
480
+ this.name = "WorkflowDedupInvalidInputError";
481
+ }
482
+ };
483
+ exports2.WorkflowDedupInvalidInputError = WorkflowDedupInvalidInputError;
484
+ }
485
+ });
486
+
487
+ // ../workflows/lib/dedup/index.js
488
+ var require_dedup = __commonJS({
489
+ "../workflows/lib/dedup/index.js"(exports2) {
490
+ "use strict";
491
+ Object.defineProperty(exports2, "__esModule", { value: true });
492
+ 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;
493
+ var env_1 = require_env();
494
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
495
+ return env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
496
+ } });
497
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
498
+ return env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
499
+ } });
500
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
501
+ return env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
502
+ } });
503
+ var workflow_dedup_client_1 = require_workflow_dedup_client();
504
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
505
+ return workflow_dedup_client_1.WorkflowDedupInvalidInputError;
506
+ } });
507
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
508
+ return workflow_dedup_client_1.WorkflowDedupTableNameMissingError;
509
+ } });
510
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
511
+ return workflow_dedup_client_1.encodeSortKey;
512
+ } });
513
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
514
+ return workflow_dedup_client_1.markFailed;
515
+ } });
516
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
517
+ return workflow_dedup_client_1.recordIfAbsent;
518
+ } });
519
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
520
+ return workflow_dedup_client_1.workflowDedupClient;
521
+ } });
522
+ }
523
+ });
524
+
525
+ // ../workflows/lib/index.js
526
+ var require_lib = __commonJS({
527
+ "../workflows/lib/index.js"(exports2) {
528
+ "use strict";
529
+ Object.defineProperty(exports2, "__esModule", { value: true });
530
+ exports2.workflowDedupClient = exports2.recordIfAbsent = exports2.markFailed = exports2.encodeSortKey = exports2.WorkflowDedupTableNameMissingError = exports2.WorkflowDedupInvalidInputError = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.parseWorkflowEvent = exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = exports2.workflowsClient = exports2.publishWorkflowEvent = exports2.WorkflowPublishError = exports2.isWellFormedDetailType = exports2.defineDetailType = exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = exports2.InvalidDetailTypeRegistrationError = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.workflowUserActorFromClaims = exports2.isWorkflowUserActor = exports2.isWorkflowSystemActor = exports2.MissingActorContextError = exports2.isSupportedEnvelopeVersion = exports2.ENVELOPE_VERSION = void 0;
531
+ var envelope_version_1 = require_envelope_version();
532
+ Object.defineProperty(exports2, "ENVELOPE_VERSION", { enumerable: true, get: function() {
533
+ return envelope_version_1.ENVELOPE_VERSION;
534
+ } });
535
+ Object.defineProperty(exports2, "isSupportedEnvelopeVersion", { enumerable: true, get: function() {
536
+ return envelope_version_1.isSupportedEnvelopeVersion;
537
+ } });
538
+ var envelope_1 = require_envelope();
539
+ Object.defineProperty(exports2, "MissingActorContextError", { enumerable: true, get: function() {
540
+ return envelope_1.MissingActorContextError;
541
+ } });
542
+ Object.defineProperty(exports2, "isWorkflowSystemActor", { enumerable: true, get: function() {
543
+ return envelope_1.isWorkflowSystemActor;
544
+ } });
545
+ Object.defineProperty(exports2, "isWorkflowUserActor", { enumerable: true, get: function() {
546
+ return envelope_1.isWorkflowUserActor;
547
+ } });
548
+ Object.defineProperty(exports2, "workflowUserActorFromClaims", { enumerable: true, get: function() {
549
+ return envelope_1.workflowUserActorFromClaims;
550
+ } });
551
+ var sources_1 = require_sources();
552
+ Object.defineProperty(exports2, "DEFAULT_BUS_NAME_BY_SOURCE", { enumerable: true, get: function() {
553
+ return sources_1.DEFAULT_BUS_NAME_BY_SOURCE;
554
+ } });
555
+ Object.defineProperty(exports2, "OPENHI_CONTROL_SOURCE", { enumerable: true, get: function() {
556
+ return sources_1.OPENHI_CONTROL_SOURCE;
557
+ } });
558
+ Object.defineProperty(exports2, "OPENHI_DATA_SOURCE", { enumerable: true, get: function() {
559
+ return sources_1.OPENHI_DATA_SOURCE;
560
+ } });
561
+ Object.defineProperty(exports2, "OPENHI_OPS_SOURCE", { enumerable: true, get: function() {
562
+ return sources_1.OPENHI_OPS_SOURCE;
563
+ } });
564
+ var detail_types_1 = require_detail_types();
565
+ Object.defineProperty(exports2, "InvalidDetailTypeRegistrationError", { enumerable: true, get: function() {
566
+ return detail_types_1.InvalidDetailTypeRegistrationError;
567
+ } });
568
+ Object.defineProperty(exports2, "PlatformDeploymentCompletedV1", { enumerable: true, get: function() {
569
+ return detail_types_1.PlatformDeploymentCompletedV1;
570
+ } });
571
+ Object.defineProperty(exports2, "PlatformSystemDataSeededV1", { enumerable: true, get: function() {
572
+ return detail_types_1.PlatformSystemDataSeededV1;
573
+ } });
574
+ Object.defineProperty(exports2, "defineDetailType", { enumerable: true, get: function() {
575
+ return detail_types_1.defineDetailType;
576
+ } });
577
+ Object.defineProperty(exports2, "isWellFormedDetailType", { enumerable: true, get: function() {
578
+ return detail_types_1.isWellFormedDetailType;
579
+ } });
580
+ var publisher_1 = require_publisher();
581
+ Object.defineProperty(exports2, "WorkflowPublishError", { enumerable: true, get: function() {
582
+ return publisher_1.WorkflowPublishError;
583
+ } });
584
+ Object.defineProperty(exports2, "publishWorkflowEvent", { enumerable: true, get: function() {
585
+ return publisher_1.publishWorkflowEvent;
586
+ } });
587
+ Object.defineProperty(exports2, "workflowsClient", { enumerable: true, get: function() {
588
+ return publisher_1.workflowsClient;
589
+ } });
590
+ var consumer_1 = require_consumer();
591
+ Object.defineProperty(exports2, "InvalidWorkflowEventError", { enumerable: true, get: function() {
592
+ return consumer_1.InvalidWorkflowEventError;
593
+ } });
594
+ Object.defineProperty(exports2, "UnsupportedEnvelopeVersionError", { enumerable: true, get: function() {
595
+ return consumer_1.UnsupportedEnvelopeVersionError;
596
+ } });
597
+ Object.defineProperty(exports2, "parseWorkflowEvent", { enumerable: true, get: function() {
598
+ return consumer_1.parseWorkflowEvent;
599
+ } });
600
+ var dedup_1 = require_dedup();
601
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
602
+ return dedup_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
603
+ } });
604
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
605
+ return dedup_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
606
+ } });
607
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
608
+ return dedup_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
609
+ } });
610
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
611
+ return dedup_1.WorkflowDedupInvalidInputError;
612
+ } });
613
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
614
+ return dedup_1.WorkflowDedupTableNameMissingError;
615
+ } });
616
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
617
+ return dedup_1.encodeSortKey;
618
+ } });
619
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
620
+ return dedup_1.markFailed;
621
+ } });
622
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
623
+ return dedup_1.recordIfAbsent;
624
+ } });
625
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
626
+ return dedup_1.workflowDedupClient;
627
+ } });
628
+ }
629
+ });
630
+
631
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts
632
+ var seed_demo_data_handler_exports = {};
633
+ __export(seed_demo_data_handler_exports, {
634
+ SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR: () => SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR,
635
+ devPasswordForEmail: () => devPasswordForEmail,
636
+ handler: () => handler,
637
+ productionCognitoProvisioner: () => productionCognitoProvisioner,
638
+ runSeedDemoData: () => runSeedDemoData,
639
+ seedDemoGraph: () => seedDemoGraph
640
+ });
641
+ module.exports = __toCommonJS(seed_demo_data_handler_exports);
642
+ var import_node_crypto = require("crypto");
643
+ var import_client_cognito_identity_provider = require("@aws-sdk/client-cognito-identity-provider");
644
+ var import_client_dynamodb2 = require("@aws-sdk/client-dynamodb");
645
+ var import_types7 = require("@openhi/types");
646
+ var import_workflows2 = __toESM(require_lib());
647
+
648
+ // src/workflows/control-plane/seed-demo-data/events.ts
649
+ var import_types = require("@openhi/types");
650
+ var import_workflows = __toESM(require_lib());
651
+ var SEED_DEMO_DATA_CONSUMER_NAME = "seed-demo-data";
652
+ var DEMO_URN_SYSTEM = "urn:openhi:demo";
653
+ var OPENHI_RESOURCE_URN_SYSTEM = "http://openhi.org/";
654
+ var DEMO_PERIOD = { start: "2026-01-01T00:00:00Z" };
655
+ var PLATFORM_SCOPE_TENANT_ID = "platform";
656
+ var PLACEHOLDER_TENANT_ID = "placeholder-tenant-id";
657
+ var PLACEHOLDER_WORKSPACE_ID = "placeholder-workspace-id";
658
+ var DEV_USERS = [
659
+ { id: "dev-russell", email: "russell@codedrifters.com" },
660
+ { id: "dev-cameron", email: "cameron@codedrifters.com" },
661
+ { id: "dev-neelima", email: "neelima@codedrifters.com" },
662
+ { id: "dev-garon", email: "garon@codedrifters.com" },
663
+ { id: "dev-dave", email: "dave@codedrifters.com" },
664
+ { id: "dev-drew", email: "drew@codedrifters.com" },
665
+ { id: "dev-jessica", email: "jessica@codedrifters.com" },
666
+ { id: "dev-jared", email: "jared@codedrifters.com" },
667
+ { id: "dev-goddess", email: "goddess@codedrifters.com" }
668
+ ];
669
+ var DEMO_TENANT_SPECS = [
670
+ {
671
+ scenario: "placeholder",
672
+ tenantId: PLACEHOLDER_TENANT_ID,
673
+ tenantName: "OpenHI Placeholder Tenant",
674
+ workspaces: [
675
+ {
676
+ id: PLACEHOLDER_WORKSPACE_ID,
677
+ name: "OpenHI Placeholder Workspace",
678
+ roleSuffix: "workspace"
679
+ }
680
+ ]
681
+ },
682
+ {
683
+ scenario: "demo-wound-care",
684
+ tenantId: "demo-wound-care-tenant",
685
+ tenantName: "Cedarbrook Wound Healing Institute",
686
+ workspaces: [
687
+ {
688
+ id: "demo-wound-care-workspace",
689
+ name: "Cedarbrook Outpatient Wound Clinic",
690
+ roleSuffix: "workspace"
691
+ }
692
+ ]
693
+ },
694
+ {
695
+ scenario: "demo-primary-care",
696
+ tenantId: "demo-primary-care-tenant",
697
+ tenantName: "Maple Ridge Family Medicine",
698
+ workspaces: [
699
+ {
700
+ id: "demo-primary-care-workspace",
701
+ name: "Maple Ridge Main Street Office",
702
+ roleSuffix: "workspace"
703
+ }
704
+ ]
705
+ },
706
+ {
707
+ scenario: "demo-mixed",
708
+ tenantId: "demo-mixed-tenant",
709
+ tenantName: "Northbridge Health Network",
710
+ workspaces: [
711
+ {
712
+ id: "demo-mixed-workspace-wound-care",
713
+ name: "Northbridge Wound Care Center",
714
+ roleSuffix: "workspace-wound-care"
715
+ },
716
+ {
717
+ id: "demo-mixed-workspace-primary-care",
718
+ name: "Northbridge Family Practice",
719
+ roleSuffix: "workspace-primary-care"
720
+ }
721
+ ]
722
+ }
723
+ ];
724
+ var demoMembershipId = (devUserId, tenantId) => `demo-membership-${devUserId}-${tenantId}`;
725
+ var demoRoleAssignmentId = (devUserId, tenantId, roleCode) => `demo-roleassignment-${devUserId}-${tenantId}-${roleCode}`;
726
+ var demoScenarioIdentifier = (scenario, roleSuffix) => ({
727
+ system: DEMO_URN_SYSTEM,
728
+ value: `${scenario}:${roleSuffix}`
729
+ });
730
+ var openhiResourceIdentifier = (params) => ({
731
+ use: "unversioned",
732
+ system: OPENHI_RESOURCE_URN_SYSTEM,
733
+ value: `urn:ohi:${params.tenantId}:${params.workspaceId}:${params.resourceType}:${params.id}`
734
+ });
735
+ var demoRolesForUserInTenant = (_user, _tenantId) => {
736
+ void _user;
737
+ void _tenantId;
738
+ return [import_types.CONTROL_PLANE_ROLE_CODE.TENANT_ADMIN];
739
+ };
740
+
741
+ // src/data/dynamo/dynamo-control-service.ts
742
+ var import_electrodb8 = require("electrodb");
743
+
744
+ // src/data/dynamo/dynamo-client.ts
745
+ var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
746
+ var defaultTableName = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
747
+ var dynamoClient = new import_client_dynamodb.DynamoDBClient({
748
+ ...process.env.MOCK_DYNAMODB_ENDPOINT && {
749
+ endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
750
+ sslEnabled: false,
751
+ region: "local"
752
+ }
753
+ });
754
+
755
+ // src/data/dynamo/entities/control/configuration-entity.ts
756
+ var import_electrodb = require("electrodb");
757
+
758
+ // src/data/dynamo/entities/control/control-entity-common.ts
759
+ var import_types2 = require("@openhi/types");
760
+
761
+ // src/data/dynamo/shard.ts
762
+ var SHARD_COUNT = 4;
763
+ function computeShard(id) {
764
+ let hash = 2166136261;
765
+ for (let i = 0; i < id.length; i++) {
766
+ hash ^= id.charCodeAt(i);
767
+ hash = Math.imul(hash, 16777619);
768
+ }
769
+ return (hash >>> 0) % SHARD_COUNT;
770
+ }
771
+
772
+ // src/data/dynamo/entities/control/control-entity-common.ts
773
+ var gsi1ShardAttribute = {
774
+ type: "string",
775
+ watch: ["id"],
776
+ set: (_val, item) => {
777
+ if (typeof item?.id !== "string" || item.id.length === 0) {
778
+ return void 0;
779
+ }
780
+ return String(computeShard(item.id));
781
+ }
782
+ };
783
+ var gsi1skAttribute = {
784
+ type: "string",
785
+ watch: ["resource", "lastUpdated", "id"],
786
+ set: (_val, item) => {
787
+ const id = typeof item?.id === "string" ? item.id : "";
788
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
789
+ const fallback = `${lastUpdated}#${id}`;
790
+ if (typeof item?.resource !== "string" || item.resource.length === 0) {
791
+ return fallback;
792
+ }
793
+ let parsed;
794
+ try {
795
+ parsed = JSON.parse(item.resource);
796
+ } catch {
797
+ return fallback;
798
+ }
799
+ if (!parsed || typeof parsed !== "object") return fallback;
800
+ const resourceType = parsed.resourceType;
801
+ if (typeof resourceType !== "string") return fallback;
802
+ const label = (0, import_types2.extractLabel)(parsed);
803
+ return label !== void 0 ? `${label}#${id}` : fallback;
804
+ }
805
+ };
806
+
807
+ // src/data/dynamo/entities/control/configuration-entity.ts
808
+ var ConfigurationEntity = new import_electrodb.Entity({
809
+ model: {
810
+ entity: "configuration",
811
+ service: "control",
812
+ version: "01"
813
+ },
814
+ attributes: {
815
+ /** Sort key. "CURRENT" for current version; version history in S3. */
816
+ sk: {
817
+ type: "string",
818
+ required: true,
819
+ default: "CURRENT"
820
+ },
821
+ /** Tenant scope. Use "BASELINE" when the config is baseline default (no tenant). */
822
+ tenantId: {
823
+ type: "string",
824
+ required: true,
825
+ default: "BASELINE"
826
+ },
827
+ /** Workspace scope. Use "-" when absent. */
828
+ workspaceId: {
829
+ type: "string",
830
+ required: true,
831
+ default: "-"
832
+ },
833
+ /** User scope. Use "-" when absent. */
834
+ userId: {
835
+ type: "string",
836
+ required: true,
837
+ default: "-"
838
+ },
839
+ /** Role scope. Use "-" when absent. */
840
+ roleId: {
841
+ type: "string",
842
+ required: true,
843
+ default: "-"
844
+ },
845
+ /** Config type (category), e.g. endpoints, branding, display. */
846
+ key: {
847
+ type: "string",
848
+ required: true
849
+ },
850
+ /** FHIR Resource.id; logical id in URL and for the Configuration resource. */
851
+ id: {
852
+ type: "string",
853
+ required: true
854
+ },
855
+ /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
856
+ resource: {
857
+ type: "string",
858
+ required: true
859
+ },
860
+ /**
861
+ * Summary projection (key display fields as JSON string: id, key, status).
862
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
863
+ */
864
+ summary: {
865
+ type: "string",
866
+ required: true
867
+ },
868
+ /** Version id (e.g. ULID). Tracks current version; S3 history key. */
869
+ vid: {
870
+ type: "string",
871
+ required: true
872
+ },
873
+ lastUpdated: {
874
+ type: "string",
875
+ required: true
876
+ },
877
+ gsi1Shard: gsi1ShardAttribute,
878
+ deleted: {
879
+ type: "boolean",
880
+ required: false
881
+ },
882
+ bundleId: {
883
+ type: "string",
884
+ required: false
885
+ },
886
+ msgId: {
887
+ type: "string",
888
+ required: false
889
+ }
890
+ },
891
+ indexes: {
892
+ /** 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. */
893
+ record: {
894
+ pk: {
895
+ field: "PK",
896
+ composite: ["tenantId", "workspaceId", "userId", "roleId"],
897
+ template: "CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
898
+ },
899
+ sk: {
900
+ field: "SK",
901
+ composite: ["key", "sk"],
902
+ template: "KEY#${key}#SK#${sk}"
903
+ }
904
+ },
905
+ /**
906
+ * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a
907
+ * (tenant, workspace) across the four shards. Use for "list configs scoped to this tenant"
908
+ * (workspaceId = "-") or "list configs scoped to this workspace". Does not support
909
+ * hierarchical resolution in one query; use base table GetItem in fallback order
910
+ * (user → workspace → tenant → baseline) for that.
911
+ * SK is `<key>#<id>` — Configuration's `key` is a required entity attribute (the
912
+ * config category: endpoints, branding, display, …) and the natural sort/lookup
913
+ * dimension. `casing: "none"` preserves the literal key value.
914
+ */
915
+ gsi1: {
916
+ index: "GSI1",
917
+ pk: {
918
+ field: "GSI1PK",
919
+ composite: ["tenantId", "workspaceId", "gsi1Shard"],
920
+ template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}"
921
+ },
922
+ sk: {
923
+ field: "GSI1SK",
924
+ casing: "none",
925
+ composite: ["key", "id"],
926
+ template: "${key}#${id}"
927
+ }
928
+ }
929
+ }
930
+ });
931
+
932
+ // src/data/dynamo/entities/control/membership-entity.ts
933
+ var import_electrodb2 = require("electrodb");
934
+ var MembershipEntity = new import_electrodb2.Entity({
935
+ model: {
936
+ entity: "membership",
937
+ service: "control",
938
+ version: "01"
939
+ },
940
+ attributes: {
941
+ /** Sort key sentinel. Always "CURRENT". */
942
+ sk: {
943
+ type: "string",
944
+ required: true,
945
+ default: "CURRENT"
946
+ },
947
+ /** Tenant in which the user has membership (required). */
948
+ tenantId: {
949
+ type: "string",
950
+ required: true
951
+ },
952
+ /** FHIR Resource.id; membership id. */
953
+ id: {
954
+ type: "string",
955
+ required: true
956
+ },
957
+ /** Full Membership resource serialized as JSON string. */
958
+ resource: {
959
+ type: "string",
960
+ required: true
961
+ },
962
+ /**
963
+ * Summary projection (key display fields as JSON string: id, displayName, status).
964
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
965
+ */
966
+ summary: {
967
+ type: "string",
968
+ required: true
969
+ },
970
+ /** Version id (e.g. ULID). */
971
+ vid: {
972
+ type: "string",
973
+ required: true
974
+ },
975
+ lastUpdated: {
976
+ type: "string",
977
+ required: true
978
+ },
979
+ gsi1Shard: gsi1ShardAttribute,
980
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
981
+ gsi1sk: gsi1skAttribute,
982
+ deleted: {
983
+ type: "boolean",
984
+ required: false
985
+ },
986
+ bundleId: {
987
+ type: "string",
988
+ required: false
989
+ },
990
+ msgId: {
991
+ type: "string",
992
+ required: false
993
+ }
994
+ },
995
+ indexes: {
996
+ /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
997
+ record: {
998
+ pk: {
999
+ field: "PK",
1000
+ composite: ["tenantId", "id"],
1001
+ template: "TID#${tenantId}#MEMBERSHIP#ID#${id}"
1002
+ },
1003
+ sk: {
1004
+ field: "SK",
1005
+ composite: ["sk"],
1006
+ template: "${sk}"
1007
+ }
1008
+ },
1009
+ /**
1010
+ * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
1011
+ * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
1012
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
1013
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
1014
+ * normalized label and ISO-8601 `T`/`Z`.
1015
+ */
1016
+ gsi1: {
1017
+ index: "GSI1",
1018
+ pk: {
1019
+ field: "GSI1PK",
1020
+ composite: ["tenantId", "gsi1Shard"],
1021
+ template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
1022
+ },
1023
+ sk: {
1024
+ field: "GSI1SK",
1025
+ casing: "none",
1026
+ composite: ["gsi1sk"],
1027
+ template: "${gsi1sk}"
1028
+ }
1029
+ }
1030
+ }
1031
+ });
1032
+
1033
+ // src/data/dynamo/entities/control/role-entity.ts
1034
+ var import_electrodb3 = require("electrodb");
1035
+ var RoleEntity = new import_electrodb3.Entity({
1036
+ model: {
1037
+ entity: "role",
1038
+ service: "control",
1039
+ version: "01"
1040
+ },
1041
+ attributes: {
1042
+ /** Sort key sentinel. Always "CURRENT". */
1043
+ sk: {
1044
+ type: "string",
1045
+ required: true,
1046
+ default: "CURRENT"
1047
+ },
1048
+ /** FHIR Resource.id; role id. */
1049
+ id: {
1050
+ type: "string",
1051
+ required: true
1052
+ },
1053
+ /** Full Role resource serialized as JSON string. */
1054
+ resource: {
1055
+ type: "string",
1056
+ required: true
1057
+ },
1058
+ /**
1059
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1060
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1061
+ */
1062
+ summary: {
1063
+ type: "string",
1064
+ required: true
1065
+ },
1066
+ /** Version id (e.g. ULID). */
1067
+ vid: {
1068
+ type: "string",
1069
+ required: true
1070
+ },
1071
+ lastUpdated: {
1072
+ type: "string",
1073
+ required: true
1074
+ },
1075
+ gsi1Shard: gsi1ShardAttribute,
1076
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1077
+ gsi1sk: gsi1skAttribute,
1078
+ deleted: {
1079
+ type: "boolean",
1080
+ required: false
1081
+ },
1082
+ bundleId: {
1083
+ type: "string",
1084
+ required: false
1085
+ },
1086
+ msgId: {
1087
+ type: "string",
1088
+ required: false
1089
+ }
1090
+ },
1091
+ indexes: {
1092
+ /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1093
+ record: {
1094
+ pk: {
1095
+ field: "PK",
1096
+ composite: ["id"],
1097
+ template: "ROLE#ID#${id}"
1098
+ },
1099
+ sk: {
1100
+ field: "SK",
1101
+ composite: ["sk"],
1102
+ template: "${sk}"
1103
+ }
1104
+ },
1105
+ /**
1106
+ * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
1107
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
1108
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
1109
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
1110
+ * normalized label and ISO-8601 `T`/`Z`.
1111
+ */
1112
+ gsi1: {
1113
+ index: "GSI1",
1114
+ pk: {
1115
+ field: "GSI1PK",
1116
+ composite: ["gsi1Shard"],
1117
+ template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
1118
+ },
1119
+ sk: {
1120
+ field: "GSI1SK",
1121
+ casing: "none",
1122
+ composite: ["gsi1sk"],
1123
+ template: "${gsi1sk}"
1124
+ }
1125
+ }
1126
+ }
1127
+ });
1128
+
1129
+ // src/data/dynamo/entities/control/roleassignment-entity.ts
1130
+ var import_electrodb4 = require("electrodb");
1131
+ var RoleAssignmentEntity = new import_electrodb4.Entity({
1132
+ model: {
1133
+ entity: "roleassignment",
1134
+ service: "control",
1135
+ version: "01"
1136
+ },
1137
+ attributes: {
1138
+ /** Sort key sentinel. Always "CURRENT". */
1139
+ sk: {
1140
+ type: "string",
1141
+ required: true,
1142
+ default: "CURRENT"
1143
+ },
1144
+ /** Tenant in which the role assignment applies (required). */
1145
+ tenantId: {
1146
+ type: "string",
1147
+ required: true
1148
+ },
1149
+ /** FHIR Resource.id; role assignment id. */
1150
+ id: {
1151
+ type: "string",
1152
+ required: true
1153
+ },
1154
+ /** Full RoleAssignment resource serialized as JSON string. */
1155
+ resource: {
1156
+ type: "string",
1157
+ required: true
1158
+ },
1159
+ /**
1160
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1161
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1162
+ */
1163
+ summary: {
1164
+ type: "string",
1165
+ required: true
1166
+ },
1167
+ /** Version id (e.g. ULID). */
1168
+ vid: {
1169
+ type: "string",
1170
+ required: true
1171
+ },
1172
+ lastUpdated: {
1173
+ type: "string",
1174
+ required: true
1175
+ },
1176
+ gsi1Shard: gsi1ShardAttribute,
1177
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1178
+ gsi1sk: gsi1skAttribute,
1179
+ deleted: {
1180
+ type: "boolean",
1181
+ required: false
1182
+ },
1183
+ bundleId: {
1184
+ type: "string",
1185
+ required: false
1186
+ },
1187
+ msgId: {
1188
+ type: "string",
1189
+ required: false
1190
+ }
1191
+ },
1192
+ indexes: {
1193
+ /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1194
+ record: {
1195
+ pk: {
1196
+ field: "PK",
1197
+ composite: ["tenantId", "id"],
1198
+ template: "TID#${tenantId}#ROLEASSIGNMENT#ID#${id}"
1199
+ },
1200
+ sk: {
1201
+ field: "SK",
1202
+ composite: ["sk"],
1203
+ template: "${sk}"
1204
+ }
1205
+ },
1206
+ /**
1207
+ * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
1208
+ * four shards. Tenant-scoped only, so `WID#-` is a sentinel.
1209
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
1210
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
1211
+ * normalized label and ISO-8601 `T`/`Z`.
1212
+ */
1213
+ gsi1: {
1214
+ index: "GSI1",
1215
+ pk: {
1216
+ field: "GSI1PK",
1217
+ composite: ["tenantId", "gsi1Shard"],
1218
+ template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
1219
+ },
1220
+ sk: {
1221
+ field: "GSI1SK",
1222
+ casing: "none",
1223
+ composite: ["gsi1sk"],
1224
+ template: "${gsi1sk}"
1225
+ }
1226
+ }
1227
+ }
1228
+ });
1229
+
1230
+ // src/data/dynamo/entities/control/tenant-entity.ts
1231
+ var import_electrodb5 = require("electrodb");
1232
+ var TenantEntity = new import_electrodb5.Entity({
1233
+ model: {
1234
+ entity: "tenant",
1235
+ service: "control",
1236
+ version: "01"
1237
+ },
1238
+ attributes: {
1239
+ /** Sort key sentinel. Always "CURRENT". */
1240
+ sk: {
1241
+ type: "string",
1242
+ required: true,
1243
+ default: "CURRENT"
1244
+ },
1245
+ /** The tenant's own id (= resource id). Drives the partition key. */
1246
+ tenantId: {
1247
+ type: "string",
1248
+ required: true
1249
+ },
1250
+ /** FHIR Resource.id; logical id in URL. Equals tenantId. */
1251
+ id: {
1252
+ type: "string",
1253
+ required: true
1254
+ },
1255
+ /** Full Tenant resource serialized as JSON string. */
1256
+ resource: {
1257
+ type: "string",
1258
+ required: true
1259
+ },
1260
+ /**
1261
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1262
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1263
+ */
1264
+ summary: {
1265
+ type: "string",
1266
+ required: true
1267
+ },
1268
+ /** Version id (e.g. ULID). */
1269
+ vid: {
1270
+ type: "string",
1271
+ required: true
1272
+ },
1273
+ lastUpdated: {
1274
+ type: "string",
1275
+ required: true
1276
+ },
1277
+ gsi1Shard: gsi1ShardAttribute,
1278
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1279
+ gsi1sk: gsi1skAttribute,
1280
+ deleted: {
1281
+ type: "boolean",
1282
+ required: false
1283
+ },
1284
+ bundleId: {
1285
+ type: "string",
1286
+ required: false
1287
+ },
1288
+ msgId: {
1289
+ type: "string",
1290
+ required: false
1291
+ }
1292
+ },
1293
+ indexes: {
1294
+ /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */
1295
+ record: {
1296
+ pk: {
1297
+ field: "PK",
1298
+ composite: ["tenantId"],
1299
+ template: "TENANT#ID#${tenantId}"
1300
+ },
1301
+ sk: {
1302
+ field: "SK",
1303
+ composite: ["sk"],
1304
+ template: "${sk}"
1305
+ }
1306
+ },
1307
+ /**
1308
+ * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.
1309
+ * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`
1310
+ * sentinels precede `RT#Tenant#SHARD#<n>`. SK is derived via `gsi1skAttribute` —
1311
+ * `<normalizedName>#<id>` when the resource carries a `name`, else `<lastUpdated>#<id>`
1312
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
1313
+ */
1314
+ gsi1: {
1315
+ index: "GSI1",
1316
+ pk: {
1317
+ field: "GSI1PK",
1318
+ composite: ["gsi1Shard"],
1319
+ template: "TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}"
1320
+ },
1321
+ sk: {
1322
+ field: "GSI1SK",
1323
+ casing: "none",
1324
+ composite: ["gsi1sk"],
1325
+ template: "${gsi1sk}"
1326
+ }
1327
+ }
1328
+ }
1329
+ });
1330
+
1331
+ // src/data/dynamo/entities/control/user-entity.ts
1332
+ var import_electrodb6 = require("electrodb");
1333
+ var UserEntity = new import_electrodb6.Entity({
1334
+ model: {
1335
+ entity: "user",
1336
+ service: "control",
1337
+ version: "01"
1338
+ },
1339
+ attributes: {
1340
+ /** Sort key sentinel. Always "CURRENT". */
1341
+ sk: {
1342
+ type: "string",
1343
+ required: true,
1344
+ default: "CURRENT"
1345
+ },
1346
+ /** FHIR Resource.id; platform user id (ohi_uid). */
1347
+ id: {
1348
+ type: "string",
1349
+ required: true
1350
+ },
1351
+ /** Full User resource serialized as JSON string. */
1352
+ resource: {
1353
+ type: "string",
1354
+ required: true
1355
+ },
1356
+ /**
1357
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1358
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1359
+ */
1360
+ summary: {
1361
+ type: "string",
1362
+ required: true
1363
+ },
1364
+ /**
1365
+ * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the
1366
+ * Post Confirmation Lambda (#770) lands; required thereafter.
1367
+ */
1368
+ cognitoSub: {
1369
+ type: "string",
1370
+ required: false
1371
+ },
1372
+ /** Version id (e.g. ULID). */
1373
+ vid: {
1374
+ type: "string",
1375
+ required: true
1376
+ },
1377
+ lastUpdated: {
1378
+ type: "string",
1379
+ required: true
1380
+ },
1381
+ gsi1Shard: gsi1ShardAttribute,
1382
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1383
+ gsi1sk: gsi1skAttribute,
1384
+ deleted: {
1385
+ type: "boolean",
1386
+ required: false
1387
+ },
1388
+ bundleId: {
1389
+ type: "string",
1390
+ required: false
1391
+ },
1392
+ msgId: {
1393
+ type: "string",
1394
+ required: false
1395
+ }
1396
+ },
1397
+ indexes: {
1398
+ /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1399
+ record: {
1400
+ pk: {
1401
+ field: "PK",
1402
+ composite: ["id"],
1403
+ template: "USER#ID#${id}"
1404
+ },
1405
+ sk: {
1406
+ field: "SK",
1407
+ composite: ["sk"],
1408
+ template: "${sk}"
1409
+ }
1410
+ },
1411
+ /**
1412
+ * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.
1413
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.
1414
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
1415
+ * extractable (string `name`/`title` via introspection), else `<lastUpdated>#<id>`
1416
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
1417
+ */
1418
+ gsi1: {
1419
+ index: "GSI1",
1420
+ pk: {
1421
+ field: "GSI1PK",
1422
+ composite: ["gsi1Shard"],
1423
+ template: "TID#-#WID#-#RT#User#SHARD#${gsi1Shard}"
1424
+ },
1425
+ sk: {
1426
+ field: "GSI1SK",
1427
+ casing: "none",
1428
+ composite: ["gsi1sk"],
1429
+ template: "${gsi1sk}"
1430
+ }
1431
+ },
1432
+ /**
1433
+ * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.
1434
+ * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are
1435
+ * not indexed.
1436
+ */
1437
+ gsi2: {
1438
+ index: "GSI2",
1439
+ condition: (attrs) => typeof attrs.cognitoSub === "string" && attrs.cognitoSub.length > 0,
1440
+ pk: {
1441
+ field: "GSI2PK",
1442
+ casing: "none",
1443
+ composite: ["cognitoSub"],
1444
+ template: "USER#SUB#${cognitoSub}"
1445
+ },
1446
+ sk: {
1447
+ field: "GSI2SK",
1448
+ casing: "none",
1449
+ composite: [],
1450
+ template: "CURRENT"
1451
+ }
1452
+ }
1453
+ }
1454
+ });
1455
+
1456
+ // src/data/dynamo/entities/control/workspace-entity.ts
1457
+ var import_electrodb7 = require("electrodb");
1458
+ var WorkspaceEntity = new import_electrodb7.Entity({
1459
+ model: {
1460
+ entity: "workspace",
1461
+ service: "control",
1462
+ version: "01"
1463
+ },
1464
+ attributes: {
1465
+ /** Sort key sentinel. Always "CURRENT". */
1466
+ sk: {
1467
+ type: "string",
1468
+ required: true,
1469
+ default: "CURRENT"
1470
+ },
1471
+ /** Tenant that contains this workspace (required). */
1472
+ tenantId: {
1473
+ type: "string",
1474
+ required: true
1475
+ },
1476
+ /** FHIR Resource.id; logical id in URL. */
1477
+ id: {
1478
+ type: "string",
1479
+ required: true
1480
+ },
1481
+ /** Full Workspace resource serialized as JSON string. */
1482
+ resource: {
1483
+ type: "string",
1484
+ required: true
1485
+ },
1486
+ /**
1487
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1488
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1489
+ */
1490
+ summary: {
1491
+ type: "string",
1492
+ required: true
1493
+ },
1494
+ /** Version id (e.g. ULID). */
1495
+ vid: {
1496
+ type: "string",
1497
+ required: true
1498
+ },
1499
+ lastUpdated: {
1500
+ type: "string",
1501
+ required: true
1502
+ },
1503
+ gsi1Shard: gsi1ShardAttribute,
1504
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1505
+ gsi1sk: gsi1skAttribute,
1506
+ deleted: {
1507
+ type: "boolean",
1508
+ required: false
1509
+ },
1510
+ bundleId: {
1511
+ type: "string",
1512
+ required: false
1513
+ },
1514
+ msgId: {
1515
+ type: "string",
1516
+ required: false
1517
+ }
1518
+ },
1519
+ indexes: {
1520
+ /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1521
+ record: {
1522
+ pk: {
1523
+ field: "PK",
1524
+ composite: ["tenantId", "id"],
1525
+ template: "TID#${tenantId}#WORKSPACE#ID#${id}"
1526
+ },
1527
+ sk: {
1528
+ field: "SK",
1529
+ composite: ["sk"],
1530
+ template: "${sk}"
1531
+ }
1532
+ },
1533
+ /**
1534
+ * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the
1535
+ * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.
1536
+ * SK is derived via `gsi1skAttribute` — `<normalizedName>#<id>` when the resource
1537
+ * carries a `name`, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves
1538
+ * the normalized label and ISO-8601 `T`/`Z`.
1539
+ */
1540
+ gsi1: {
1541
+ index: "GSI1",
1542
+ pk: {
1543
+ field: "GSI1PK",
1544
+ composite: ["tenantId", "gsi1Shard"],
1545
+ template: "TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}"
1546
+ },
1547
+ sk: {
1548
+ field: "GSI1SK",
1549
+ casing: "none",
1550
+ composite: ["gsi1sk"],
1551
+ template: "${gsi1sk}"
1552
+ }
1553
+ }
1554
+ }
1555
+ });
1556
+
1557
+ // src/data/dynamo/dynamo-control-service.ts
1558
+ var controlPlaneEntities = {
1559
+ configuration: ConfigurationEntity,
1560
+ membership: MembershipEntity,
1561
+ role: RoleEntity,
1562
+ roleAssignment: RoleAssignmentEntity,
1563
+ tenant: TenantEntity,
1564
+ user: UserEntity,
1565
+ workspace: WorkspaceEntity
1566
+ };
1567
+ var controlPlaneService = new import_electrodb8.Service(controlPlaneEntities, {
1568
+ table: defaultTableName,
1569
+ client: dynamoClient
1570
+ });
1571
+ var DynamoControlService = {
1572
+ entities: controlPlaneService.entities
1573
+ };
1574
+ function getDynamoControlService(tableName) {
1575
+ const resolved = tableName ?? defaultTableName;
1576
+ const service = new import_electrodb8.Service(controlPlaneEntities, {
1577
+ table: resolved,
1578
+ client: dynamoClient
1579
+ });
1580
+ return {
1581
+ entities: service.entities
1582
+ };
1583
+ }
1584
+
1585
+ // src/data/errors/domain-errors.ts
1586
+ var DomainError = class extends Error {
1587
+ constructor(message, code, options) {
1588
+ super(message, options);
1589
+ this.name = this.constructor.name;
1590
+ this.code = code;
1591
+ this.details = options?.details;
1592
+ Object.setPrototypeOf(this, new.target.prototype);
1593
+ }
1594
+ };
1595
+ var NotFoundError = class extends DomainError {
1596
+ constructor(message, options) {
1597
+ super(message, "NOT_FOUND", options);
1598
+ }
1599
+ };
1600
+
1601
+ // src/data/operations/control/membership/membership-create-operation.ts
1602
+ var import_types3 = require("@openhi/types");
1603
+ async function createMembershipOperation(params) {
1604
+ const { context, body, tableName } = params;
1605
+ const service = getDynamoControlService(tableName);
1606
+ const id = body.id ?? `membership-${Date.now()}`;
1607
+ const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
1608
+ const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
1609
+ const vid = `1`;
1610
+ const resource = { resourceType: "Membership", id, ...parsedResource };
1611
+ const summary = JSON.stringify((0, import_types3.extractSummary)(resource));
1612
+ await service.entities.membership.put({
1613
+ tenantId: context.tenantId,
1614
+ id,
1615
+ resource: JSON.stringify(resource),
1616
+ summary,
1617
+ vid,
1618
+ lastUpdated
1619
+ }).go();
1620
+ return {
1621
+ id,
1622
+ resource,
1623
+ meta: { lastUpdated, versionId: vid }
1624
+ };
1625
+ }
1626
+
1627
+ // src/data/operations/control/role/role-get-by-id-operation.ts
1628
+ async function getRoleByIdOperation(params) {
1629
+ const { id, tableName } = params;
1630
+ const service = getDynamoControlService(tableName);
1631
+ const response = await service.entities.role.get({ id, sk: "CURRENT" }).go();
1632
+ const item = response.data;
1633
+ if (!item) {
1634
+ throw new NotFoundError(`Role not found: ${id}`);
1635
+ }
1636
+ const parsedResource = JSON.parse(item.resource);
1637
+ return {
1638
+ id,
1639
+ resource: { resourceType: "Role", id, ...parsedResource }
1640
+ };
1641
+ }
1642
+
1643
+ // src/data/operations/control/roleassignment/roleassignment-create-operation.ts
1644
+ var import_types4 = require("@openhi/types");
1645
+ async function createRoleAssignmentOperation(params) {
1646
+ const { context, body, tableName } = params;
1647
+ const service = getDynamoControlService(tableName);
1648
+ const id = body.id ?? `roleassignment-${Date.now()}`;
1649
+ const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
1650
+ const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
1651
+ const vid = `1`;
1652
+ const resource = { resourceType: "RoleAssignment", id, ...parsedResource };
1653
+ const summary = JSON.stringify((0, import_types4.extractSummary)(resource));
1654
+ await service.entities.roleAssignment.put({
1655
+ tenantId: context.tenantId,
1656
+ id,
1657
+ resource: JSON.stringify(resource),
1658
+ summary,
1659
+ vid,
1660
+ lastUpdated
1661
+ }).go();
1662
+ return {
1663
+ id,
1664
+ resource,
1665
+ meta: { lastUpdated, versionId: vid }
1666
+ };
1667
+ }
1668
+
1669
+ // src/data/operations/control/tenant/tenant-create-operation.ts
1670
+ var import_types5 = require("@openhi/types");
1671
+ async function createTenantOperation(params) {
1672
+ const { context, body, tableName } = params;
1673
+ const service = getDynamoControlService(tableName);
1674
+ const id = body.id ?? `tenant-${Date.now()}`;
1675
+ const lastUpdated = context.date;
1676
+ const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
1677
+ const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
1678
+ const resource = { resourceType: "Tenant", id, ...parsedResource };
1679
+ const summary = JSON.stringify((0, import_types5.extractSummary)(resource));
1680
+ await service.entities.tenant.put({
1681
+ tenantId: id,
1682
+ id,
1683
+ resource: JSON.stringify(resource),
1684
+ summary,
1685
+ vid,
1686
+ lastUpdated
1687
+ }).go();
1688
+ return { id, resource, meta: { lastUpdated, versionId: vid } };
1689
+ }
1690
+
1691
+ // src/data/operations/control/workspace/workspace-create-operation.ts
1692
+ var import_types6 = require("@openhi/types");
1693
+ async function createWorkspaceOperation(params) {
1694
+ const { context, body, tableName } = params;
1695
+ const { tenantId } = context;
1696
+ const service = getDynamoControlService(tableName);
1697
+ const id = body.id ?? `workspace-${Date.now()}`;
1698
+ const lastUpdated = context.date;
1699
+ const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
1700
+ const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
1701
+ const resource = { resourceType: "Workspace", id, ...parsedResource };
1702
+ const summary = JSON.stringify((0, import_types6.extractSummary)(resource));
1703
+ await service.entities.workspace.put({
1704
+ tenantId,
1705
+ id,
1706
+ resource: JSON.stringify(resource),
1707
+ summary,
1708
+ vid,
1709
+ lastUpdated
1710
+ }).go();
1711
+ return { id, resource, meta: { lastUpdated, versionId: vid } };
1712
+ }
1713
+
1714
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts
1715
+ var SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR = "SEED_DEMO_DATA_USER_POOL_ID";
1716
+ var errorMessage = (err) => {
1717
+ if (err instanceof Error) {
1718
+ return err.message;
1719
+ }
1720
+ return String(err);
1721
+ };
1722
+ var idForRoleCode = (code) => {
1723
+ for (const key of Object.keys(import_types7.CONTROL_PLANE_ROLE_IDS)) {
1724
+ if (import_types7.CONTROL_PLANE_ROLE_CONCEPTS[key].code === code) {
1725
+ return import_types7.CONTROL_PLANE_ROLE_IDS[key];
1726
+ }
1727
+ }
1728
+ throw new Error(`No id mapping for role code "${code}".`);
1729
+ };
1730
+ var verifySystemRolesExist = async () => {
1731
+ const probeContext = {
1732
+ tenantId: "",
1733
+ workspaceId: "",
1734
+ date: (/* @__PURE__ */ new Date(0)).toISOString(),
1735
+ actorId: "platform-deploy-bridge",
1736
+ actorName: "Platform Deploy Bridge",
1737
+ actorType: "internal-system",
1738
+ source: "step-function"
1739
+ };
1740
+ for (const id of Object.values(import_types7.CONTROL_PLANE_ROLE_IDS)) {
1741
+ try {
1742
+ await getRoleByIdOperation({ context: probeContext, id });
1743
+ } catch (err) {
1744
+ if (err instanceof NotFoundError) {
1745
+ throw new Error(
1746
+ `seed-demo-data pre-flight: control-plane Role "${id}" is missing. Ensure seed-system-data has run on this environment before retrying.`
1747
+ );
1748
+ }
1749
+ throw err;
1750
+ }
1751
+ }
1752
+ };
1753
+ var tenantResourceBody = (spec) => ({
1754
+ name: spec.tenantName,
1755
+ active: true,
1756
+ identifier: [
1757
+ demoScenarioIdentifier(spec.scenario, "tenant"),
1758
+ openhiResourceIdentifier({
1759
+ tenantId: spec.tenantId,
1760
+ workspaceId: "",
1761
+ resourceType: "Tenant",
1762
+ id: spec.tenantId
1763
+ })
1764
+ ]
1765
+ });
1766
+ var workspaceResourceBody = (spec, workspace) => ({
1767
+ name: workspace.name,
1768
+ active: true,
1769
+ identifier: [
1770
+ demoScenarioIdentifier(spec.scenario, workspace.roleSuffix),
1771
+ openhiResourceIdentifier({
1772
+ tenantId: spec.tenantId,
1773
+ workspaceId: "",
1774
+ resourceType: "Workspace",
1775
+ id: workspace.id
1776
+ })
1777
+ ],
1778
+ tenant: { reference: `Tenant/${spec.tenantId}`, type: "Tenant" }
1779
+ });
1780
+ var membershipResourceBody = (spec, user, membershipId) => ({
1781
+ identifier: [
1782
+ demoScenarioIdentifier(spec.scenario, `membership-${user.id}`),
1783
+ openhiResourceIdentifier({
1784
+ tenantId: spec.tenantId,
1785
+ workspaceId: "",
1786
+ resourceType: "Membership",
1787
+ id: membershipId
1788
+ })
1789
+ ],
1790
+ user: { reference: `User/${user.id}`, type: "User" },
1791
+ tenant: { reference: `Tenant/${spec.tenantId}`, type: "Tenant" },
1792
+ period: DEMO_PERIOD
1793
+ });
1794
+ var roleAssignmentResourceBody = (scenario, tenantId, user, roleCode, roleAssignmentId) => ({
1795
+ identifier: [
1796
+ demoScenarioIdentifier(scenario, `roleassignment-${user.id}-${roleCode}`),
1797
+ openhiResourceIdentifier({
1798
+ tenantId,
1799
+ workspaceId: "",
1800
+ resourceType: "RoleAssignment",
1801
+ id: roleAssignmentId
1802
+ })
1803
+ ],
1804
+ user: { reference: `User/${user.id}`, type: "User" },
1805
+ role: { reference: `Role/${idForRoleCode(roleCode)}`, type: "Role" },
1806
+ tenant: { reference: `Tenant/${tenantId}`, type: "Tenant" },
1807
+ period: DEMO_PERIOD
1808
+ });
1809
+ var userResourceBody = (user, cognitoSub) => ({
1810
+ resourceType: "User",
1811
+ id: user.id,
1812
+ name: [{ text: user.email }],
1813
+ status: "active",
1814
+ cognitoSub,
1815
+ currentTenant: { reference: `Tenant/${PLACEHOLDER_TENANT_ID}` },
1816
+ currentWorkspace: { reference: `Workspace/${PLACEHOLDER_WORKSPACE_ID}` }
1817
+ });
1818
+ var upsertUser = async (context, user, cognitoSub) => {
1819
+ const service = getDynamoControlService();
1820
+ const resource = userResourceBody(user, cognitoSub);
1821
+ const summary = JSON.stringify((0, import_types7.extractSummary)(resource));
1822
+ await service.entities.user.put({
1823
+ id: user.id,
1824
+ cognitoSub,
1825
+ resource: JSON.stringify(resource),
1826
+ summary,
1827
+ vid: "1",
1828
+ lastUpdated: context.date ?? (/* @__PURE__ */ new Date()).toISOString()
1829
+ }).go();
1830
+ };
1831
+ var seedDemoGraph = async (params) => {
1832
+ const { baseContext, devUsers, cognito } = params;
1833
+ for (const spec of DEMO_TENANT_SPECS) {
1834
+ const tenantContext = {
1835
+ ...baseContext,
1836
+ tenantId: spec.tenantId
1837
+ };
1838
+ await createTenantOperation({
1839
+ context: tenantContext,
1840
+ body: { id: spec.tenantId, resource: tenantResourceBody(spec) }
1841
+ });
1842
+ for (const workspace of spec.workspaces) {
1843
+ await createWorkspaceOperation({
1844
+ context: tenantContext,
1845
+ body: {
1846
+ id: workspace.id,
1847
+ resource: workspaceResourceBody(spec, workspace)
1848
+ }
1849
+ });
1850
+ }
1851
+ }
1852
+ for (const user of devUsers) {
1853
+ const cognitoSub = await cognito.ensureUser(user.email);
1854
+ await upsertUser(baseContext, user, cognitoSub);
1855
+ for (const spec of DEMO_TENANT_SPECS) {
1856
+ const tenantContext = {
1857
+ ...baseContext,
1858
+ tenantId: spec.tenantId
1859
+ };
1860
+ const membershipId = demoMembershipId(user.id, spec.tenantId);
1861
+ await createMembershipOperation({
1862
+ context: tenantContext,
1863
+ body: {
1864
+ id: membershipId,
1865
+ resource: membershipResourceBody(spec, user, membershipId)
1866
+ }
1867
+ });
1868
+ for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {
1869
+ const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);
1870
+ await createRoleAssignmentOperation({
1871
+ context: tenantContext,
1872
+ body: {
1873
+ id: raId,
1874
+ resource: roleAssignmentResourceBody(
1875
+ spec.scenario,
1876
+ spec.tenantId,
1877
+ user,
1878
+ roleCode,
1879
+ raId
1880
+ )
1881
+ }
1882
+ });
1883
+ }
1884
+ }
1885
+ const platformContext = {
1886
+ ...baseContext,
1887
+ tenantId: PLATFORM_SCOPE_TENANT_ID
1888
+ };
1889
+ const platformRoleCode = import_types7.CONTROL_PLANE_ROLE_CODE.SYSTEM_ADMIN;
1890
+ const platformRaId = demoRoleAssignmentId(
1891
+ user.id,
1892
+ PLATFORM_SCOPE_TENANT_ID,
1893
+ platformRoleCode
1894
+ );
1895
+ await createRoleAssignmentOperation({
1896
+ context: platformContext,
1897
+ body: {
1898
+ id: platformRaId,
1899
+ resource: roleAssignmentResourceBody(
1900
+ "platform",
1901
+ PLATFORM_SCOPE_TENANT_ID,
1902
+ user,
1903
+ platformRoleCode,
1904
+ platformRaId
1905
+ )
1906
+ }
1907
+ });
1908
+ }
1909
+ };
1910
+ var runSeedDemoData = async (event, deps, devUsers) => {
1911
+ const parsed = (0, import_workflows2.parseWorkflowEvent)(event, import_workflows.PlatformSystemDataSeededV1);
1912
+ const recordResult = await deps.dedupClient.recordIfAbsent({
1913
+ consumerName: SEED_DEMO_DATA_CONSUMER_NAME,
1914
+ eventId: parsed.dedupKey.eventId,
1915
+ attempt: parsed.dedupKey.attempt
1916
+ });
1917
+ if (!recordResult.recorded) {
1918
+ return;
1919
+ }
1920
+ const baseContext = {
1921
+ tenantId: "",
1922
+ workspaceId: "",
1923
+ date: parsed.envelope.occurredAt,
1924
+ actorId: "platform-deploy-bridge",
1925
+ actorName: "Platform Deploy Bridge",
1926
+ actorType: "internal-system",
1927
+ source: "step-function"
1928
+ };
1929
+ try {
1930
+ await deps.verifyRoles();
1931
+ await deps.seedDemoGraph({
1932
+ baseContext,
1933
+ devUsers,
1934
+ cognito: deps.cognito
1935
+ });
1936
+ } catch (err) {
1937
+ await deps.dedupClient.markFailed({
1938
+ consumerName: SEED_DEMO_DATA_CONSUMER_NAME,
1939
+ eventId: parsed.dedupKey.eventId,
1940
+ attempt: parsed.dedupKey.attempt,
1941
+ reason: errorMessage(err)
1942
+ });
1943
+ throw err;
1944
+ }
1945
+ };
1946
+ var devPasswordForEmail = (email) => {
1947
+ const digest = (0, import_node_crypto.createHash)("sha256").update(email).digest();
1948
+ const base64url = digest.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
1949
+ return `Dev-${base64url.slice(0, 20)}!1`;
1950
+ };
1951
+ var productionCognitoProvisioner = () => {
1952
+ const client = new import_client_cognito_identity_provider.CognitoIdentityProviderClient({});
1953
+ const userPoolId = process.env[SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR];
1954
+ if (!userPoolId || userPoolId.trim() === "") {
1955
+ throw new Error(
1956
+ `${SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR} is not set; the seed-demo-data Lambda cannot provision Cognito users without a user-pool id.`
1957
+ );
1958
+ }
1959
+ const subFromAttributes = (attrs) => {
1960
+ for (const attr of attrs ?? []) {
1961
+ if (attr.Name === "sub" && typeof attr.Value === "string") {
1962
+ return attr.Value;
1963
+ }
1964
+ }
1965
+ return void 0;
1966
+ };
1967
+ return {
1968
+ ensureUser: async (email) => {
1969
+ try {
1970
+ const created = await client.send(
1971
+ new import_client_cognito_identity_provider.AdminCreateUserCommand({
1972
+ UserPoolId: userPoolId,
1973
+ Username: email,
1974
+ MessageAction: "SUPPRESS",
1975
+ UserAttributes: [
1976
+ { Name: "email", Value: email },
1977
+ { Name: "email_verified", Value: "true" }
1978
+ ]
1979
+ })
1980
+ );
1981
+ await client.send(
1982
+ new import_client_cognito_identity_provider.AdminSetUserPasswordCommand({
1983
+ UserPoolId: userPoolId,
1984
+ Username: email,
1985
+ Password: devPasswordForEmail(email),
1986
+ Permanent: true
1987
+ })
1988
+ );
1989
+ const sub = subFromAttributes(created.User?.Attributes);
1990
+ if (!sub) {
1991
+ throw new Error(
1992
+ `AdminCreateUser response for "${email}" did not carry a sub attribute.`
1993
+ );
1994
+ }
1995
+ return sub;
1996
+ } catch (err) {
1997
+ if (err instanceof import_client_cognito_identity_provider.UsernameExistsException) {
1998
+ const got = await client.send(
1999
+ new import_client_cognito_identity_provider.AdminGetUserCommand({
2000
+ UserPoolId: userPoolId,
2001
+ Username: email
2002
+ })
2003
+ );
2004
+ const sub = subFromAttributes(got.UserAttributes);
2005
+ if (!sub) {
2006
+ throw new Error(
2007
+ `AdminGetUser response for "${email}" did not carry a sub attribute.`
2008
+ );
2009
+ }
2010
+ return sub;
2011
+ }
2012
+ throw err;
2013
+ }
2014
+ }
2015
+ };
2016
+ };
2017
+ var productionDependencies = () => {
2018
+ const dynamodb = new import_client_dynamodb2.DynamoDBClient({});
2019
+ const cognito = productionCognitoProvisioner();
2020
+ return {
2021
+ dedupClient: (0, import_workflows2.workflowDedupClient)(dynamodb),
2022
+ verifyRoles: verifySystemRolesExist,
2023
+ seedDemoGraph,
2024
+ cognito
2025
+ };
2026
+ };
2027
+ var handler = async (event) => runSeedDemoData(event, productionDependencies(), DEV_USERS);
2028
+ // Annotate the CommonJS export names for ESM import in node:
2029
+ 0 && (module.exports = {
2030
+ SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR,
2031
+ devPasswordForEmail,
2032
+ handler,
2033
+ productionCognitoProvisioner,
2034
+ runSeedDemoData,
2035
+ seedDemoGraph
2036
+ });
2037
+ //# sourceMappingURL=seed-demo-data.handler.js.map