@reventlessdev/rescript-pulumi-aws 3.0.0-alpha.0 → 3.0.0-alpha.10
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/CHANGELOG.md +84 -0
- package/package.json +4 -4
- package/src/AppSync/AppSync_Resolver_Functions.res +459 -268
- package/src/AppSync/AppSync_Resolver_Functions.res.mjs +234 -112
- package/src/Cloudwatch/Cloudwatch_EventRule.res +4 -0
- package/src/Cloudwatch/Cloudwatch_EventTarget.res +11 -0
- package/src/DynamoDb/DynamoDb_Table.res +26 -1
- package/src/DynamoDb/DynamoDb_Table.res.mjs +8 -1
- package/src/example/AlarmMailExample.res +34 -0
- package/src/example/AlarmMailExample.res.mjs +44 -0
- package/tests/AppSync_Resolver_FunctionsTest.mjs +368 -26
|
@@ -388,25 +388,99 @@ export function request(ctx) {
|
|
|
388
388
|
`;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
391
|
+
function pageWindowBudget(filtered) {
|
|
392
|
+
return `(` + filtered + ` ? (_first > 1000 ? _first : 1000) : _first + _from)`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let need = "(_backward ? _upTo : _first + _from)";
|
|
396
|
+
|
|
397
|
+
let listPageWindowBudget = `(parts.length > 0 ? (` + need + ` > 1000 ? ` + need + ` : 1000) : ` + need + `)`;
|
|
398
|
+
|
|
399
|
+
function cursorDecode(args) {
|
|
400
|
+
return `
|
|
401
|
+
let _window = null;
|
|
402
|
+
let _from = 0;
|
|
403
|
+
let _cursorPath = null;
|
|
404
|
+
if (` + args + `.after != null && ` + args + `.after !== '') {
|
|
405
|
+
const _c = JSON.parse(util.base64Decode(` + args + `.after));
|
|
406
|
+
_window = (_c.t !== undefined ? _c.t : _c.token) ?? null;
|
|
407
|
+
_from = _c.n !== undefined ? _c.n + 1 : 0;
|
|
408
|
+
_cursorPath = _c.p ?? 's';
|
|
409
|
+
}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let cursorPathGuard = `
|
|
413
|
+
if (_cursorPath !== null && _cursorPath !== (_exempt ? 's' : 'q')) {
|
|
414
|
+
util.error('This cursor belongs to a different read of this list; restart from the first page.', 'CursorPathMismatch');
|
|
415
|
+
}`;
|
|
416
|
+
|
|
417
|
+
let listCursorPreamble = `
|
|
418
|
+
const _first = ctx.args.first ?? 50;
|
|
419
|
+
let _window = null;
|
|
420
|
+
let _from = 0;
|
|
421
|
+
let _cursorPath = null;
|
|
422
|
+
let _backward = false;
|
|
423
|
+
let _upTo = -1;
|
|
424
|
+
if (ctx.args.before != null && ctx.args.before !== '') {
|
|
425
|
+
const _c = JSON.parse(util.base64Decode(ctx.args.before));
|
|
426
|
+
_window = (_c.t !== undefined ? _c.t : _c.token) ?? null;
|
|
427
|
+
_cursorPath = _c.p ?? 's';
|
|
428
|
+
_backward = true;
|
|
429
|
+
_upTo = _c.n !== undefined ? _c.n : 0;
|
|
430
|
+
_from = (_upTo - _first) > 0 ? (_upTo - _first) : 0;
|
|
431
|
+
} else if (ctx.args.after != null && ctx.args.after !== '') {
|
|
432
|
+
const _c = JSON.parse(util.base64Decode(ctx.args.after));
|
|
433
|
+
_window = (_c.t !== undefined ? _c.t : _c.token) ?? null;
|
|
434
|
+
_from = _c.n !== undefined ? _c.n + 1 : 0;
|
|
435
|
+
_cursorPath = _c.p ?? 's';
|
|
436
|
+
}`;
|
|
437
|
+
|
|
438
|
+
function connectionPageResponse(pathExpr, bidirectionalOpt) {
|
|
439
|
+
let bidirectional = bidirectionalOpt !== undefined ? bidirectionalOpt : false;
|
|
440
|
+
let tag = pathExpr !== undefined ? `, p: ` + pathExpr : "";
|
|
441
|
+
let firstDecl = bidirectional ? "" : "\n const _first = ctx.args.first ?? 50;";
|
|
442
|
+
let slice = bidirectional ? `
|
|
443
|
+
const _rest = _backward ? items.slice(_from, _upTo) : items.slice(_from);
|
|
444
|
+
const _page = _backward ? _rest : _rest.slice(0, _first);
|
|
445
|
+
// A backward page was cut from ahead, so a next page provably exists — the one
|
|
446
|
+
// the caller came from — whatever this window's tail looks like.
|
|
447
|
+
const _more = _backward ? true : _rest.length > _first;` : `
|
|
448
|
+
const _rest = items.slice(_from);
|
|
449
|
+
const _page = _rest.slice(0, _first);
|
|
450
|
+
const _more = _rest.length > _first;`;
|
|
451
|
+
let hasPrevious = bidirectional ? "_from > 0" : "!!ctx.args.after";
|
|
452
|
+
return firstDecl + slice + `
|
|
453
|
+
const _next = ctx.result?.nextToken ?? null;
|
|
454
|
+
const _lastIndex = _page.length - 1;
|
|
455
|
+
// A row's cursor names its own position. The last row of a page that closes its
|
|
456
|
+
// window is the exception — no position follows it there, so it names the next
|
|
457
|
+
// window, or resuming from it answers blank.
|
|
458
|
+
const edges = _page.map((item, i) => ({
|
|
397
459
|
node: item,
|
|
398
|
-
cursor: util.base64Encode(JSON.stringify(
|
|
460
|
+
cursor: util.base64Encode(JSON.stringify(
|
|
461
|
+
(!_more && _next && i === _lastIndex)
|
|
462
|
+
? { t: _next, n: -1` + tag + ` }
|
|
463
|
+
: { t: _window, n: _from + i` + tag + ` }
|
|
464
|
+
)),
|
|
399
465
|
}));
|
|
400
|
-
|
|
466
|
+
// A window the filter emptied leaves no row to cut a cursor from; the token is
|
|
467
|
+
// the window's, so a client can step past it rather than restart.
|
|
468
|
+
const _boundary = _next ? util.base64Encode(JSON.stringify({ t: _next, n: -1` + tag + ` })) : null;
|
|
401
469
|
return {
|
|
402
470
|
edges,
|
|
403
471
|
pageInfo: {
|
|
404
|
-
hasNextPage: !!
|
|
405
|
-
hasPreviousPage:
|
|
406
|
-
startCursor: edges.length > 0 ? edges[0].cursor :
|
|
407
|
-
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor :
|
|
472
|
+
hasNextPage: _more || !!_next,
|
|
473
|
+
hasPreviousPage: ` + hasPrevious + `,
|
|
474
|
+
startCursor: edges.length > 0 ? edges[0].cursor : _boundary,
|
|
475
|
+
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : _boundary,
|
|
408
476
|
},
|
|
409
|
-
}
|
|
477
|
+
};`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let indexConnectionResponseCode = `
|
|
481
|
+
export function response(ctx) {
|
|
482
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
483
|
+
const items = ctx.result?.items ?? [];` + cursorDecode("ctx.args") + connectionPageResponse(undefined, undefined) + `
|
|
410
484
|
}`;
|
|
411
485
|
|
|
412
486
|
let indexBackwardPagingGuard = `
|
|
@@ -414,12 +488,8 @@ let indexBackwardPagingGuard = `
|
|
|
414
488
|
util.error('Backward pagination (last/before) is not supported on by-index connections; use first/after.', 'UnsupportedPagination');
|
|
415
489
|
}`;
|
|
416
490
|
|
|
417
|
-
let indexCursorPreamble = `
|
|
418
|
-
|
|
419
|
-
if (args.after != null && args.after !== '') {
|
|
420
|
-
const parsed = JSON.parse(util.base64Decode(args.after));
|
|
421
|
-
after = parsed.token ?? null;
|
|
422
|
-
}`;
|
|
491
|
+
let indexCursorPreamble = cursorDecode("args") + `
|
|
492
|
+
const _first = args.first ?? 50;`;
|
|
423
493
|
|
|
424
494
|
let indexReservedArgs = `key === 'first' || key === 'after' || key === 'last' || key === 'before' || key === 'includeRetired' || key === 'limit' || key === 'nextToken' || key === 'forward'`;
|
|
425
495
|
|
|
@@ -458,8 +528,8 @@ export function request(ctx) {
|
|
|
458
528
|
operation: 'Query',
|
|
459
529
|
query,
|
|
460
530
|
index: '` + index + `',
|
|
461
|
-
limit: (
|
|
462
|
-
nextToken:
|
|
531
|
+
limit: ` + (`(` + "expression" + ` ? (_first > 1000 ? _first : 1000) : _first + _from)`) + `,
|
|
532
|
+
nextToken: _window,
|
|
463
533
|
scanIndexForward: (args.forward ?? true)
|
|
464
534
|
};
|
|
465
535
|
if (expression) {
|
|
@@ -515,8 +585,8 @@ export function request(ctx) {
|
|
|
515
585
|
operation: 'Query',
|
|
516
586
|
query,
|
|
517
587
|
index: '` + index + `',
|
|
518
|
-
limit: (
|
|
519
|
-
nextToken:
|
|
588
|
+
limit: ` + (`(` + "expression" + ` ? (_first > 1000 ? _first : 1000) : _first + _from)`) + `,
|
|
589
|
+
nextToken: _window,
|
|
520
590
|
scanIndexForward: (args.forward ?? true)
|
|
521
591
|
};
|
|
522
592
|
if (expression) {
|
|
@@ -539,18 +609,17 @@ export function request(ctx) {
|
|
|
539
609
|
` + resultResponseCode + `
|
|
540
610
|
`;
|
|
541
611
|
|
|
542
|
-
function listAllItemsConnection(labelField, filterFieldsOpt, rangeFieldsOpt, sortFieldsOpt, requireAttribute, ownerField, elevatedGroupsOpt, retiredField, retiredValues) {
|
|
612
|
+
function listAllItemsConnection(labelField, filterFieldsOpt, rangeFieldsOpt, sortFieldsOpt, requireAttribute, ownerField, elevatedGroupsOpt, retiredField, retiredValues, ownerIndex, ownerIndexSortField) {
|
|
543
613
|
let filterFields = filterFieldsOpt !== undefined ? filterFieldsOpt : [];
|
|
544
614
|
let rangeFields = rangeFieldsOpt !== undefined ? rangeFieldsOpt : [];
|
|
545
615
|
let sortFields = sortFieldsOpt !== undefined ? sortFieldsOpt : [];
|
|
546
616
|
let elevatedGroups = elevatedGroupsOpt !== undefined ? elevatedGroupsOpt : [];
|
|
617
|
+
let ownerIndex$1 = Stdlib_Option.isSome(ownerField) ? ownerIndex : undefined;
|
|
547
618
|
let requireAttributeClause = requireAttribute !== undefined ? `
|
|
548
619
|
names['#` + requireAttribute + `'] = '` + requireAttribute + `';
|
|
549
620
|
parts.push('attribute_exists(#` + requireAttribute + `)');` : "";
|
|
550
|
-
let
|
|
551
|
-
|
|
552
|
-
let elevatedLiteral = elevatedGroups.map(g => `'` + g + `'`).join(", ");
|
|
553
|
-
ownerClause = `
|
|
621
|
+
let elevatedLiteral = elevatedGroups.map(g => `'` + g + `'`).join(", ");
|
|
622
|
+
let ownerIdentityPreamble = `
|
|
554
623
|
// ── owner scoping (generated) ──
|
|
555
624
|
// Not read from ctx.args: a predicate deciding what the caller may see must
|
|
556
625
|
// arrive on a channel the caller cannot name, and this field is usually absent
|
|
@@ -561,15 +630,15 @@ function listAllItemsConnection(labelField, filterFieldsOpt, rangeFieldsOpt, sor
|
|
|
561
630
|
const _elevated = [` + elevatedLiteral + `];
|
|
562
631
|
// No identity at all, or an identity with no \`sub\`, is the IAM service caller
|
|
563
632
|
// the API also accepts — inside the trust boundary, and exempt.
|
|
564
|
-
const _exempt = _sub == null || _groups.some(g => _elevated.indexOf(g) >= 0)
|
|
633
|
+
const _exempt = _sub == null || _groups.some(g => _elevated.indexOf(g) >= 0);`;
|
|
634
|
+
let ownerClause = ownerField !== undefined ? (
|
|
635
|
+
ownerIndex$1 !== undefined ? ownerIdentityPreamble : ownerIdentityPreamble + `
|
|
565
636
|
if (!_exempt) {
|
|
566
637
|
names['#owner'] = '` + ownerField + `';
|
|
567
638
|
values[':owner'] = util.dynamodb.toDynamoDB(_sub);
|
|
568
639
|
parts.push('#owner = :owner');
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
ownerClause = "";
|
|
572
|
-
}
|
|
640
|
+
}`
|
|
641
|
+
) : "";
|
|
573
642
|
let retiredClause;
|
|
574
643
|
if (retiredField !== undefined) {
|
|
575
644
|
let elevatedLiteral$1 = elevatedGroups.map(g => `'` + g + `'`).join(", ");
|
|
@@ -621,13 +690,14 @@ function listAllItemsConnection(labelField, filterFieldsOpt, rangeFieldsOpt, sor
|
|
|
621
690
|
parts.push('#` + f + ` <= :` + f + `To');
|
|
622
691
|
}`).join("");
|
|
623
692
|
let sortFieldsLiteral = sortFields.map(f => `'` + f + `'`).join(", ");
|
|
693
|
+
let sortGuard = ownerIndex$1 !== undefined && ownerIndexSortField !== undefined ? "!_indexOrdered && " : "";
|
|
624
694
|
let sortBlock = sortFields.length === 0 ? "" : `
|
|
625
695
|
// Per-page sort (Scan returns items in indeterminate order; ScanIndexForward
|
|
626
696
|
// does not apply to Scan). Global ordering across pages requires v1.5 index
|
|
627
697
|
// promotion; @scanSort is per-page even then.
|
|
628
698
|
const orderBy = ctx.args.orderBy;
|
|
629
699
|
const sortFields = [` + sortFieldsLiteral + `];
|
|
630
|
-
if (orderBy && orderBy.field && sortFields.indexOf(orderBy.field) >= 0) {
|
|
700
|
+
if (` + sortGuard + `orderBy && orderBy.field && sortFields.indexOf(orderBy.field) >= 0) {
|
|
631
701
|
const field = orderBy.field;
|
|
632
702
|
const nulls = items.filter(it => it[field] === null || it[field] === undefined);
|
|
633
703
|
const nonNulls = items.filter(it => it[field] !== null && it[field] !== undefined);
|
|
@@ -642,13 +712,53 @@ function listAllItemsConnection(labelField, filterFieldsOpt, rangeFieldsOpt, sor
|
|
|
642
712
|
if (orderBy.direction === 'DESC') encoded.reverse();
|
|
643
713
|
items = encoded.map(e => JSON.parse(e.split('\\x01')[1])).concat(nulls);
|
|
644
714
|
}`;
|
|
715
|
+
let indexOrderedExpr = ownerIndex$1 !== undefined && ownerIndexSortField !== undefined ? `!_exempt && !!(ctx.args.orderBy && ctx.args.orderBy.field === '` + ownerIndexSortField + `')` : "false";
|
|
716
|
+
let requestOperation = ownerField !== undefined ? (
|
|
717
|
+
ownerIndex$1 !== undefined ? `
|
|
718
|
+
const _indexOrdered = ` + indexOrderedExpr + `;
|
|
719
|
+
const req = _exempt
|
|
720
|
+
? {
|
|
721
|
+
operation: 'Scan',
|
|
722
|
+
limit: ` + listPageWindowBudget + `,
|
|
723
|
+
nextToken: _window,
|
|
724
|
+
}
|
|
725
|
+
: {
|
|
726
|
+
operation: 'Query',
|
|
727
|
+
index: '` + ownerIndex$1 + `',
|
|
728
|
+
query: {
|
|
729
|
+
expression: '#owner = :owner',
|
|
730
|
+
expressionNames: { '#owner': '` + ownerField + `' },
|
|
731
|
+
expressionValues: { ':owner': util.dynamodb.toDynamoDB(_sub) },
|
|
732
|
+
},
|
|
733
|
+
limit: ` + listPageWindowBudget + `,
|
|
734
|
+
nextToken: _window,
|
|
735
|
+
scanIndexForward: !(_indexOrdered && ctx.args.orderBy.direction === 'DESC'),
|
|
736
|
+
};` : `
|
|
737
|
+
const req = {
|
|
738
|
+
operation: 'Scan',
|
|
739
|
+
limit: ` + listPageWindowBudget + `,
|
|
740
|
+
nextToken: _window,
|
|
741
|
+
};`
|
|
742
|
+
) : `
|
|
743
|
+
const req = {
|
|
744
|
+
operation: 'Scan',
|
|
745
|
+
limit: ` + listPageWindowBudget + `,
|
|
746
|
+
nextToken: _window,
|
|
747
|
+
};`;
|
|
748
|
+
let requestPathGuard = Stdlib_Option.isSome(ownerIndex$1) ? cursorPathGuard : "";
|
|
749
|
+
let responsePathPreamble = ownerIndex$1 !== undefined ? ownerIdentityPreamble + `
|
|
750
|
+
const _path = _exempt ? 's' : 'q';
|
|
751
|
+
const _indexOrdered = ` + indexOrderedExpr + `;` : "";
|
|
752
|
+
let pageResponse = ownerIndex$1 !== undefined ? connectionPageResponse("_path", true) : connectionPageResponse(undefined, true);
|
|
645
753
|
return importUtil + `
|
|
646
754
|
export function request(ctx) {
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
|
|
651
|
-
|
|
755
|
+
// 'before' IS served — the page is cut backward out of the window the cursor
|
|
756
|
+
// names. 'last' is not: "the last N rows of the list" needs the end of the
|
|
757
|
+
// list, which a forward-only Scan cursor cannot reach. The ordered
|
|
758
|
+
// {single}Items connection (queryItemsWithSortConditions) has a real keyset
|
|
759
|
+
// cursor and honours both — direct callers who need 'last' there.
|
|
760
|
+
if (ctx.args.last != null) {
|
|
761
|
+
util.error('last is not supported on full-list connections; page backward with first and before.', 'UnsupportedPagination');
|
|
652
762
|
}
|
|
653
763
|
const filter = ctx.args.filter ?? {};
|
|
654
764
|
const names = {};
|
|
@@ -673,18 +783,14 @@ export function request(ctx) {
|
|
|
673
783
|
});
|
|
674
784
|
parts.push('#id IN (' + placeholders.join(', ') + ')');
|
|
675
785
|
}` + filterClauses + rangeClauses + requireAttributeClause + ownerClause + retiredClause + `
|
|
676
|
-
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
operation: 'Scan',
|
|
685
|
-
limit: (ctx.args.first ?? 50),
|
|
686
|
-
nextToken: after,
|
|
687
|
-
};
|
|
786
|
+
` + listCursorPreamble + requestPathGuard + `
|
|
787
|
+
// The one page a cursor cannot reach: it begins in an earlier window, and a
|
|
788
|
+
// continuation token cannot name the one before it. Refused by itself rather
|
|
789
|
+
// than folded into the 'last' guard, because the two are different limits and a
|
|
790
|
+
// caller can act on this one (page forward from the start).
|
|
791
|
+
if (_backward && _upTo <= 0 && _window !== null) {
|
|
792
|
+
util.error('The previous page begins in an earlier read window, which this cursor cannot name; page forward from the start.', 'UnsupportedPagination');
|
|
793
|
+
}` + requestOperation + `
|
|
688
794
|
if (parts.length > 0) {
|
|
689
795
|
req.filter = {
|
|
690
796
|
expression: parts.join(' AND '),
|
|
@@ -696,35 +802,33 @@ export function request(ctx) {
|
|
|
696
802
|
}
|
|
697
803
|
export function response(ctx) {
|
|
698
804
|
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
699
|
-
let items = ctx.result?.items ?? [];` + sortBlock + `
|
|
700
|
-
// One Scan continuation token per page; encode it (with the item's page index for a
|
|
701
|
-
// unique, opaque Relay cursor). The request side decodes .token back to the raw
|
|
702
|
-
// DynamoDB nextToken (Fix 1).
|
|
703
|
-
const next = ctx.result?.nextToken ?? null;
|
|
704
|
-
const edges = items.map((item, i) => ({
|
|
705
|
-
node: item,
|
|
706
|
-
cursor: util.base64Encode(JSON.stringify({ token: next, index: i })),
|
|
707
|
-
}));
|
|
708
|
-
// A filtered/1MB-capped page can be empty or short while next is still set (limit
|
|
709
|
-
// caps rows scanned, not returned). The token is page-level, so synthesise a
|
|
710
|
-
// boundary cursor from it alone so a client can resume past a fully-filtered-out
|
|
711
|
-
// page instead of restarting from page 1 (Fix 3). The request only reads .token,
|
|
712
|
-
// so index -1 is inert on resume.
|
|
713
|
-
const boundary = next ? util.base64Encode(JSON.stringify({ token: next, index: -1 })) : null;
|
|
714
|
-
return {
|
|
715
|
-
edges,
|
|
716
|
-
pageInfo: {
|
|
717
|
-
hasNextPage: !!next,
|
|
718
|
-
hasPreviousPage: !!ctx.args.after,
|
|
719
|
-
startCursor: edges.length > 0 ? edges[0].cursor : boundary,
|
|
720
|
-
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : boundary,
|
|
721
|
-
},
|
|
722
|
-
};
|
|
805
|
+
let items = ctx.result?.items ?? [];` + responsePathPreamble + sortBlock + listCursorPreamble + pageResponse + `
|
|
723
806
|
}
|
|
724
807
|
`;
|
|
725
808
|
}
|
|
726
809
|
|
|
727
|
-
function
|
|
810
|
+
function resolvedFieldResponse(multi, ownerField, elevatedGroups, retiredField, retiredValues) {
|
|
811
|
+
if (ownerField === undefined && retiredField === undefined) {
|
|
812
|
+
if (multi) {
|
|
813
|
+
return resultListResponseCode;
|
|
814
|
+
} else {
|
|
815
|
+
return firstResultResponseCode;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
let ownerPart = ownerField !== undefined ? `
|
|
819
|
+
// ── owner scoping (generated) ──` + ownerGuardPreamble(ownerField, elevatedGroups) : "\n const _owns = (row) => true;";
|
|
820
|
+
let retiredPart = retiredGuardPreamble(retiredField, retiredValues, elevatedGroups, Stdlib_Option.isSome(ownerField));
|
|
821
|
+
let live = Stdlib_Option.isSome(retiredField) ? " && _live(_row)" : "";
|
|
822
|
+
let body = multi ? ` return (ctx.result.items ?? []).filter(_row => _owns(_row)` + live + `);` : ` const _row = ctx.result.items[0] ?? null;\n return _owns(_row)` + live + ` ? _row : null;`;
|
|
823
|
+
return `
|
|
824
|
+
export function response(ctx) {
|
|
825
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);` + ownerPart + retiredPart + `
|
|
826
|
+
` + body + `
|
|
827
|
+
}`;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function resolveId(sourceIdField, responseOpt) {
|
|
831
|
+
let response = responseOpt !== undefined ? responseOpt : firstResultResponseCode;
|
|
728
832
|
return importUtil + `
|
|
729
833
|
export function request(ctx) {
|
|
730
834
|
if (!ctx.source.` + sourceIdField + `) return runtime.earlyReturn(null);
|
|
@@ -740,11 +844,12 @@ export function request(ctx) {
|
|
|
740
844
|
scanIndexForward: (ctx.args.forward ?? true)
|
|
741
845
|
};
|
|
742
846
|
}
|
|
743
|
-
` +
|
|
847
|
+
` + response + `
|
|
744
848
|
`;
|
|
745
849
|
}
|
|
746
850
|
|
|
747
|
-
function resolveIdSort(sourceIdField, sourceSortField, targetSortField) {
|
|
851
|
+
function resolveIdSort(sourceIdField, sourceSortField, targetSortField, responseOpt) {
|
|
852
|
+
let response = responseOpt !== undefined ? responseOpt : firstResultResponseCode;
|
|
748
853
|
return importUtil + `
|
|
749
854
|
export function request(ctx) {
|
|
750
855
|
return {
|
|
@@ -762,11 +867,12 @@ export function request(ctx) {
|
|
|
762
867
|
scanIndexForward: (ctx.args.forward ?? true)
|
|
763
868
|
};
|
|
764
869
|
}
|
|
765
|
-
` +
|
|
870
|
+
` + response + `
|
|
766
871
|
`;
|
|
767
872
|
}
|
|
768
873
|
|
|
769
|
-
function resolveIdSortArgument(sourceIdField, sourceSortArgument, targetSortField) {
|
|
874
|
+
function resolveIdSortArgument(sourceIdField, sourceSortArgument, targetSortField, responseOpt) {
|
|
875
|
+
let response = responseOpt !== undefined ? responseOpt : firstResultResponseCode;
|
|
770
876
|
return importUtil + `
|
|
771
877
|
export function request(ctx) {
|
|
772
878
|
const query = ctx.args.` + sourceSortArgument + `
|
|
@@ -791,11 +897,12 @@ export function request(ctx) {
|
|
|
791
897
|
scanIndexForward: (ctx.args.forward ?? true)
|
|
792
898
|
};
|
|
793
899
|
}
|
|
794
|
-
` +
|
|
900
|
+
` + response + `
|
|
795
901
|
`;
|
|
796
902
|
}
|
|
797
903
|
|
|
798
|
-
function resolveIdByIndex(index, sourceIdField, targetIdField) {
|
|
904
|
+
function resolveIdByIndex(index, sourceIdField, targetIdField, responseOpt) {
|
|
905
|
+
let response = responseOpt !== undefined ? responseOpt : firstResultResponseCode;
|
|
799
906
|
return importUtil + `
|
|
800
907
|
export function request(ctx) {
|
|
801
908
|
return {
|
|
@@ -811,11 +918,12 @@ export function request(ctx) {
|
|
|
811
918
|
scanIndexForward: (ctx.args.forward ?? true)
|
|
812
919
|
};
|
|
813
920
|
}
|
|
814
|
-
` +
|
|
921
|
+
` + response + `
|
|
815
922
|
`;
|
|
816
923
|
}
|
|
817
924
|
|
|
818
|
-
function resolveIdByIndexSort(index, sourceIdField, sourceSortField, targetIdField, targetSortField) {
|
|
925
|
+
function resolveIdByIndexSort(index, sourceIdField, sourceSortField, targetIdField, targetSortField, responseOpt) {
|
|
926
|
+
let response = responseOpt !== undefined ? responseOpt : firstResultResponseCode;
|
|
819
927
|
return importUtil + `
|
|
820
928
|
export function request(ctx) {
|
|
821
929
|
return {
|
|
@@ -834,11 +942,12 @@ export function request(ctx) {
|
|
|
834
942
|
scanIndexForward: (ctx.args.forward ?? true)
|
|
835
943
|
};
|
|
836
944
|
}
|
|
837
|
-
` +
|
|
945
|
+
` + response + `
|
|
838
946
|
`;
|
|
839
947
|
}
|
|
840
948
|
|
|
841
|
-
function resolveIdByIndexSortArgument(index, sourceIdField, sourceSortArgument, targetIdField, targetSortField) {
|
|
949
|
+
function resolveIdByIndexSortArgument(index, sourceIdField, sourceSortArgument, targetIdField, targetSortField, responseOpt) {
|
|
950
|
+
let response = responseOpt !== undefined ? responseOpt : firstResultResponseCode;
|
|
842
951
|
return importUtil + `
|
|
843
952
|
export function request(ctx) {
|
|
844
953
|
const query = ctx.args.` + sourceSortArgument + `
|
|
@@ -864,34 +973,42 @@ export function request(ctx) {
|
|
|
864
973
|
scanIndexForward: (ctx.args.forward ?? true)
|
|
865
974
|
};
|
|
866
975
|
}
|
|
867
|
-
` +
|
|
976
|
+
` + response + `
|
|
868
977
|
`;
|
|
869
978
|
}
|
|
870
979
|
|
|
871
|
-
function resolveIds(
|
|
872
|
-
|
|
873
|
-
|
|
980
|
+
function resolveIds(idsField, sortField, ownerField, retiredField, retiredValues, $staropt$star) {
|
|
981
|
+
return tableName => {
|
|
982
|
+
let elevatedGroups = $staropt$star !== undefined ? $staropt$star : [];
|
|
983
|
+
let keysCode = sortField !== undefined ? `id => ({ id: util.dynamodb.toString(id.id), ` + sortField + `: util.dynamodb.toString(id.` + sortField + `) })` : `id => ({ id: util.dynamodb.toString(id) })`;
|
|
984
|
+
return importUtil + `
|
|
874
985
|
import { runtime } from '@aws-appsync/utils';
|
|
875
986
|
export function request(ctx) {
|
|
876
|
-
const idList = ctx.source.` + idsField +
|
|
877
|
-
if (idList
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
987
|
+
const idList = ctx.source.` + idsField + ` ?? [];
|
|
988
|
+
if (idList.length === 0) return runtime.earlyReturn([]);
|
|
989
|
+
return {
|
|
990
|
+
operation: 'BatchGetItem',
|
|
991
|
+
tables: {
|
|
992
|
+
'` + tableName + `': {
|
|
993
|
+
keys: idList.map(` + keysCode + `),
|
|
994
|
+
consistentRead: true
|
|
885
995
|
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
return { operation: 'GetItem', key: { id: util.dynamodb.toString(ctx.source.id) } };
|
|
996
|
+
}
|
|
997
|
+
};
|
|
889
998
|
}
|
|
890
999
|
export function response(ctx) {
|
|
891
|
-
if (ctx.error) util.error(ctx.error.message, ctx.error.type)
|
|
892
|
-
|
|
1000
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);` + (
|
|
1001
|
+
ownerField !== undefined ? ownerGuardPreamble(ownerField, elevatedGroups) : ""
|
|
1002
|
+
) + retiredGuardPreamble(retiredField, retiredValues, elevatedGroups, Stdlib_Option.isSome(ownerField)) + `
|
|
1003
|
+
return (ctx.result?.data?.['` + tableName + `'] ?? []).filter(item =>
|
|
1004
|
+
item !== null` + (
|
|
1005
|
+
Stdlib_Option.isSome(ownerField) ? " && _owns(item)" : ""
|
|
1006
|
+
) + (
|
|
1007
|
+
Stdlib_Option.isSome(retiredField) ? " && _live(item)" : ""
|
|
1008
|
+
) + `);
|
|
893
1009
|
}
|
|
894
1010
|
`;
|
|
1011
|
+
};
|
|
895
1012
|
}
|
|
896
1013
|
|
|
897
1014
|
function batchGetItemsByIds(ownerField, retiredField, retiredValues, $staropt$star) {
|
|
@@ -914,12 +1031,9 @@ export function request(ctx) {
|
|
|
914
1031
|
}
|
|
915
1032
|
export function response(ctx) {
|
|
916
1033
|
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
917
|
-
// BatchGetItem returns null
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
// missing id makes the entire field fail with "Cannot return null for
|
|
921
|
-
// non-nullable type" and the caller sees data=null. Filter the nulls so
|
|
922
|
-
// the field returns just the items that were found.
|
|
1034
|
+
// BatchGetItem returns null for keys that don't exist, preserving index
|
|
1035
|
+
// correspondence. The SDL declares \`[T!]!\`, so one missing id would null the
|
|
1036
|
+
// whole field — drop them and return what was found.
|
|
923
1037
|
// The owner and retirement guards the list pushes into a FilterExpression,
|
|
924
1038
|
// applied after the read because BatchGetItem has none to push into. A row the
|
|
925
1039
|
// caller does not own is dropped rather than refused, for the reason the
|
|
@@ -938,7 +1052,7 @@ export function response(ctx) {
|
|
|
938
1052
|
};
|
|
939
1053
|
}
|
|
940
1054
|
|
|
941
|
-
function refsByIds(labelField, retiredField, retiredValues, namedWhenRetired, ownerField, $staropt$star) {
|
|
1055
|
+
function refsByIds(labelField, retiredField, retiredValues, namedWhenRetired, imageExpr, ownerField, $staropt$star) {
|
|
942
1056
|
return tableName => {
|
|
943
1057
|
let elevatedGroups = $staropt$star !== undefined ? $staropt$star : [];
|
|
944
1058
|
let ownerGuard = ownerField !== undefined ? ownerGuardPreamble(ownerField, elevatedGroups) : "\n const _owns = (row) => true;";
|
|
@@ -981,6 +1095,7 @@ export function response(ctx) {
|
|
|
981
1095
|
.map(row => ({
|
|
982
1096
|
id: row.id,
|
|
983
1097
|
label: row['` + labelField + `'] ?? row.id,
|
|
1098
|
+
image: ` + Stdlib_Option.getOr(imageExpr, "null") + `,
|
|
984
1099
|
retired: _retired(row),
|
|
985
1100
|
retiredState: ` + stateExpr + `,
|
|
986
1101
|
}));
|
|
@@ -1215,6 +1330,12 @@ export {
|
|
|
1215
1330
|
queryByIndex,
|
|
1216
1331
|
queryByIndexDeletable,
|
|
1217
1332
|
queryByIndexSort,
|
|
1333
|
+
pageWindowBudget,
|
|
1334
|
+
listPageWindowBudget,
|
|
1335
|
+
cursorDecode,
|
|
1336
|
+
cursorPathGuard,
|
|
1337
|
+
listCursorPreamble,
|
|
1338
|
+
connectionPageResponse,
|
|
1218
1339
|
indexConnectionResponseCode,
|
|
1219
1340
|
indexBackwardPagingGuard,
|
|
1220
1341
|
indexCursorPreamble,
|
|
@@ -1223,6 +1344,7 @@ export {
|
|
|
1223
1344
|
queryByIndexSortFiltered,
|
|
1224
1345
|
listAllItems,
|
|
1225
1346
|
listAllItemsConnection,
|
|
1347
|
+
resolvedFieldResponse,
|
|
1226
1348
|
resolveId,
|
|
1227
1349
|
resolveIdSort,
|
|
1228
1350
|
resolveIdSortArgument,
|
|
@@ -1250,4 +1372,4 @@ export {
|
|
|
1250
1372
|
resolveIdsResult,
|
|
1251
1373
|
$$null,
|
|
1252
1374
|
}
|
|
1253
|
-
/*
|
|
1375
|
+
/* indexConnectionResponseCode Not a pure module */
|
|
@@ -44,6 +44,10 @@ module ScheduleExpression: {
|
|
|
44
44
|
type args = {
|
|
45
45
|
description?: Pulumi.Input.t<string>,
|
|
46
46
|
scheduleExpression?: ScheduleExpression.t,
|
|
47
|
+
/** JSON event pattern the rule matches on. A rule fires on a schedule or on a
|
|
48
|
+
pattern — set exactly one of the two.
|
|
49
|
+
see: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html */
|
|
50
|
+
eventPattern?: Pulumi.Input.t<string>,
|
|
47
51
|
roleArn?: Pulumi.Input.t<string>,
|
|
48
52
|
tags?: Pulumi.Input.t<Aws.tags>,
|
|
49
53
|
}
|
|
@@ -17,10 +17,21 @@ module Rule: {
|
|
|
17
17
|
let ofEventRule = (rule: Cloudwatch_EventRule.t) => rule.name->Pulumi.Output.asInput
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Rewrites a matched event before it reaches the target. `inputPaths` binds names to
|
|
21
|
+
JSON paths in the event; `inputTemplate` substitutes them where it writes `<name>`.
|
|
22
|
+
A template that is not JSON arrives as plain text — an SNS email body, say.
|
|
23
|
+
see: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html */
|
|
24
|
+
type inputTransformer = {
|
|
25
|
+
inputPaths?: dict<string>,
|
|
26
|
+
inputTemplate: Pulumi.Input.t<string>,
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
type args = {
|
|
21
30
|
rule: Rule.t,
|
|
22
31
|
arn: Pulumi.Input.t<string>,
|
|
32
|
+
/** A fixed payload, sent verbatim. Mutually exclusive with `inputTransformer`. */
|
|
23
33
|
input?: Pulumi.Input.t<string>,
|
|
34
|
+
inputTransformer?: Pulumi.Input.t<inputTransformer>,
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
@module("@pulumi/aws") @scope("cloudwatch") @new
|
|
@@ -9,7 +9,9 @@ type t = {
|
|
|
9
9
|
name: Pulumi.Output.t<string>,
|
|
10
10
|
id: Pulumi.Output.t<string>,
|
|
11
11
|
hashKey: Pulumi.Output.t<string>,
|
|
12
|
-
|
|
12
|
+
// Pulumi resolves an absent range key to `null`, which ReScript's `option`
|
|
13
|
+
// (None = undefined) does not accept. Nullable is the shape that arrives.
|
|
14
|
+
rangeKey: Pulumi.Output.t<Nullable.t<string>>,
|
|
13
15
|
streamEnabled: Pulumi.Output.t<option<bool>>,
|
|
14
16
|
streamArn: Pulumi.Output.t<string>,
|
|
15
17
|
streamLabel: Pulumi.Output.t<string>,
|
|
@@ -64,3 +66,26 @@ type args = {
|
|
|
64
66
|
@module("@pulumi/aws") @scope("dynamodb") @new
|
|
65
67
|
external make: (~name: string, ~args: args, ~opts: Pulumi.CustomResourceOptions.t=?) => table =
|
|
66
68
|
"Table"
|
|
69
|
+
|
|
70
|
+
/** Look up a table this stack does not own.
|
|
71
|
+
|
|
72
|
+
In a submodule because the lookup result's fields are a subset of [t]'s: at
|
|
73
|
+
file level the later record would win inference for `{id, name, arn}` and
|
|
74
|
+
silently retype every such destructuring of a real table.
|
|
75
|
+
|
|
76
|
+
The lookup fails the deploy when the table does not exist, which is the point —
|
|
77
|
+
a name that resolves to nothing would otherwise reach a Lambda's environment
|
|
78
|
+
and surface at sign-in. */
|
|
79
|
+
module Get = {
|
|
80
|
+
type args = {name: string}
|
|
81
|
+
|
|
82
|
+
type result = {
|
|
83
|
+
arn: string,
|
|
84
|
+
id: string,
|
|
85
|
+
name: string,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@module("@pulumi/aws") @scope("dynamodb") @val
|
|
89
|
+
external output: (~args: args, ~opts: Pulumi.InvokeOptions.t=?) => Pulumi.Output.t<result> =
|
|
90
|
+
"getTableOutput"
|
|
91
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Compile-only smoke for the EventBridge bindings: a rule that matches CloudWatch
|
|
2
|
+
// alarm state changes by pattern, and an SNS target that rewrites the matched event
|
|
3
|
+
// into a plain-text body rather than forwarding the raw JSON. Not executed at deploy
|
|
4
|
+
// time — it keeps `eventPattern` and `inputTransformer` type-checked together.
|
|
5
|
+
|
|
6
|
+
let alarmMailTopic = SNS.Topic.make(~name="example-alarm-mail")
|
|
7
|
+
|
|
8
|
+
let alarmStateChanges = Cloudwatch.EventRule.make(
|
|
9
|
+
~name="example-alarm-state-changes",
|
|
10
|
+
~args={
|
|
11
|
+
description: "CloudWatch alarm state changes, ALARM and OK only"->Pulumi.Input.make,
|
|
12
|
+
eventPattern: `{"source":["aws.cloudwatch"],"detail-type":["CloudWatch Alarm State Change"],"detail":{"state":{"value":["ALARM","OK"]}}}`->Pulumi.Input.make,
|
|
13
|
+
},
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// A template that is not JSON has to arrive quoted — the surrounding double quotes are
|
|
17
|
+
// part of the value, not ReScript syntax.
|
|
18
|
+
let mailBody: Cloudwatch.EventTarget.inputTransformer = {
|
|
19
|
+
inputPaths: Dict.fromArray([
|
|
20
|
+
("name", "$.detail.alarmName"),
|
|
21
|
+
("state", "$.detail.state.value"),
|
|
22
|
+
("reason", "$.detail.state.reason"),
|
|
23
|
+
]),
|
|
24
|
+
inputTemplate: `"<state>: <name> — <reason>"`->Pulumi.Input.make,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let _ = Cloudwatch.EventTarget.make(
|
|
28
|
+
~name="example-alarm-mail-target",
|
|
29
|
+
~args={
|
|
30
|
+
rule: Cloudwatch.EventTarget.Rule.ofEventRule(alarmStateChanges),
|
|
31
|
+
arn: alarmMailTopic.arn->Pulumi.Output.asInput,
|
|
32
|
+
inputTransformer: mailBody->Pulumi.Input.make,
|
|
33
|
+
},
|
|
34
|
+
)
|