@openhi/constructs 0.0.85 → 0.0.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{chunk-SWSN6GDD.mjs → chunk-CEOAGPYY.mjs} +1 -5
- package/lib/chunk-CEOAGPYY.mjs.map +1 -0
- package/lib/chunk-X5MHU7DA.mjs +298 -0
- package/lib/chunk-X5MHU7DA.mjs.map +1 -0
- package/lib/data-store-postgres-replication.handler.d.mts +55 -0
- package/lib/data-store-postgres-replication.handler.d.ts +55 -0
- package/lib/data-store-postgres-replication.handler.js +448 -0
- package/lib/data-store-postgres-replication.handler.js.map +1 -0
- package/lib/data-store-postgres-replication.handler.mjs +313 -0
- package/lib/data-store-postgres-replication.handler.mjs.map +1 -0
- package/lib/firehose-archive-transform.handler.js +0 -4
- package/lib/firehose-archive-transform.handler.js.map +1 -1
- package/lib/firehose-archive-transform.handler.mjs +5 -290
- package/lib/firehose-archive-transform.handler.mjs.map +1 -1
- package/lib/index.d.mts +230 -5
- package/lib/index.d.ts +231 -6
- package/lib/index.js +489 -117
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +468 -97
- package/lib/index.mjs.map +1 -1
- package/lib/post-authentication.handler.d.mts +5 -0
- package/lib/post-authentication.handler.d.ts +5 -0
- package/lib/post-authentication.handler.js +45 -0
- package/lib/post-authentication.handler.js.map +1 -0
- package/lib/post-authentication.handler.mjs +25 -0
- package/lib/post-authentication.handler.mjs.map +1 -0
- package/lib/rest-api-lambda.handler.js +636 -153
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +639 -153
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/package.json +20 -11
- package/scripts/generate-operations.js +2 -2
- package/scripts/generate-routes.js +1 -1
- package/lib/chunk-SWSN6GDD.mjs.map +0 -1
|
@@ -142,6 +142,31 @@ var dynamoClient = new DynamoDBClient({
|
|
|
142
142
|
|
|
143
143
|
// src/data/dynamo/entities/control/configuration-entity.ts
|
|
144
144
|
import { Entity } from "electrodb";
|
|
145
|
+
|
|
146
|
+
// src/data/dynamo/shard.ts
|
|
147
|
+
var SHARD_COUNT = 4;
|
|
148
|
+
function computeShard(id) {
|
|
149
|
+
let hash = 2166136261;
|
|
150
|
+
for (let i = 0; i < id.length; i++) {
|
|
151
|
+
hash ^= id.charCodeAt(i);
|
|
152
|
+
hash = Math.imul(hash, 16777619);
|
|
153
|
+
}
|
|
154
|
+
return (hash >>> 0) % SHARD_COUNT;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/data/dynamo/entities/control/control-entity-common.ts
|
|
158
|
+
var gsi1ShardAttribute = {
|
|
159
|
+
type: "string",
|
|
160
|
+
watch: ["id"],
|
|
161
|
+
set: (_val, item) => {
|
|
162
|
+
if (typeof item?.id !== "string" || item.id.length === 0) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
return String(computeShard(item.id));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// src/data/dynamo/entities/control/configuration-entity.ts
|
|
145
170
|
var ConfigurationEntity = new Entity({
|
|
146
171
|
model: {
|
|
147
172
|
entity: "configuration",
|
|
@@ -194,6 +219,14 @@ var ConfigurationEntity = new Entity({
|
|
|
194
219
|
type: "string",
|
|
195
220
|
required: true
|
|
196
221
|
},
|
|
222
|
+
/**
|
|
223
|
+
* Summary projection (key display fields as JSON string: id, key, status).
|
|
224
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
225
|
+
*/
|
|
226
|
+
summary: {
|
|
227
|
+
type: "string",
|
|
228
|
+
required: true
|
|
229
|
+
},
|
|
197
230
|
/** Version id (e.g. ULID). Tracks current version; S3 history key. */
|
|
198
231
|
vid: {
|
|
199
232
|
type: "string",
|
|
@@ -203,6 +236,7 @@ var ConfigurationEntity = new Entity({
|
|
|
203
236
|
type: "string",
|
|
204
237
|
required: true
|
|
205
238
|
},
|
|
239
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
206
240
|
deleted: {
|
|
207
241
|
type: "boolean",
|
|
208
242
|
required: false
|
|
@@ -230,19 +264,27 @@ var ConfigurationEntity = new Entity({
|
|
|
230
264
|
template: "KEY#${key}#SK#${sk}"
|
|
231
265
|
}
|
|
232
266
|
},
|
|
233
|
-
/**
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
/**
|
|
268
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a
|
|
269
|
+
* (tenant, workspace) across the four shards. Use for "list configs scoped to this tenant"
|
|
270
|
+
* (workspaceId = "-") or "list configs scoped to this workspace". Does not support
|
|
271
|
+
* hierarchical resolution in one query; use base table GetItem in fallback order
|
|
272
|
+
* (user → workspace → tenant → baseline) for that.
|
|
273
|
+
* SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).
|
|
274
|
+
* `casing: "none"` on the SK preserves ISO-8601 `T`/`Z`.
|
|
275
|
+
*/
|
|
276
|
+
gsi1: {
|
|
277
|
+
index: "GSI1",
|
|
237
278
|
pk: {
|
|
238
|
-
field: "
|
|
239
|
-
composite: ["tenantId", "workspaceId"],
|
|
240
|
-
template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration"
|
|
279
|
+
field: "GSI1PK",
|
|
280
|
+
composite: ["tenantId", "workspaceId", "gsi1Shard"],
|
|
281
|
+
template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}"
|
|
241
282
|
},
|
|
242
283
|
sk: {
|
|
243
|
-
field: "
|
|
244
|
-
|
|
245
|
-
|
|
284
|
+
field: "GSI1SK",
|
|
285
|
+
casing: "none",
|
|
286
|
+
composite: ["lastUpdated", "id"],
|
|
287
|
+
template: "${lastUpdated}#${id}"
|
|
246
288
|
}
|
|
247
289
|
}
|
|
248
290
|
}
|
|
@@ -278,6 +320,14 @@ var MembershipEntity = new Entity2({
|
|
|
278
320
|
type: "string",
|
|
279
321
|
required: true
|
|
280
322
|
},
|
|
323
|
+
/**
|
|
324
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
325
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
326
|
+
*/
|
|
327
|
+
summary: {
|
|
328
|
+
type: "string",
|
|
329
|
+
required: true
|
|
330
|
+
},
|
|
281
331
|
/** Version id (e.g. ULID). */
|
|
282
332
|
vid: {
|
|
283
333
|
type: "string",
|
|
@@ -287,6 +337,7 @@ var MembershipEntity = new Entity2({
|
|
|
287
337
|
type: "string",
|
|
288
338
|
required: true
|
|
289
339
|
},
|
|
340
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
290
341
|
deleted: {
|
|
291
342
|
type: "boolean",
|
|
292
343
|
required: false
|
|
@@ -314,19 +365,24 @@ var MembershipEntity = new Entity2({
|
|
|
314
365
|
template: "${sk}"
|
|
315
366
|
}
|
|
316
367
|
},
|
|
317
|
-
/**
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
368
|
+
/**
|
|
369
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
|
|
370
|
+
* four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
|
|
371
|
+
* SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).
|
|
372
|
+
* `casing: "none"` on the SK preserves ISO-8601 `T`/`Z`.
|
|
373
|
+
*/
|
|
374
|
+
gsi1: {
|
|
375
|
+
index: "GSI1",
|
|
321
376
|
pk: {
|
|
322
|
-
field: "
|
|
323
|
-
composite: ["tenantId"],
|
|
324
|
-
template: "TID#${tenantId}#RT#Membership"
|
|
377
|
+
field: "GSI1PK",
|
|
378
|
+
composite: ["tenantId", "gsi1Shard"],
|
|
379
|
+
template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
|
|
325
380
|
},
|
|
326
381
|
sk: {
|
|
327
|
-
field: "
|
|
328
|
-
|
|
329
|
-
|
|
382
|
+
field: "GSI1SK",
|
|
383
|
+
casing: "none",
|
|
384
|
+
composite: ["lastUpdated", "id"],
|
|
385
|
+
template: "${lastUpdated}#${id}"
|
|
330
386
|
}
|
|
331
387
|
}
|
|
332
388
|
}
|
|
@@ -357,6 +413,14 @@ var RoleEntity = new Entity3({
|
|
|
357
413
|
type: "string",
|
|
358
414
|
required: true
|
|
359
415
|
},
|
|
416
|
+
/**
|
|
417
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
418
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
419
|
+
*/
|
|
420
|
+
summary: {
|
|
421
|
+
type: "string",
|
|
422
|
+
required: true
|
|
423
|
+
},
|
|
360
424
|
/** Version id (e.g. ULID). */
|
|
361
425
|
vid: {
|
|
362
426
|
type: "string",
|
|
@@ -366,6 +430,7 @@ var RoleEntity = new Entity3({
|
|
|
366
430
|
type: "string",
|
|
367
431
|
required: true
|
|
368
432
|
},
|
|
433
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
369
434
|
deleted: {
|
|
370
435
|
type: "boolean",
|
|
371
436
|
required: false
|
|
@@ -393,19 +458,24 @@ var RoleEntity = new Entity3({
|
|
|
393
458
|
template: "${sk}"
|
|
394
459
|
}
|
|
395
460
|
},
|
|
396
|
-
/**
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
461
|
+
/**
|
|
462
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
|
|
463
|
+
* Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
|
|
464
|
+
* SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).
|
|
465
|
+
* `casing: "none"` on the SK preserves ISO-8601 `T`/`Z`.
|
|
466
|
+
*/
|
|
467
|
+
gsi1: {
|
|
468
|
+
index: "GSI1",
|
|
400
469
|
pk: {
|
|
401
|
-
field: "
|
|
402
|
-
composite: [],
|
|
403
|
-
template: "RT#Role"
|
|
470
|
+
field: "GSI1PK",
|
|
471
|
+
composite: ["gsi1Shard"],
|
|
472
|
+
template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
|
|
404
473
|
},
|
|
405
474
|
sk: {
|
|
406
|
-
field: "
|
|
407
|
-
|
|
408
|
-
|
|
475
|
+
field: "GSI1SK",
|
|
476
|
+
casing: "none",
|
|
477
|
+
composite: ["lastUpdated", "id"],
|
|
478
|
+
template: "${lastUpdated}#${id}"
|
|
409
479
|
}
|
|
410
480
|
}
|
|
411
481
|
}
|
|
@@ -441,6 +511,14 @@ var RoleAssignmentEntity = new Entity4({
|
|
|
441
511
|
type: "string",
|
|
442
512
|
required: true
|
|
443
513
|
},
|
|
514
|
+
/**
|
|
515
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
516
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
517
|
+
*/
|
|
518
|
+
summary: {
|
|
519
|
+
type: "string",
|
|
520
|
+
required: true
|
|
521
|
+
},
|
|
444
522
|
/** Version id (e.g. ULID). */
|
|
445
523
|
vid: {
|
|
446
524
|
type: "string",
|
|
@@ -450,6 +528,7 @@ var RoleAssignmentEntity = new Entity4({
|
|
|
450
528
|
type: "string",
|
|
451
529
|
required: true
|
|
452
530
|
},
|
|
531
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
453
532
|
deleted: {
|
|
454
533
|
type: "boolean",
|
|
455
534
|
required: false
|
|
@@ -477,19 +556,24 @@ var RoleAssignmentEntity = new Entity4({
|
|
|
477
556
|
template: "${sk}"
|
|
478
557
|
}
|
|
479
558
|
},
|
|
480
|
-
/**
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
559
|
+
/**
|
|
560
|
+
* GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
|
|
561
|
+
* four shards. Tenant-scoped only, so `WID#-` is a sentinel.
|
|
562
|
+
* SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).
|
|
563
|
+
* `casing: "none"` on the SK preserves ISO-8601 `T`/`Z`.
|
|
564
|
+
*/
|
|
565
|
+
gsi1: {
|
|
566
|
+
index: "GSI1",
|
|
484
567
|
pk: {
|
|
485
|
-
field: "
|
|
486
|
-
composite: ["tenantId"],
|
|
487
|
-
template: "TID#${tenantId}#RT#RoleAssignment"
|
|
568
|
+
field: "GSI1PK",
|
|
569
|
+
composite: ["tenantId", "gsi1Shard"],
|
|
570
|
+
template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
|
|
488
571
|
},
|
|
489
572
|
sk: {
|
|
490
|
-
field: "
|
|
491
|
-
|
|
492
|
-
|
|
573
|
+
field: "GSI1SK",
|
|
574
|
+
casing: "none",
|
|
575
|
+
composite: ["lastUpdated", "id"],
|
|
576
|
+
template: "${lastUpdated}#${id}"
|
|
493
577
|
}
|
|
494
578
|
}
|
|
495
579
|
}
|
|
@@ -525,6 +609,14 @@ var TenantEntity = new Entity5({
|
|
|
525
609
|
type: "string",
|
|
526
610
|
required: true
|
|
527
611
|
},
|
|
612
|
+
/**
|
|
613
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
614
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
615
|
+
*/
|
|
616
|
+
summary: {
|
|
617
|
+
type: "string",
|
|
618
|
+
required: true
|
|
619
|
+
},
|
|
528
620
|
/** Version id (e.g. ULID). */
|
|
529
621
|
vid: {
|
|
530
622
|
type: "string",
|
|
@@ -534,6 +626,7 @@ var TenantEntity = new Entity5({
|
|
|
534
626
|
type: "string",
|
|
535
627
|
required: true
|
|
536
628
|
},
|
|
629
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
537
630
|
deleted: {
|
|
538
631
|
type: "boolean",
|
|
539
632
|
required: false
|
|
@@ -561,19 +654,24 @@ var TenantEntity = new Entity5({
|
|
|
561
654
|
template: "${sk}"
|
|
562
655
|
}
|
|
563
656
|
},
|
|
564
|
-
/**
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
657
|
+
/**
|
|
658
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.
|
|
659
|
+
* Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`
|
|
660
|
+
* sentinels precede `RT#Tenant#SHARD#<n>`. SK is `<ISO-8601 lastUpdated>#<id>` (control-plane
|
|
661
|
+
* unlabeled per DR-004). `casing: "none"` on the SK preserves ISO-8601 `T`/`Z`.
|
|
662
|
+
*/
|
|
663
|
+
gsi1: {
|
|
664
|
+
index: "GSI1",
|
|
568
665
|
pk: {
|
|
569
|
-
field: "
|
|
570
|
-
composite: [],
|
|
571
|
-
template: "RT#Tenant"
|
|
666
|
+
field: "GSI1PK",
|
|
667
|
+
composite: ["gsi1Shard"],
|
|
668
|
+
template: "TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}"
|
|
572
669
|
},
|
|
573
670
|
sk: {
|
|
574
|
-
field: "
|
|
575
|
-
|
|
576
|
-
|
|
671
|
+
field: "GSI1SK",
|
|
672
|
+
casing: "none",
|
|
673
|
+
composite: ["lastUpdated", "id"],
|
|
674
|
+
template: "${lastUpdated}#${id}"
|
|
577
675
|
}
|
|
578
676
|
}
|
|
579
677
|
}
|
|
@@ -604,6 +702,22 @@ var UserEntity = new Entity6({
|
|
|
604
702
|
type: "string",
|
|
605
703
|
required: true
|
|
606
704
|
},
|
|
705
|
+
/**
|
|
706
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
707
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
708
|
+
*/
|
|
709
|
+
summary: {
|
|
710
|
+
type: "string",
|
|
711
|
+
required: true
|
|
712
|
+
},
|
|
713
|
+
/**
|
|
714
|
+
* Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the
|
|
715
|
+
* Post Confirmation Lambda (#770) lands; required thereafter.
|
|
716
|
+
*/
|
|
717
|
+
cognitoSub: {
|
|
718
|
+
type: "string",
|
|
719
|
+
required: false
|
|
720
|
+
},
|
|
607
721
|
/** Version id (e.g. ULID). */
|
|
608
722
|
vid: {
|
|
609
723
|
type: "string",
|
|
@@ -613,6 +727,7 @@ var UserEntity = new Entity6({
|
|
|
613
727
|
type: "string",
|
|
614
728
|
required: true
|
|
615
729
|
},
|
|
730
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
616
731
|
deleted: {
|
|
617
732
|
type: "boolean",
|
|
618
733
|
required: false
|
|
@@ -640,19 +755,45 @@ var UserEntity = new Entity6({
|
|
|
640
755
|
template: "${sk}"
|
|
641
756
|
}
|
|
642
757
|
},
|
|
643
|
-
/**
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
758
|
+
/**
|
|
759
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.
|
|
760
|
+
* Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.
|
|
761
|
+
* SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).
|
|
762
|
+
* `casing: "none"` on the SK preserves ISO-8601 `T`/`Z` characters.
|
|
763
|
+
*/
|
|
764
|
+
gsi1: {
|
|
765
|
+
index: "GSI1",
|
|
647
766
|
pk: {
|
|
648
|
-
field: "
|
|
649
|
-
composite: [],
|
|
650
|
-
template: "RT#User"
|
|
767
|
+
field: "GSI1PK",
|
|
768
|
+
composite: ["gsi1Shard"],
|
|
769
|
+
template: "TID#-#WID#-#RT#User#SHARD#${gsi1Shard}"
|
|
651
770
|
},
|
|
652
771
|
sk: {
|
|
653
|
-
field: "
|
|
654
|
-
|
|
655
|
-
|
|
772
|
+
field: "GSI1SK",
|
|
773
|
+
casing: "none",
|
|
774
|
+
composite: ["lastUpdated", "id"],
|
|
775
|
+
template: "${lastUpdated}#${id}"
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
/**
|
|
779
|
+
* GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.
|
|
780
|
+
* `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are
|
|
781
|
+
* not indexed.
|
|
782
|
+
*/
|
|
783
|
+
gsi2: {
|
|
784
|
+
index: "GSI2",
|
|
785
|
+
condition: (attrs) => typeof attrs.cognitoSub === "string" && attrs.cognitoSub.length > 0,
|
|
786
|
+
pk: {
|
|
787
|
+
field: "GSI2PK",
|
|
788
|
+
casing: "none",
|
|
789
|
+
composite: ["cognitoSub"],
|
|
790
|
+
template: "USER#SUB#${cognitoSub}"
|
|
791
|
+
},
|
|
792
|
+
sk: {
|
|
793
|
+
field: "GSI2SK",
|
|
794
|
+
casing: "none",
|
|
795
|
+
composite: [],
|
|
796
|
+
template: "CURRENT"
|
|
656
797
|
}
|
|
657
798
|
}
|
|
658
799
|
}
|
|
@@ -688,6 +829,14 @@ var WorkspaceEntity = new Entity7({
|
|
|
688
829
|
type: "string",
|
|
689
830
|
required: true
|
|
690
831
|
},
|
|
832
|
+
/**
|
|
833
|
+
* Summary projection (key display fields as JSON string: id, displayName, status).
|
|
834
|
+
* Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
|
|
835
|
+
*/
|
|
836
|
+
summary: {
|
|
837
|
+
type: "string",
|
|
838
|
+
required: true
|
|
839
|
+
},
|
|
691
840
|
/** Version id (e.g. ULID). */
|
|
692
841
|
vid: {
|
|
693
842
|
type: "string",
|
|
@@ -697,6 +846,7 @@ var WorkspaceEntity = new Entity7({
|
|
|
697
846
|
type: "string",
|
|
698
847
|
required: true
|
|
699
848
|
},
|
|
849
|
+
gsi1Shard: gsi1ShardAttribute,
|
|
700
850
|
deleted: {
|
|
701
851
|
type: "boolean",
|
|
702
852
|
required: false
|
|
@@ -724,19 +874,24 @@ var WorkspaceEntity = new Entity7({
|
|
|
724
874
|
template: "${sk}"
|
|
725
875
|
}
|
|
726
876
|
},
|
|
727
|
-
/**
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
877
|
+
/**
|
|
878
|
+
* GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the
|
|
879
|
+
* four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.
|
|
880
|
+
* SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).
|
|
881
|
+
* `casing: "none"` on the SK preserves ISO-8601 `T`/`Z`.
|
|
882
|
+
*/
|
|
883
|
+
gsi1: {
|
|
884
|
+
index: "GSI1",
|
|
731
885
|
pk: {
|
|
732
|
-
field: "
|
|
733
|
-
composite: ["tenantId"],
|
|
734
|
-
template: "TID#${tenantId}#RT#Workspace"
|
|
886
|
+
field: "GSI1PK",
|
|
887
|
+
composite: ["tenantId", "gsi1Shard"],
|
|
888
|
+
template: "TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}"
|
|
735
889
|
},
|
|
736
890
|
sk: {
|
|
737
|
-
field: "
|
|
738
|
-
|
|
739
|
-
|
|
891
|
+
field: "GSI1SK",
|
|
892
|
+
casing: "none",
|
|
893
|
+
composite: ["lastUpdated", "id"],
|
|
894
|
+
template: "${lastUpdated}#${id}"
|
|
740
895
|
}
|
|
741
896
|
}
|
|
742
897
|
}
|
|
@@ -790,6 +945,7 @@ async function createConfigurationOperation(params) {
|
|
|
790
945
|
const roleId = body.roleId ?? context.roleId ?? "-";
|
|
791
946
|
const lastUpdated = body.lastUpdated ?? date;
|
|
792
947
|
const vid = body.vid ?? (date.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36));
|
|
948
|
+
const summary = JSON.stringify({ resourceType: "Configuration", id, key });
|
|
793
949
|
const service = getDynamoControlService(tableName);
|
|
794
950
|
await service.entities.configuration.put({
|
|
795
951
|
tenantId,
|
|
@@ -799,6 +955,7 @@ async function createConfigurationOperation(params) {
|
|
|
799
955
|
key,
|
|
800
956
|
id,
|
|
801
957
|
resource: compressResource(resourceStr),
|
|
958
|
+
summary,
|
|
802
959
|
vid,
|
|
803
960
|
lastUpdated,
|
|
804
961
|
sk: SK
|
|
@@ -1205,22 +1362,25 @@ async function listConfigurationsOperation(params) {
|
|
|
1205
1362
|
const { context, tableName } = params;
|
|
1206
1363
|
const { tenantId, workspaceId } = context;
|
|
1207
1364
|
const service = getDynamoControlService(tableName);
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1365
|
+
const shardResults = await Promise.all(
|
|
1366
|
+
Array.from(
|
|
1367
|
+
{ length: SHARD_COUNT },
|
|
1368
|
+
(_, shard) => service.entities.configuration.query.gsi1({ tenantId, workspaceId, gsi1Shard: String(shard) }).go()
|
|
1369
|
+
)
|
|
1370
|
+
);
|
|
1371
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
1372
|
+
const resource = JSON.parse(decompressResource(item.resource));
|
|
1373
|
+
return {
|
|
1374
|
+
id: item.id,
|
|
1375
|
+
key: item.key,
|
|
1376
|
+
resource: {
|
|
1377
|
+
resourceType: "Configuration",
|
|
1213
1378
|
id: item.id,
|
|
1214
1379
|
key: item.key,
|
|
1215
|
-
resource
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
resource
|
|
1220
|
-
}
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
);
|
|
1380
|
+
resource
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
});
|
|
1224
1384
|
return { entries };
|
|
1225
1385
|
}
|
|
1226
1386
|
|
|
@@ -1394,8 +1554,14 @@ async function updateConfigurationOperation(params) {
|
|
|
1394
1554
|
const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
|
|
1395
1555
|
const lastUpdated = body.lastUpdated ?? date;
|
|
1396
1556
|
const nextVid = existing.data.vid != null ? String(Number(existing.data.vid) + 1) : date.replace(/[-:T.Z]/g, "").slice(0, 12) || "2";
|
|
1557
|
+
const summary = JSON.stringify({
|
|
1558
|
+
resourceType: "Configuration",
|
|
1559
|
+
id: existing.data.id,
|
|
1560
|
+
key: existing.data.key
|
|
1561
|
+
});
|
|
1397
1562
|
await service.entities.configuration.patch({ tenantId, workspaceId, userId: actorId, roleId, key: id, sk: SK4 }).set({
|
|
1398
1563
|
resource: compressResource(resourceStr),
|
|
1564
|
+
summary,
|
|
1399
1565
|
lastUpdated,
|
|
1400
1566
|
vid: nextVid
|
|
1401
1567
|
}).go();
|
|
@@ -1473,6 +1639,7 @@ router.delete("/:id", deleteConfigurationRoute);
|
|
|
1473
1639
|
import express2 from "express";
|
|
1474
1640
|
|
|
1475
1641
|
// src/data/operations/control/membership/membership-create-operation.ts
|
|
1642
|
+
import { extractSummary } from "@openhi/types";
|
|
1476
1643
|
async function createMembershipOperation(params) {
|
|
1477
1644
|
const { context, body, tableName } = params;
|
|
1478
1645
|
const service = getDynamoControlService(tableName);
|
|
@@ -1480,20 +1647,19 @@ async function createMembershipOperation(params) {
|
|
|
1480
1647
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
1481
1648
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1482
1649
|
const vid = `1`;
|
|
1650
|
+
const resource = { resourceType: "Membership", id, ...parsedResource };
|
|
1651
|
+
const summary = JSON.stringify(extractSummary(resource));
|
|
1483
1652
|
await service.entities.membership.put({
|
|
1484
1653
|
tenantId: context.tenantId,
|
|
1485
1654
|
id,
|
|
1486
|
-
resource: JSON.stringify(
|
|
1487
|
-
|
|
1488
|
-
id,
|
|
1489
|
-
...parsedResource
|
|
1490
|
-
}),
|
|
1655
|
+
resource: JSON.stringify(resource),
|
|
1656
|
+
summary,
|
|
1491
1657
|
vid,
|
|
1492
1658
|
lastUpdated
|
|
1493
1659
|
}).go();
|
|
1494
1660
|
return {
|
|
1495
1661
|
id,
|
|
1496
|
-
resource
|
|
1662
|
+
resource,
|
|
1497
1663
|
meta: { lastUpdated, versionId: vid }
|
|
1498
1664
|
};
|
|
1499
1665
|
}
|
|
@@ -1601,12 +1767,21 @@ async function getMembershipByIdRoute(req, res) {
|
|
|
1601
1767
|
async function listMembershipsOperation(params) {
|
|
1602
1768
|
const { context, tableName } = params;
|
|
1603
1769
|
const service = getDynamoControlService(tableName);
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1770
|
+
const shardResults = await Promise.all(
|
|
1771
|
+
Array.from(
|
|
1772
|
+
{ length: SHARD_COUNT },
|
|
1773
|
+
(_, shard) => service.entities.membership.query.gsi1({ tenantId: context.tenantId, gsi1Shard: String(shard) }).go()
|
|
1774
|
+
)
|
|
1775
|
+
);
|
|
1776
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
1606
1777
|
const parsedResource = JSON.parse(item.resource);
|
|
1607
1778
|
return {
|
|
1608
1779
|
id: item.id,
|
|
1609
|
-
resource: {
|
|
1780
|
+
resource: {
|
|
1781
|
+
resourceType: "Membership",
|
|
1782
|
+
id: item.id,
|
|
1783
|
+
...parsedResource
|
|
1784
|
+
}
|
|
1610
1785
|
};
|
|
1611
1786
|
});
|
|
1612
1787
|
return { entries };
|
|
@@ -1635,6 +1810,7 @@ async function listMembershipsRoute(req, res) {
|
|
|
1635
1810
|
}
|
|
1636
1811
|
|
|
1637
1812
|
// src/data/operations/control/membership/membership-update-operation.ts
|
|
1813
|
+
import { extractSummary as extractSummary2 } from "@openhi/types";
|
|
1638
1814
|
async function updateMembershipOperation(params) {
|
|
1639
1815
|
const { context, id, body, tableName } = params;
|
|
1640
1816
|
const service = getDynamoControlService(tableName);
|
|
@@ -1645,20 +1821,19 @@ async function updateMembershipOperation(params) {
|
|
|
1645
1821
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
1646
1822
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1647
1823
|
const vid = `${Date.now()}`;
|
|
1824
|
+
const resource = { resourceType: "Membership", id, ...parsedResource };
|
|
1825
|
+
const summary = JSON.stringify(extractSummary2(resource));
|
|
1648
1826
|
await service.entities.membership.put({
|
|
1649
1827
|
tenantId: context.tenantId,
|
|
1650
1828
|
id,
|
|
1651
|
-
resource: JSON.stringify(
|
|
1652
|
-
|
|
1653
|
-
id,
|
|
1654
|
-
...parsedResource
|
|
1655
|
-
}),
|
|
1829
|
+
resource: JSON.stringify(resource),
|
|
1830
|
+
summary,
|
|
1656
1831
|
vid,
|
|
1657
1832
|
lastUpdated
|
|
1658
1833
|
}).go();
|
|
1659
1834
|
return {
|
|
1660
1835
|
id,
|
|
1661
|
-
resource
|
|
1836
|
+
resource,
|
|
1662
1837
|
meta: { lastUpdated, versionId: vid }
|
|
1663
1838
|
};
|
|
1664
1839
|
}
|
|
@@ -1715,6 +1890,7 @@ router2.delete("/:id", deleteMembershipRoute);
|
|
|
1715
1890
|
import express3 from "express";
|
|
1716
1891
|
|
|
1717
1892
|
// src/data/operations/control/role/role-create-operation.ts
|
|
1893
|
+
import { extractSummary as extractSummary3 } from "@openhi/types";
|
|
1718
1894
|
async function createRoleOperation(params) {
|
|
1719
1895
|
const { context, body, tableName } = params;
|
|
1720
1896
|
const service = getDynamoControlService(tableName);
|
|
@@ -1722,15 +1898,18 @@ async function createRoleOperation(params) {
|
|
|
1722
1898
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
1723
1899
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1724
1900
|
const vid = `1`;
|
|
1901
|
+
const resource = { resourceType: "Role", id, ...parsedResource };
|
|
1902
|
+
const summary = JSON.stringify(extractSummary3(resource));
|
|
1725
1903
|
await service.entities.role.put({
|
|
1726
1904
|
id,
|
|
1727
|
-
resource: JSON.stringify(
|
|
1905
|
+
resource: JSON.stringify(resource),
|
|
1906
|
+
summary,
|
|
1728
1907
|
vid,
|
|
1729
1908
|
lastUpdated
|
|
1730
1909
|
}).go();
|
|
1731
1910
|
return {
|
|
1732
1911
|
id,
|
|
1733
|
-
resource
|
|
1912
|
+
resource,
|
|
1734
1913
|
meta: { lastUpdated, versionId: vid }
|
|
1735
1914
|
};
|
|
1736
1915
|
}
|
|
@@ -1838,8 +2017,13 @@ async function getRoleByIdRoute(req, res) {
|
|
|
1838
2017
|
async function listRolesOperation(params) {
|
|
1839
2018
|
const { tableName } = params;
|
|
1840
2019
|
const service = getDynamoControlService(tableName);
|
|
1841
|
-
const
|
|
1842
|
-
|
|
2020
|
+
const shardResults = await Promise.all(
|
|
2021
|
+
Array.from(
|
|
2022
|
+
{ length: SHARD_COUNT },
|
|
2023
|
+
(_, shard) => service.entities.role.query.gsi1({ gsi1Shard: String(shard) }).go()
|
|
2024
|
+
)
|
|
2025
|
+
);
|
|
2026
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
1843
2027
|
const parsedResource = JSON.parse(item.resource);
|
|
1844
2028
|
return {
|
|
1845
2029
|
id: item.id,
|
|
@@ -1872,6 +2056,7 @@ async function listRolesRoute(req, res) {
|
|
|
1872
2056
|
}
|
|
1873
2057
|
|
|
1874
2058
|
// src/data/operations/control/role/role-update-operation.ts
|
|
2059
|
+
import { extractSummary as extractSummary4 } from "@openhi/types";
|
|
1875
2060
|
async function updateRoleOperation(params) {
|
|
1876
2061
|
const { context, id, body, tableName } = params;
|
|
1877
2062
|
const service = getDynamoControlService(tableName);
|
|
@@ -1882,15 +2067,18 @@ async function updateRoleOperation(params) {
|
|
|
1882
2067
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
1883
2068
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1884
2069
|
const vid = `${Date.now()}`;
|
|
2070
|
+
const resource = { resourceType: "Role", id, ...parsedResource };
|
|
2071
|
+
const summary = JSON.stringify(extractSummary4(resource));
|
|
1885
2072
|
await service.entities.role.put({
|
|
1886
2073
|
id,
|
|
1887
|
-
resource: JSON.stringify(
|
|
2074
|
+
resource: JSON.stringify(resource),
|
|
2075
|
+
summary,
|
|
1888
2076
|
vid,
|
|
1889
2077
|
lastUpdated
|
|
1890
2078
|
}).go();
|
|
1891
2079
|
return {
|
|
1892
2080
|
id,
|
|
1893
|
-
resource
|
|
2081
|
+
resource,
|
|
1894
2082
|
meta: { lastUpdated, versionId: vid }
|
|
1895
2083
|
};
|
|
1896
2084
|
}
|
|
@@ -1947,6 +2135,7 @@ router3.delete("/:id", deleteRoleRoute);
|
|
|
1947
2135
|
import express4 from "express";
|
|
1948
2136
|
|
|
1949
2137
|
// src/data/operations/control/roleassignment/roleassignment-create-operation.ts
|
|
2138
|
+
import { extractSummary as extractSummary5 } from "@openhi/types";
|
|
1950
2139
|
async function createRoleAssignmentOperation(params) {
|
|
1951
2140
|
const { context, body, tableName } = params;
|
|
1952
2141
|
const service = getDynamoControlService(tableName);
|
|
@@ -1954,20 +2143,19 @@ async function createRoleAssignmentOperation(params) {
|
|
|
1954
2143
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
1955
2144
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1956
2145
|
const vid = `1`;
|
|
2146
|
+
const resource = { resourceType: "RoleAssignment", id, ...parsedResource };
|
|
2147
|
+
const summary = JSON.stringify(extractSummary5(resource));
|
|
1957
2148
|
await service.entities.roleAssignment.put({
|
|
1958
2149
|
tenantId: context.tenantId,
|
|
1959
2150
|
id,
|
|
1960
|
-
resource: JSON.stringify(
|
|
1961
|
-
|
|
1962
|
-
id,
|
|
1963
|
-
...parsedResource
|
|
1964
|
-
}),
|
|
2151
|
+
resource: JSON.stringify(resource),
|
|
2152
|
+
summary,
|
|
1965
2153
|
vid,
|
|
1966
2154
|
lastUpdated
|
|
1967
2155
|
}).go();
|
|
1968
2156
|
return {
|
|
1969
2157
|
id,
|
|
1970
|
-
resource
|
|
2158
|
+
resource,
|
|
1971
2159
|
meta: { lastUpdated, versionId: vid }
|
|
1972
2160
|
};
|
|
1973
2161
|
}
|
|
@@ -2075,8 +2263,13 @@ async function getRoleAssignmentByIdRoute(req, res) {
|
|
|
2075
2263
|
async function listRoleAssignmentsOperation(params) {
|
|
2076
2264
|
const { context, tableName } = params;
|
|
2077
2265
|
const service = getDynamoControlService(tableName);
|
|
2078
|
-
const
|
|
2079
|
-
|
|
2266
|
+
const shardResults = await Promise.all(
|
|
2267
|
+
Array.from(
|
|
2268
|
+
{ length: SHARD_COUNT },
|
|
2269
|
+
(_, shard) => service.entities.roleAssignment.query.gsi1({ tenantId: context.tenantId, gsi1Shard: String(shard) }).go()
|
|
2270
|
+
)
|
|
2271
|
+
);
|
|
2272
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
2080
2273
|
const parsedResource = JSON.parse(item.resource);
|
|
2081
2274
|
return {
|
|
2082
2275
|
id: item.id,
|
|
@@ -2113,6 +2306,7 @@ async function listRoleAssignmentsRoute(req, res) {
|
|
|
2113
2306
|
}
|
|
2114
2307
|
|
|
2115
2308
|
// src/data/operations/control/roleassignment/roleassignment-update-operation.ts
|
|
2309
|
+
import { extractSummary as extractSummary6 } from "@openhi/types";
|
|
2116
2310
|
async function updateRoleAssignmentOperation(params) {
|
|
2117
2311
|
const { context, id, body, tableName } = params;
|
|
2118
2312
|
const service = getDynamoControlService(tableName);
|
|
@@ -2123,20 +2317,19 @@ async function updateRoleAssignmentOperation(params) {
|
|
|
2123
2317
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2124
2318
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2125
2319
|
const vid = `${Date.now()}`;
|
|
2320
|
+
const resource = { resourceType: "RoleAssignment", id, ...parsedResource };
|
|
2321
|
+
const summary = JSON.stringify(extractSummary6(resource));
|
|
2126
2322
|
await service.entities.roleAssignment.put({
|
|
2127
2323
|
tenantId: context.tenantId,
|
|
2128
2324
|
id,
|
|
2129
|
-
resource: JSON.stringify(
|
|
2130
|
-
|
|
2131
|
-
id,
|
|
2132
|
-
...parsedResource
|
|
2133
|
-
}),
|
|
2325
|
+
resource: JSON.stringify(resource),
|
|
2326
|
+
summary,
|
|
2134
2327
|
vid,
|
|
2135
2328
|
lastUpdated
|
|
2136
2329
|
}).go();
|
|
2137
2330
|
return {
|
|
2138
2331
|
id,
|
|
2139
|
-
resource
|
|
2332
|
+
resource,
|
|
2140
2333
|
meta: { lastUpdated, versionId: vid }
|
|
2141
2334
|
};
|
|
2142
2335
|
}
|
|
@@ -2193,6 +2386,7 @@ router4.delete("/:id", deleteRoleAssignmentRoute);
|
|
|
2193
2386
|
import express5 from "express";
|
|
2194
2387
|
|
|
2195
2388
|
// src/data/operations/control/tenant/tenant-create-operation.ts
|
|
2389
|
+
import { extractSummary as extractSummary7 } from "@openhi/types";
|
|
2196
2390
|
async function createTenantOperation(params) {
|
|
2197
2391
|
const { context, body, tableName } = params;
|
|
2198
2392
|
const service = getDynamoControlService(tableName);
|
|
@@ -2201,10 +2395,12 @@ async function createTenantOperation(params) {
|
|
|
2201
2395
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
2202
2396
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2203
2397
|
const resource = { resourceType: "Tenant", id, ...parsedResource };
|
|
2398
|
+
const summary = JSON.stringify(extractSummary7(resource));
|
|
2204
2399
|
await service.entities.tenant.put({
|
|
2205
2400
|
tenantId: id,
|
|
2206
2401
|
id,
|
|
2207
2402
|
resource: JSON.stringify(resource),
|
|
2403
|
+
summary,
|
|
2208
2404
|
vid,
|
|
2209
2405
|
lastUpdated
|
|
2210
2406
|
}).go();
|
|
@@ -2313,8 +2509,13 @@ async function getTenantByIdRoute(req, res) {
|
|
|
2313
2509
|
async function listTenantsOperation(params) {
|
|
2314
2510
|
const { tableName } = params;
|
|
2315
2511
|
const service = getDynamoControlService(tableName);
|
|
2316
|
-
const
|
|
2317
|
-
|
|
2512
|
+
const shardResults = await Promise.all(
|
|
2513
|
+
Array.from(
|
|
2514
|
+
{ length: SHARD_COUNT },
|
|
2515
|
+
(_, shard) => service.entities.tenant.query.gsi1({ gsi1Shard: String(shard) }).go()
|
|
2516
|
+
)
|
|
2517
|
+
);
|
|
2518
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
2318
2519
|
const parsed = JSON.parse(item.resource);
|
|
2319
2520
|
return {
|
|
2320
2521
|
id: item.id,
|
|
@@ -2347,6 +2548,7 @@ async function listTenantsRoute(req, res) {
|
|
|
2347
2548
|
}
|
|
2348
2549
|
|
|
2349
2550
|
// src/data/operations/control/tenant/tenant-update-operation.ts
|
|
2551
|
+
import { extractSummary as extractSummary8 } from "@openhi/types";
|
|
2350
2552
|
async function updateTenantOperation(params) {
|
|
2351
2553
|
const { context, id, body, tableName } = params;
|
|
2352
2554
|
const service = getDynamoControlService(tableName);
|
|
@@ -2364,7 +2566,8 @@ async function updateTenantOperation(params) {
|
|
|
2364
2566
|
resourceType: "Tenant",
|
|
2365
2567
|
id
|
|
2366
2568
|
};
|
|
2367
|
-
|
|
2569
|
+
const summary = JSON.stringify(extractSummary8(updated));
|
|
2570
|
+
await service.entities.tenant.patch({ tenantId: id, sk: "CURRENT" }).set({ resource: JSON.stringify(updated), summary, vid, lastUpdated }).go();
|
|
2368
2571
|
return { id, resource: updated, meta: { lastUpdated, versionId: vid } };
|
|
2369
2572
|
}
|
|
2370
2573
|
|
|
@@ -2420,6 +2623,7 @@ router5.delete("/:id", deleteTenantRoute);
|
|
|
2420
2623
|
import express6 from "express";
|
|
2421
2624
|
|
|
2422
2625
|
// src/data/operations/control/user/user-create-operation.ts
|
|
2626
|
+
import { extractSummary as extractSummary9 } from "@openhi/types";
|
|
2423
2627
|
async function createUserOperation(params) {
|
|
2424
2628
|
const { context, body, tableName } = params;
|
|
2425
2629
|
const service = getDynamoControlService(tableName);
|
|
@@ -2427,15 +2631,18 @@ async function createUserOperation(params) {
|
|
|
2427
2631
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2428
2632
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2429
2633
|
const vid = `1`;
|
|
2634
|
+
const resource = { resourceType: "User", id, ...parsedResource };
|
|
2635
|
+
const summary = JSON.stringify(extractSummary9(resource));
|
|
2430
2636
|
await service.entities.user.put({
|
|
2431
2637
|
id,
|
|
2432
|
-
resource: JSON.stringify(
|
|
2638
|
+
resource: JSON.stringify(resource),
|
|
2639
|
+
summary,
|
|
2433
2640
|
vid,
|
|
2434
2641
|
lastUpdated
|
|
2435
2642
|
}).go();
|
|
2436
2643
|
return {
|
|
2437
2644
|
id,
|
|
2438
|
-
resource
|
|
2645
|
+
resource,
|
|
2439
2646
|
meta: { lastUpdated, versionId: vid }
|
|
2440
2647
|
};
|
|
2441
2648
|
}
|
|
@@ -2543,8 +2750,13 @@ async function getUserByIdRoute(req, res) {
|
|
|
2543
2750
|
async function listUsersOperation(params) {
|
|
2544
2751
|
const { tableName } = params;
|
|
2545
2752
|
const service = getDynamoControlService(tableName);
|
|
2546
|
-
const
|
|
2547
|
-
|
|
2753
|
+
const shardResults = await Promise.all(
|
|
2754
|
+
Array.from(
|
|
2755
|
+
{ length: SHARD_COUNT },
|
|
2756
|
+
(_, shard) => service.entities.user.query.gsi1({ gsi1Shard: String(shard) }).go()
|
|
2757
|
+
)
|
|
2758
|
+
);
|
|
2759
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
2548
2760
|
const parsedResource = JSON.parse(item.resource);
|
|
2549
2761
|
return {
|
|
2550
2762
|
id: item.id,
|
|
@@ -2577,6 +2789,7 @@ async function listUsersRoute(req, res) {
|
|
|
2577
2789
|
}
|
|
2578
2790
|
|
|
2579
2791
|
// src/data/operations/control/user/user-update-operation.ts
|
|
2792
|
+
import { extractSummary as extractSummary10 } from "@openhi/types";
|
|
2580
2793
|
async function updateUserOperation(params) {
|
|
2581
2794
|
const { context, id, body, tableName } = params;
|
|
2582
2795
|
const service = getDynamoControlService(tableName);
|
|
@@ -2587,15 +2800,18 @@ async function updateUserOperation(params) {
|
|
|
2587
2800
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2588
2801
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2589
2802
|
const vid = `${Date.now()}`;
|
|
2803
|
+
const resource = { resourceType: "User", id, ...parsedResource };
|
|
2804
|
+
const summary = JSON.stringify(extractSummary10(resource));
|
|
2590
2805
|
await service.entities.user.put({
|
|
2591
2806
|
id,
|
|
2592
|
-
resource: JSON.stringify(
|
|
2807
|
+
resource: JSON.stringify(resource),
|
|
2808
|
+
summary,
|
|
2593
2809
|
vid,
|
|
2594
2810
|
lastUpdated
|
|
2595
2811
|
}).go();
|
|
2596
2812
|
return {
|
|
2597
2813
|
id,
|
|
2598
|
-
resource
|
|
2814
|
+
resource,
|
|
2599
2815
|
meta: { lastUpdated, versionId: vid }
|
|
2600
2816
|
};
|
|
2601
2817
|
}
|
|
@@ -2652,6 +2868,7 @@ router6.delete("/:id", deleteUserRoute);
|
|
|
2652
2868
|
import express7 from "express";
|
|
2653
2869
|
|
|
2654
2870
|
// src/data/operations/control/workspace/workspace-create-operation.ts
|
|
2871
|
+
import { extractSummary as extractSummary11 } from "@openhi/types";
|
|
2655
2872
|
async function createWorkspaceOperation(params) {
|
|
2656
2873
|
const { context, body, tableName } = params;
|
|
2657
2874
|
const { tenantId } = context;
|
|
@@ -2661,7 +2878,15 @@ async function createWorkspaceOperation(params) {
|
|
|
2661
2878
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
2662
2879
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2663
2880
|
const resource = { resourceType: "Workspace", id, ...parsedResource };
|
|
2664
|
-
|
|
2881
|
+
const summary = JSON.stringify(extractSummary11(resource));
|
|
2882
|
+
await service.entities.workspace.put({
|
|
2883
|
+
tenantId,
|
|
2884
|
+
id,
|
|
2885
|
+
resource: JSON.stringify(resource),
|
|
2886
|
+
summary,
|
|
2887
|
+
vid,
|
|
2888
|
+
lastUpdated
|
|
2889
|
+
}).go();
|
|
2665
2890
|
return { id, resource, meta: { lastUpdated, versionId: vid } };
|
|
2666
2891
|
}
|
|
2667
2892
|
|
|
@@ -2770,8 +2995,13 @@ async function listWorkspacesOperation(params) {
|
|
|
2770
2995
|
const { context, tableName } = params;
|
|
2771
2996
|
const { tenantId } = context;
|
|
2772
2997
|
const service = getDynamoControlService(tableName);
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2998
|
+
const shardResults = await Promise.all(
|
|
2999
|
+
Array.from(
|
|
3000
|
+
{ length: SHARD_COUNT },
|
|
3001
|
+
(_, shard) => service.entities.workspace.query.gsi1({ tenantId, gsi1Shard: String(shard) }).go()
|
|
3002
|
+
)
|
|
3003
|
+
);
|
|
3004
|
+
const entries = shardResults.flatMap((shardResult) => shardResult.data ?? []).map((item) => {
|
|
2775
3005
|
const parsed = JSON.parse(item.resource);
|
|
2776
3006
|
return {
|
|
2777
3007
|
id: item.id,
|
|
@@ -2804,6 +3034,7 @@ async function listWorkspacesRoute(req, res) {
|
|
|
2804
3034
|
}
|
|
2805
3035
|
|
|
2806
3036
|
// src/data/operations/control/workspace/workspace-update-operation.ts
|
|
3037
|
+
import { extractSummary as extractSummary12 } from "@openhi/types";
|
|
2807
3038
|
async function updateWorkspaceOperation(params) {
|
|
2808
3039
|
const { context, id, body, tableName } = params;
|
|
2809
3040
|
const { tenantId } = context;
|
|
@@ -2822,7 +3053,8 @@ async function updateWorkspaceOperation(params) {
|
|
|
2822
3053
|
resourceType: "Workspace",
|
|
2823
3054
|
id
|
|
2824
3055
|
};
|
|
2825
|
-
|
|
3056
|
+
const summary = JSON.stringify(extractSummary12(updated));
|
|
3057
|
+
await service.entities.workspace.patch({ tenantId, id, sk: "CURRENT" }).set({ resource: JSON.stringify(updated), summary, vid, lastUpdated }).go();
|
|
2826
3058
|
return { id, resource: updated, meta: { lastUpdated, versionId: vid } };
|
|
2827
3059
|
}
|
|
2828
3060
|
|
|
@@ -2934,6 +3166,18 @@ var dataEntityAttributes = {
|
|
|
2934
3166
|
type: "string",
|
|
2935
3167
|
required: true
|
|
2936
3168
|
},
|
|
3169
|
+
/**
|
|
3170
|
+
* Summary projection of the FHIR resource as a JSON string (uncompressed). Populated on every
|
|
3171
|
+
* write via `extractSummary(resource)` so GSI projections can surface list/lookup data without
|
|
3172
|
+
* reading the compressed `resource` blob. Kept uncompressed because the summary is small and
|
|
3173
|
+
* must be fast to retrieve without encode/decode overhead.
|
|
3174
|
+
*
|
|
3175
|
+
* @see sites/www-docs/content/architecture/adr/2026-04-17-02-fhir-summary-projection-for-gsi-access-patterns.md
|
|
3176
|
+
*/
|
|
3177
|
+
summary: {
|
|
3178
|
+
type: "string",
|
|
3179
|
+
required: true
|
|
3180
|
+
},
|
|
2937
3181
|
/** Version id (e.g. ULID). Tracks current version; S3 history key. */
|
|
2938
3182
|
vid: {
|
|
2939
3183
|
type: "string",
|
|
@@ -2943,6 +3187,41 @@ var dataEntityAttributes = {
|
|
|
2943
3187
|
type: "string",
|
|
2944
3188
|
required: true
|
|
2945
3189
|
},
|
|
3190
|
+
/**
|
|
3191
|
+
* Shard index segment for the GSI1 partition key. Computed deterministically from `id`
|
|
3192
|
+
* via `computeShard` so updates always land on the same shard. Stored as a string because
|
|
3193
|
+
* it appears as a literal segment in the GSI1 PK template; the underlying value is 0..3.
|
|
3194
|
+
* Not `required` because the value is derived via `watch`/`set`; ElectroDB's required-field
|
|
3195
|
+
* check runs before watch propagation, so callers must not fail validation on a derived field.
|
|
3196
|
+
*
|
|
3197
|
+
* @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md — GSI1 (sharded)
|
|
3198
|
+
*/
|
|
3199
|
+
gsi1Shard: {
|
|
3200
|
+
type: "string",
|
|
3201
|
+
watch: ["id"],
|
|
3202
|
+
set: (_val, item) => {
|
|
3203
|
+
if (typeof item?.id !== "string" || item.id.length === 0) {
|
|
3204
|
+
return void 0;
|
|
3205
|
+
}
|
|
3206
|
+
return String(computeShard(item.id));
|
|
3207
|
+
}
|
|
3208
|
+
},
|
|
3209
|
+
/**
|
|
3210
|
+
* GSI1 sort key. Written as the index's SK verbatim so list endpoints can
|
|
3211
|
+
* use `BEGINS_WITH` for prefix queries (e.g. `?name=Sm` against Patient).
|
|
3212
|
+
* Computed at write time via `extractSortKey(resource)` per DR-004:
|
|
3213
|
+
* - Labeled types (LABEL_PATHS): `<normalizedLabel>#<id>`
|
|
3214
|
+
* - Unlabeled types: `<ISO-8601 lastUpdated>#<id>`
|
|
3215
|
+
* The factory deliberately does not derive this from `lastUpdated`/`id`
|
|
3216
|
+
* — that would lock every type into the unlabeled fallback and defeat
|
|
3217
|
+
* label-based BEGINS_WITH on labeled types.
|
|
3218
|
+
*
|
|
3219
|
+
* @see openhi-planning DR-004
|
|
3220
|
+
*/
|
|
3221
|
+
gsi1sk: {
|
|
3222
|
+
type: "string",
|
|
3223
|
+
required: true
|
|
3224
|
+
},
|
|
2946
3225
|
deleted: {
|
|
2947
3226
|
type: "boolean",
|
|
2948
3227
|
required: false
|
|
@@ -2977,18 +3256,26 @@ function createDataEntity(entity, resourceTypeLabel) {
|
|
|
2977
3256
|
composite: ["sk"]
|
|
2978
3257
|
}
|
|
2979
3258
|
},
|
|
2980
|
-
/**
|
|
2981
|
-
|
|
2982
|
-
|
|
3259
|
+
/**
|
|
3260
|
+
* GSI1 — Unified Sharded List: list all resources of this type in a workspace; reads fan
|
|
3261
|
+
* out across the four shards and merge by SK. SK is the writer-supplied `gsi1sk` verbatim
|
|
3262
|
+
* (per DR-004) so labeled types serve `BEGINS_WITH` prefix queries on the natural label.
|
|
3263
|
+
* `casing: "none"` is required on the SK because the writer (`extractSortKey`) already
|
|
3264
|
+
* applies DR-004 normalization — ElectroDB's default lowercasing would mangle the
|
|
3265
|
+
* ISO-8601 unlabeled fallback (`T`/`Z` → `t`/`z`).
|
|
3266
|
+
*/
|
|
3267
|
+
gsi1: {
|
|
3268
|
+
index: "GSI1",
|
|
2983
3269
|
pk: {
|
|
2984
|
-
field: "
|
|
2985
|
-
composite: ["tenantId", "workspaceId"],
|
|
2986
|
-
template: `TID#\${tenantId}#WID#\${workspaceId}#RT#${resourceTypeLabel}`
|
|
3270
|
+
field: "GSI1PK",
|
|
3271
|
+
composite: ["tenantId", "workspaceId", "gsi1Shard"],
|
|
3272
|
+
template: `TID#\${tenantId}#WID#\${workspaceId}#RT#${resourceTypeLabel}#SHARD#\${gsi1Shard}`
|
|
2987
3273
|
},
|
|
2988
3274
|
sk: {
|
|
2989
|
-
field: "
|
|
2990
|
-
|
|
2991
|
-
|
|
3275
|
+
field: "GSI1SK",
|
|
3276
|
+
casing: "none",
|
|
3277
|
+
composite: ["gsi1sk"],
|
|
3278
|
+
template: `\${gsi1sk}`
|
|
2992
3279
|
}
|
|
2993
3280
|
}
|
|
2994
3281
|
}
|
|
@@ -3882,6 +4169,7 @@ function getDynamoDataService(tableName) {
|
|
|
3882
4169
|
}
|
|
3883
4170
|
|
|
3884
4171
|
// src/data/operations/data-operations-common.ts
|
|
4172
|
+
import { extractSortKey, extractSummary as extractSummary13 } from "@openhi/types";
|
|
3885
4173
|
var DATA_ENTITY_SK = "CURRENT";
|
|
3886
4174
|
async function getDataEntityById(entity, tenantId, workspaceId, id, resourceLabel) {
|
|
3887
4175
|
const result = await entity.get({
|
|
@@ -3910,28 +4198,40 @@ async function deleteDataEntityById(entity, tenantId, workspaceId, id) {
|
|
|
3910
4198
|
}).go();
|
|
3911
4199
|
}
|
|
3912
4200
|
async function listDataEntitiesByWorkspace(entity, tenantId, workspaceId) {
|
|
3913
|
-
const
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
4201
|
+
const shardResults = await Promise.all(
|
|
4202
|
+
Array.from(
|
|
4203
|
+
{ length: SHARD_COUNT },
|
|
4204
|
+
(_, shard) => entity.query.gsi1({ tenantId, workspaceId, gsi1Shard: String(shard) }).go()
|
|
4205
|
+
)
|
|
4206
|
+
);
|
|
4207
|
+
const entries = [];
|
|
4208
|
+
for (const shardResult of shardResults) {
|
|
4209
|
+
for (const item of shardResult.data ?? []) {
|
|
4210
|
+
const parsed = JSON.parse(decompressResource(item.resource));
|
|
4211
|
+
entries.push({
|
|
4212
|
+
id: item.id,
|
|
4213
|
+
resource: { ...parsed, id: item.id }
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
3922
4217
|
return { entries };
|
|
3923
4218
|
}
|
|
3924
4219
|
async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourceWithAudit, fallbackDate) {
|
|
3925
4220
|
const lastUpdated = resourceWithAudit.meta?.lastUpdated ?? fallbackDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3926
4221
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
4222
|
+
const resourceLike = resourceWithAudit;
|
|
4223
|
+
const summary = JSON.stringify(extractSummary13(resourceLike));
|
|
4224
|
+
const gsi1sk = extractSortKey(resourceLike);
|
|
3927
4225
|
await entity.put({
|
|
3928
4226
|
sk: DATA_ENTITY_SK,
|
|
3929
4227
|
tenantId,
|
|
3930
4228
|
workspaceId,
|
|
3931
4229
|
id,
|
|
3932
4230
|
resource: compressResource(JSON.stringify(resourceWithAudit)),
|
|
4231
|
+
summary,
|
|
3933
4232
|
vid,
|
|
3934
|
-
lastUpdated
|
|
4233
|
+
lastUpdated,
|
|
4234
|
+
gsi1sk
|
|
3935
4235
|
}).go();
|
|
3936
4236
|
return {
|
|
3937
4237
|
id,
|
|
@@ -3978,6 +4278,9 @@ async function updateDataEntityById(entity, tenantId, workspaceId, id, resourceL
|
|
|
3978
4278
|
}
|
|
3979
4279
|
const existingStr = decompressResource(existing.data.resource);
|
|
3980
4280
|
const { resource, lastUpdated } = buildPatched(existingStr);
|
|
4281
|
+
const resourceLike = resource;
|
|
4282
|
+
const summary = JSON.stringify(extractSummary13(resourceLike));
|
|
4283
|
+
const gsi1sk = extractSortKey(resourceLike);
|
|
3981
4284
|
await entity.patch({
|
|
3982
4285
|
tenantId,
|
|
3983
4286
|
workspaceId,
|
|
@@ -3985,7 +4288,9 @@ async function updateDataEntityById(entity, tenantId, workspaceId, id, resourceL
|
|
|
3985
4288
|
sk: DATA_ENTITY_SK
|
|
3986
4289
|
}).set({
|
|
3987
4290
|
resource: compressResource(JSON.stringify(resource)),
|
|
3988
|
-
|
|
4291
|
+
summary,
|
|
4292
|
+
lastUpdated,
|
|
4293
|
+
gsi1sk
|
|
3989
4294
|
}).go();
|
|
3990
4295
|
return {
|
|
3991
4296
|
id,
|
|
@@ -12629,9 +12934,168 @@ async function listEncountersOperation(params) {
|
|
|
12629
12934
|
);
|
|
12630
12935
|
}
|
|
12631
12936
|
|
|
12937
|
+
// src/data/postgres/data-api-postgres-query-runner.ts
|
|
12938
|
+
import {
|
|
12939
|
+
ExecuteStatementCommand,
|
|
12940
|
+
RDSDataClient
|
|
12941
|
+
} from "@aws-sdk/client-rds-data";
|
|
12942
|
+
var DataApiPostgresQueryRunner = class {
|
|
12943
|
+
constructor(options) {
|
|
12944
|
+
this.client = options.client ?? new RDSDataClient({});
|
|
12945
|
+
this.clusterArn = options.clusterArn;
|
|
12946
|
+
this.secretArn = options.secretArn;
|
|
12947
|
+
this.database = options.database;
|
|
12948
|
+
this.schema = options.schema;
|
|
12949
|
+
}
|
|
12950
|
+
async query(sql, params) {
|
|
12951
|
+
const out = await this.client.send(
|
|
12952
|
+
new ExecuteStatementCommand({
|
|
12953
|
+
resourceArn: this.clusterArn,
|
|
12954
|
+
secretArn: this.secretArn,
|
|
12955
|
+
database: this.database,
|
|
12956
|
+
schema: this.schema,
|
|
12957
|
+
sql,
|
|
12958
|
+
parameters: params.map(toSqlParameter),
|
|
12959
|
+
// Results as named columns so we can map them back to JS objects.
|
|
12960
|
+
includeResultMetadata: true,
|
|
12961
|
+
// Encode JSONB results as strings, then parse client-side. Without
|
|
12962
|
+
// this, Data API returns JSON values inline as their underlying types
|
|
12963
|
+
// and complex JSONB columns get clipped.
|
|
12964
|
+
formatRecordsAs: "JSON"
|
|
12965
|
+
})
|
|
12966
|
+
);
|
|
12967
|
+
if (!out.formattedRecords) {
|
|
12968
|
+
return [];
|
|
12969
|
+
}
|
|
12970
|
+
return JSON.parse(out.formattedRecords);
|
|
12971
|
+
}
|
|
12972
|
+
};
|
|
12973
|
+
function toSqlParameter(param) {
|
|
12974
|
+
if (param.value === null) {
|
|
12975
|
+
return { name: param.name, value: { isNull: true } };
|
|
12976
|
+
}
|
|
12977
|
+
const v = param.value;
|
|
12978
|
+
let field;
|
|
12979
|
+
if (typeof v === "string") {
|
|
12980
|
+
field = { stringValue: v };
|
|
12981
|
+
} else if (typeof v === "boolean") {
|
|
12982
|
+
field = { booleanValue: v };
|
|
12983
|
+
} else if (Number.isInteger(v)) {
|
|
12984
|
+
field = { longValue: v };
|
|
12985
|
+
} else {
|
|
12986
|
+
field = { doubleValue: v };
|
|
12987
|
+
}
|
|
12988
|
+
return { name: param.name, value: field };
|
|
12989
|
+
}
|
|
12990
|
+
|
|
12991
|
+
// src/data/postgres/default-postgres-query-runner.ts
|
|
12992
|
+
var cached;
|
|
12993
|
+
function readEnv(name) {
|
|
12994
|
+
const v = process.env[name]?.trim();
|
|
12995
|
+
if (!v) {
|
|
12996
|
+
throw new Error(
|
|
12997
|
+
`Missing required env var for default PostgresQueryRunner: ${name}`
|
|
12998
|
+
);
|
|
12999
|
+
}
|
|
13000
|
+
return v;
|
|
13001
|
+
}
|
|
13002
|
+
function getDefaultPostgresQueryRunner() {
|
|
13003
|
+
if (!cached) {
|
|
13004
|
+
cached = new DataApiPostgresQueryRunner({
|
|
13005
|
+
clusterArn: readEnv("OPENHI_PG_CLUSTER_ARN"),
|
|
13006
|
+
secretArn: readEnv("OPENHI_PG_SECRET_ARN"),
|
|
13007
|
+
database: readEnv("OPENHI_PG_DATABASE"),
|
|
13008
|
+
schema: readEnv("OPENHI_PG_SCHEMA")
|
|
13009
|
+
});
|
|
13010
|
+
}
|
|
13011
|
+
return cached;
|
|
13012
|
+
}
|
|
13013
|
+
|
|
13014
|
+
// src/data/operations/data/encounter/encounter-search-by-patient-operation.ts
|
|
13015
|
+
var DEFAULT_LIMIT = 100;
|
|
13016
|
+
function buildOpenHiResourceUrn(opts) {
|
|
13017
|
+
return `urn:ohi:${opts.tenantId}:${opts.workspaceId}:${opts.resourceType}:${opts.resourceId}`;
|
|
13018
|
+
}
|
|
13019
|
+
function buildSearchEncountersByPatientSql() {
|
|
13020
|
+
return [
|
|
13021
|
+
"SELECT resource_id AS id, resource",
|
|
13022
|
+
"FROM resources",
|
|
13023
|
+
"WHERE tenant_id = :tenantId",
|
|
13024
|
+
" AND workspace_id = :workspaceId",
|
|
13025
|
+
" AND resource_type = 'Encounter'",
|
|
13026
|
+
" AND deleted_at IS NULL",
|
|
13027
|
+
" AND (resource @> :containmentRelative::jsonb",
|
|
13028
|
+
" OR resource @> :containmentUrn::jsonb)",
|
|
13029
|
+
"ORDER BY last_updated DESC",
|
|
13030
|
+
"LIMIT :limit;"
|
|
13031
|
+
].join("\n");
|
|
13032
|
+
}
|
|
13033
|
+
async function searchEncountersByPatientOperation(params) {
|
|
13034
|
+
const { context, patientId } = params;
|
|
13035
|
+
const { tenantId, workspaceId } = context;
|
|
13036
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
13037
|
+
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
13038
|
+
const containmentRelative = JSON.stringify({
|
|
13039
|
+
subject: { reference: `Patient/${patientId}` }
|
|
13040
|
+
});
|
|
13041
|
+
const containmentUrn = JSON.stringify({
|
|
13042
|
+
subject: {
|
|
13043
|
+
reference: buildOpenHiResourceUrn({
|
|
13044
|
+
tenantId,
|
|
13045
|
+
workspaceId,
|
|
13046
|
+
resourceType: "Patient",
|
|
13047
|
+
resourceId: patientId
|
|
13048
|
+
})
|
|
13049
|
+
}
|
|
13050
|
+
});
|
|
13051
|
+
const rows = await runner.query(
|
|
13052
|
+
buildSearchEncountersByPatientSql(),
|
|
13053
|
+
[
|
|
13054
|
+
{ name: "tenantId", value: tenantId },
|
|
13055
|
+
{ name: "workspaceId", value: workspaceId },
|
|
13056
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
13057
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
13058
|
+
{ name: "limit", value: limit }
|
|
13059
|
+
]
|
|
13060
|
+
);
|
|
13061
|
+
const entries = rows.map((row) => ({
|
|
13062
|
+
id: row.id,
|
|
13063
|
+
resource: {
|
|
13064
|
+
...row.resource,
|
|
13065
|
+
id: row.id
|
|
13066
|
+
}
|
|
13067
|
+
}));
|
|
13068
|
+
return { entries };
|
|
13069
|
+
}
|
|
13070
|
+
|
|
12632
13071
|
// src/data/rest-api/routes/data/encounter/encounter-list-route.ts
|
|
13072
|
+
function singleStringQueryParam(req, name) {
|
|
13073
|
+
const v = req.query[name];
|
|
13074
|
+
if (typeof v !== "string") {
|
|
13075
|
+
return void 0;
|
|
13076
|
+
}
|
|
13077
|
+
const trimmed = v.trim();
|
|
13078
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
13079
|
+
}
|
|
12633
13080
|
async function listEncountersRoute(req, res) {
|
|
12634
13081
|
const ctx = req.openhiContext;
|
|
13082
|
+
const patientId = singleStringQueryParam(req, "patient");
|
|
13083
|
+
if (patientId) {
|
|
13084
|
+
try {
|
|
13085
|
+
const result = await searchEncountersByPatientOperation({
|
|
13086
|
+
context: ctx,
|
|
13087
|
+
patientId
|
|
13088
|
+
});
|
|
13089
|
+
const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
|
|
13090
|
+
return res.json(bundle);
|
|
13091
|
+
} catch (err) {
|
|
13092
|
+
return sendOperationOutcome500(
|
|
13093
|
+
res,
|
|
13094
|
+
err,
|
|
13095
|
+
"GET /Encounter?patient= search error:"
|
|
13096
|
+
);
|
|
13097
|
+
}
|
|
13098
|
+
}
|
|
12635
13099
|
try {
|
|
12636
13100
|
const result = await listEncountersOperation({ context: ctx });
|
|
12637
13101
|
const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
|
|
@@ -24548,6 +25012,7 @@ import { ulid as ulid99 } from "ulid";
|
|
|
24548
25012
|
// src/data/import-patient.ts
|
|
24549
25013
|
import { readFileSync } from "fs";
|
|
24550
25014
|
import { resolve } from "path";
|
|
25015
|
+
import { extractSummary as extractSummary14 } from "@openhi/types";
|
|
24551
25016
|
function extractPatient(parsed) {
|
|
24552
25017
|
if (parsed && typeof parsed === "object" && "resourceType" in parsed) {
|
|
24553
25018
|
const root = parsed;
|
|
@@ -24613,6 +25078,9 @@ function patientToPutAttrs(patient, options) {
|
|
|
24613
25078
|
workspaceId,
|
|
24614
25079
|
id: patient.id,
|
|
24615
25080
|
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
25081
|
+
summary: JSON.stringify(
|
|
25082
|
+
extractSummary14(patientWithMeta)
|
|
25083
|
+
),
|
|
24616
25084
|
vid: lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36),
|
|
24617
25085
|
lastUpdated,
|
|
24618
25086
|
identifierSystem: "",
|
|
@@ -33933,6 +34401,7 @@ app.use(
|
|
|
33933
34401
|
"/MedicinalProductPackaged",
|
|
33934
34402
|
"/MedicinalProductPharmaceutical",
|
|
33935
34403
|
"/MedicinalProductUndesirableEffect",
|
|
34404
|
+
"/Membership",
|
|
33936
34405
|
"/MessageDefinition",
|
|
33937
34406
|
"/MessageHeader",
|
|
33938
34407
|
"/MolecularSequence",
|
|
@@ -33962,6 +34431,8 @@ app.use(
|
|
|
33962
34431
|
"/ResearchSubject",
|
|
33963
34432
|
"/RiskAssessment",
|
|
33964
34433
|
"/RiskEvidenceSynthesis",
|
|
34434
|
+
"/Role",
|
|
34435
|
+
"/RoleAssignment",
|
|
33965
34436
|
"/Schedule",
|
|
33966
34437
|
"/ServiceRequest",
|
|
33967
34438
|
"/SearchParameter",
|
|
@@ -33981,12 +34452,15 @@ app.use(
|
|
|
33981
34452
|
"/SupplyDelivery",
|
|
33982
34453
|
"/SupplyRequest",
|
|
33983
34454
|
"/Task",
|
|
34455
|
+
"/Tenant",
|
|
33984
34456
|
"/TerminologyCapabilities",
|
|
33985
34457
|
"/TestReport",
|
|
33986
34458
|
"/TestScript",
|
|
34459
|
+
"/User",
|
|
33987
34460
|
"/ValueSet",
|
|
33988
34461
|
"/VerificationResult",
|
|
33989
|
-
"/VisionPrescription"
|
|
34462
|
+
"/VisionPrescription",
|
|
34463
|
+
"/Workspace"
|
|
33990
34464
|
],
|
|
33991
34465
|
openHiContextMiddleware
|
|
33992
34466
|
);
|
|
@@ -34148,6 +34622,18 @@ app.use("/RoleAssignment", router4);
|
|
|
34148
34622
|
app.use("/Tenant", router5);
|
|
34149
34623
|
app.use("/User", router6);
|
|
34150
34624
|
app.use("/Workspace", router7);
|
|
34625
|
+
app.use((_req, res) => {
|
|
34626
|
+
res.status(404).json({
|
|
34627
|
+
resourceType: "OperationOutcome",
|
|
34628
|
+
issue: [
|
|
34629
|
+
{
|
|
34630
|
+
severity: "error",
|
|
34631
|
+
code: "not-supported",
|
|
34632
|
+
diagnostics: "The requested endpoint or resource type is not supported by this server."
|
|
34633
|
+
}
|
|
34634
|
+
]
|
|
34635
|
+
});
|
|
34636
|
+
});
|
|
34151
34637
|
|
|
34152
34638
|
// src/data/lambda/rest-api-lambda.handler.ts
|
|
34153
34639
|
var handler = serverlessExpress({ app });
|