lambder 7.2.5 → 8.0.2
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 +1021 -3
- package/README.md +43 -21
- package/dist/api/LambderApiAnswer.d.ts +18 -22
- package/dist/api/LambderApiAnswer.js +6 -7
- package/dist/api/LambderApiCallContext.d.ts +21 -8
- package/dist/api/LambderApiCallContext.js +22 -4
- package/dist/api/LambderApiDefinition.d.ts +4 -3
- package/dist/api/LambderApiEnvelope.d.ts +14 -9
- package/dist/api/LambderApiEnvelope.js +33 -34
- package/dist/api/LambderApiGuards.d.ts +78 -51
- package/dist/api/LambderApiGuards.js +34 -36
- package/dist/api/LambderApiIdempotency.d.ts +74 -61
- package/dist/api/LambderApiIdempotency.js +226 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +77 -39
- package/dist/api/LambderApiPipeline.js +135 -62
- package/dist/api/LambderApiRateLimits.d.ts +208 -54
- package/dist/api/LambderApiRateLimits.js +197 -108
- package/dist/api/LambderApiRequest.d.ts +27 -21
- package/dist/api/LambderApiRequest.js +26 -19
- package/dist/api/LambderApiSignature.d.ts +12 -15
- package/dist/api/LambderApiSignature.js +28 -51
- package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
- package/dist/api/LambderApiValidationRefusal.js +10 -10
- package/dist/build/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/writeApiSignatures.d.ts +109 -0
- package/dist/build/writeApiSignatures.js +222 -0
- package/dist/build.d.ts +9 -0
- package/dist/build.js +8 -0
- package/dist/client/LambderCaller.d.ts +13 -44
- package/dist/client/LambderCaller.js +77 -84
- package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
- package/dist/client/LambderReloadLoopBreaker.js +90 -46
- package/dist/client/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +5 -3
- package/dist/client.js +2 -1
- package/dist/core/Lambder.d.ts +161 -69
- package/dist/core/Lambder.js +370 -226
- package/dist/core/LambderContext.d.ts +82 -15
- package/dist/core/LambderContext.js +107 -20
- package/dist/core/LambderCors.d.ts +21 -3
- package/dist/core/LambderCors.js +35 -16
- package/dist/core/LambderCrashHandling.d.ts +40 -0
- package/dist/core/LambderCrashHandling.js +97 -0
- package/dist/core/LambderCreateOptions.d.ts +151 -75
- package/dist/core/LambderCreateOptions.js +16 -23
- package/dist/core/LambderFiles.d.ts +28 -7
- package/dist/core/LambderFiles.js +73 -33
- package/dist/core/LambderIndexHtml.js +12 -11
- package/dist/core/LambderPolicyBuilders.d.ts +17 -5
- package/dist/core/LambderPolicyBuilders.js +17 -5
- package/dist/core/LambderPublicFiles.d.ts +11 -5
- package/dist/core/LambderPublicFiles.js +32 -4
- package/dist/core/LambderRequestPath.d.ts +43 -0
- package/dist/core/LambderRequestPath.js +63 -0
- package/dist/core/LambderResponse.d.ts +26 -5
- package/dist/core/LambderResponse.js +157 -70
- package/dist/core/LambderResponseBuilder.d.ts +49 -4
- package/dist/core/LambderResponseBuilder.js +64 -3
- package/dist/core/LambderRouting.d.ts +2 -3
- package/dist/core/LambderRouting.js +22 -7
- package/dist/core/LambderTemplatingEngine.js +211 -32
- package/dist/index.d.ts +15 -8
- package/dist/index.js +5 -4
- package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
- package/dist/invoke/LambderInvokeCaller.js +76 -66
- package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
- package/dist/invoke/LambderInvokeOutcome.js +9 -22
- package/dist/invoke/LambderLambdaEvent.d.ts +44 -10
- package/dist/invoke/LambderLambdaEvent.js +80 -37
- package/dist/invoke/lambderHandlerTransport.d.ts +12 -10
- package/dist/invoke/lambderHandlerTransport.js +16 -19
- package/dist/mock/LambderMockApp.d.ts +67 -83
- package/dist/mock/LambderMockApp.js +167 -153
- package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
- package/dist/mock/LambderMockBrowserCookies.js +24 -28
- package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
- package/dist/mock/LambderMockCallRecorder.js +19 -28
- package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
- package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
- package/dist/mock/LambderMockEntryRegistry.js +24 -29
- package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
- package/dist/mock/LambderMockFailureInjector.js +3 -6
- package/dist/mock/LambderMockTypes.d.ts +78 -108
- package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
- package/dist/mock/lambderMockInvokeTransport.js +11 -10
- package/dist/mock/lambderMockMswHandler.d.ts +33 -29
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock.d.ts +3 -1
- package/dist/mock.js +5 -3
- package/dist/session/LambderSessionController.d.ts +108 -89
- package/dist/session/LambderSessionController.js +187 -168
- package/dist/session/LambderSessionCrypto.d.ts +16 -7
- package/dist/session/LambderSessionCrypto.js +26 -12
- package/dist/session/LambderSessionManager.d.ts +136 -47
- package/dist/session/LambderSessionManager.js +280 -139
- package/dist/shared/LambderHtml.d.ts +42 -3
- package/dist/shared/LambderHtml.js +127 -7
- package/dist/shared/LambderHtmlPositions.d.ts +173 -0
- package/dist/shared/LambderHtmlPositions.js +652 -0
- package/dist/shared/LambderI18n.d.ts +10 -11
- package/dist/shared/LambderI18n.js +33 -21
- package/dist/shared/contracts/LambderCache.d.ts +66 -0
- package/dist/shared/contracts/LambderCache.js +11 -0
- package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
- package/dist/shared/contracts/LambderFileSource.js +5 -8
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
- package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
- package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
- package/dist/shared/contracts/LambderRateLimiter.js +4 -5
- package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
- package/dist/shared/contracts/LambderSessionStore.js +5 -6
- package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
- package/dist/shared/transport/LambderApiTransport.js +7 -7
- package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
- package/dist/shared/transport/LambderCookieJar.js +54 -66
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
- package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
- package/dist/shared/util/LambderCallAbort.d.ts +5 -5
- package/dist/shared/util/LambderCallAbort.js +5 -5
- package/dist/shared/util/LambderClientIp.d.ts +27 -11
- package/dist/shared/util/LambderClientIp.js +96 -13
- package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
- package/dist/shared/util/LambderExpiringMap.js +41 -57
- package/dist/shared/util/LambderNodeModules.js +6 -7
- package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
- package/dist/shared/util/LambderOptionChecks.js +4 -4
- package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
- package/dist/shared/util/LambderResponseBrand.js +5 -5
- package/dist/shared/util/LambderTestingDoors.d.ts +29 -0
- package/dist/shared/util/LambderTestingDoors.js +29 -0
- package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
- package/dist/shared/util/LambderTypeUtilities.js +3 -3
- package/dist/shared/util/boundKeyField.d.ts +20 -0
- package/dist/shared/util/boundKeyField.js +34 -0
- package/dist/shared/util/canonicalJson.d.ts +11 -0
- package/dist/shared/util/canonicalJson.js +28 -0
- package/dist/shared/util/joinKeyFields.d.ts +20 -0
- package/dist/shared/util/joinKeyFields.js +22 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
- package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
- package/dist/shared/wire/LambderApiContract.d.ts +107 -32
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +39 -27
- package/dist/shared/wire/LambderApiRefusal.js +36 -7
- package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
- package/dist/shared/wire/LambderApiSignature.js +16 -19
- package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
- package/dist/shared/wire/LambderCallOptions.js +9 -11
- package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
- package/dist/shared/wire/LambderCompressionCodec.js +31 -36
- package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
- package/dist/shared/wire/LambderCompressionOption.js +9 -9
- package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
- package/dist/shared/wire/LambderCrashDetail.js +12 -15
- package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
- package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
- package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
- package/dist/shared/wire/LambderInvokeApiId.js +27 -0
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +79 -0
- package/dist/shared/wire/LambderOutcomeAssertions.js +112 -0
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- package/dist/stores/LambderCacheFiller.d.ts +48 -0
- package/dist/stores/LambderCacheFiller.js +119 -0
- package/dist/stores/LambderCacheKeys.d.ts +26 -0
- package/dist/stores/LambderCacheKeys.js +54 -0
- package/dist/stores/LambderCacheValues.d.ts +45 -0
- package/dist/stores/LambderCacheValues.js +74 -0
- package/dist/stores/LambderDdbCache.d.ts +121 -56
- package/dist/stores/LambderDdbCache.js +528 -225
- package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
- package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
- package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
- package/dist/stores/LambderDdbRateLimiter.js +151 -39
- package/dist/stores/LambderDdbSdk.d.ts +43 -31
- package/dist/stores/LambderDdbSdk.js +79 -33
- package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
- package/dist/stores/LambderDdbSessionStore.js +119 -47
- package/dist/stores/LambderHttpFileSource.d.ts +15 -6
- package/dist/stores/LambderHttpFileSource.js +15 -13
- package/dist/stores/LambderMemoryCache.d.ts +49 -0
- package/dist/stores/LambderMemoryCache.js +113 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
- package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
- package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
- package/dist/stores/LambderMemoryRateLimiter.js +14 -13
- package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
- package/dist/stores/LambderMemorySessionStore.js +38 -19
- package/dist/stores/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/testing/LambderTestApp.d.ts +176 -0
- package/dist/testing/LambderTestApp.js +204 -0
- package/dist/testing/LambderTestVisitor.d.ts +153 -0
- package/dist/testing/LambderTestVisitor.js +154 -0
- package/dist/testing.d.ts +27 -0
- package/dist/testing.js +24 -0
- package/package.json +20 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -36
- package/dist/api/LambderApiPolicyEngine.js +0 -77
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- package/dist/shared/util/LambderKeyFields.js +0 -34
|
@@ -1,27 +1,169 @@
|
|
|
1
1
|
import { getFS } from "../shared/util/LambderNodeModules.js";
|
|
2
2
|
import { renderHtmlValue } from "../shared/LambderHtml.js";
|
|
3
|
+
import { HtmlPositionReader, URL_ATTRIBUTE_NAMES, safeUrlValue, valueLanguageOf } from "../shared/LambderHtmlPositions.js";
|
|
3
4
|
const TOKEN_PATTERN = /<!--\s*(?:(slot:([\w-]+)\s*\/)|(slot:([\w-]+))|(\/slot:([\w-]+))|(if:(!?)([\w-]+))|(else)|(\/if:(!?)([\w-]+)))\s*-->/g;
|
|
4
5
|
/**
|
|
5
6
|
* Fail loudly on slot positions where HTML escaping cannot protect against
|
|
6
|
-
* injection:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* injection:
|
|
8
|
+
* - an unquoted attribute value (`class=<!--slot:x/-->`, with or without a
|
|
9
|
+
* prefix before the slot), where a space in the value starts a new
|
|
10
|
+
* attribute;
|
|
11
|
+
* - inside a tag where an attribute name goes (`<input <!--slot:x/-->>`),
|
|
12
|
+
* where any value is a new attribute;
|
|
13
|
+
* - inside the quoted value of an event handler, `style` or `srcdoc`, which
|
|
14
|
+
* the browser reads as another language (see valueLanguageOf);
|
|
15
|
+
* - inside <script>/<style> elements (HTML escaping is the wrong grammar
|
|
16
|
+
* there; embed data with jsonScript() into a normal slot instead).
|
|
17
|
+
* Attribute values vary through if/else around whole-tag variants, or a
|
|
18
|
+
* slot inside a quoted value.
|
|
9
19
|
*/
|
|
10
|
-
const assertSafeSlotPosition = (
|
|
11
|
-
|
|
12
|
-
if (/=\s*$/.test(before)) {
|
|
20
|
+
const assertSafeSlotPosition = (point, slotName) => {
|
|
21
|
+
if (point.position === "afterEquals" || point.position === "unquoted") {
|
|
13
22
|
throw new Error(`LambderTemplatingEngine: slot "${slotName}" is in an unquoted attribute position. ` +
|
|
14
23
|
`Escaping cannot prevent injection there; quote the attribute: attr="<!--slot:${slotName}/-->".`);
|
|
15
24
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
if (point.position === "tag") {
|
|
26
|
+
throw new Error(`LambderTemplatingEngine: slot "${slotName}" is inside a tag where an attribute name goes. ` +
|
|
27
|
+
`Escaping cannot prevent injection there; put the slot inside a quoted value, or vary the whole tag with <!--if:...-->.`);
|
|
28
|
+
}
|
|
29
|
+
const valueLanguage = point.position === "quoted" ? valueLanguageOf(point.attributeName) : null;
|
|
30
|
+
if (valueLanguage) {
|
|
31
|
+
throw new Error(`LambderTemplatingEngine: slot "${slotName}" is inside the ${point.attributeName} attribute, whose value the browser ` +
|
|
32
|
+
`reads as ${valueLanguage} once it has decoded the HTML escapes, so escaping cannot prevent injection there. ` +
|
|
33
|
+
`Pass the value in a data- attribute a script reads, or vary the whole tag with <!--if:...-->.`);
|
|
34
|
+
}
|
|
35
|
+
if (point.position === "rawText") {
|
|
36
|
+
throw new Error(`LambderTemplatingEngine: slot "${slotName}" is inside a <${point.tagName}> element where HTML ` +
|
|
37
|
+
`escaping does not apply. Pass data with the jsonScript() helper in a regular slot instead.`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** Where a point sits, in the words of a refusal. */
|
|
41
|
+
const describePoint = (point) => {
|
|
42
|
+
switch (point.position) {
|
|
43
|
+
case "text": return "in text";
|
|
44
|
+
case "comment": return "in a comment";
|
|
45
|
+
case "tag": return "inside a tag where an attribute name goes";
|
|
46
|
+
case "afterEquals":
|
|
47
|
+
case "unquoted": return `in the unquoted ${point.attributeName} value`;
|
|
48
|
+
case "quoted": return `in the quoted ${point.attributeName} value`;
|
|
49
|
+
case "rawText": return `inside a <${point.tagName}> element`;
|
|
22
50
|
}
|
|
23
51
|
};
|
|
52
|
+
/** What a refusal of branches that leave the template in different positions tells the author to do. */
|
|
53
|
+
const BRANCH_ADVICE = "Vary the whole tag or element inside the branches instead, so every branch ends where the others do.";
|
|
24
54
|
const parseTemplate = (source) => {
|
|
55
|
+
const tokens = [...source.matchAll(TOKEN_PATTERN)];
|
|
56
|
+
// Every slot's position is checked before the tree is built, which also
|
|
57
|
+
// finds the URL values holding one: the tree wraps each such value in a
|
|
58
|
+
// node of its own, so a render can check the value whole.
|
|
59
|
+
//
|
|
60
|
+
// The HTML is read in one pass along the template's own structure, with
|
|
61
|
+
// the tokens left out: they are not HTML, and inside a tag the ">" that
|
|
62
|
+
// ends <!--if:x--> would otherwise read as the tag's end. Each branch of
|
|
63
|
+
// an if is read from where the block starts, a missing else being an
|
|
64
|
+
// empty branch, and a slot with default content is a block whose
|
|
65
|
+
// branches are the default and the value. After a block, the text that
|
|
66
|
+
// follows is read in both resulting readings until they sit at the same
|
|
67
|
+
// position and join, and they have to before the next block, or which
|
|
68
|
+
// position the next block's branches start from would depend on the
|
|
69
|
+
// data. A slot met while they are still apart passes only when it lands
|
|
70
|
+
// alike in both. Each character is read at most twice, whatever the
|
|
71
|
+
// nesting. Malformed nesting stops the walk, and the build below refuses
|
|
72
|
+
// it by name.
|
|
73
|
+
const urlValues = new Map();
|
|
74
|
+
let readings = [new HtmlPositionReader()];
|
|
75
|
+
let apartSince = "";
|
|
76
|
+
const blocks = [];
|
|
77
|
+
const readOn = (text) => {
|
|
78
|
+
for (const reading of readings) {
|
|
79
|
+
reading.read(text);
|
|
80
|
+
const undecidable = reading.undecidable;
|
|
81
|
+
if (undecidable?.reason === "commentValue") {
|
|
82
|
+
throw new Error(`LambderTemplatingEngine: slot "${undecidable.label}" is inside a comment, right before text that ends the comment ` +
|
|
83
|
+
`or not depending on how the value ends ("-", "--" or "--!" before a ">"). Put a space after the slot.`);
|
|
84
|
+
}
|
|
85
|
+
if (undecidable) {
|
|
86
|
+
throw new Error(`LambderTemplatingEngine: the branches of ${undecidable.label} end after different attribute names, and the "=" ` +
|
|
87
|
+
`after them would give its value to a different attribute in each. ${BRANCH_ADVICE}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (readings.length === 2 && readings[0].joinWith(readings[1], apartSince))
|
|
91
|
+
readings = [readings[0]];
|
|
92
|
+
};
|
|
93
|
+
const requireOneReading = (token) => {
|
|
94
|
+
if (readings.length === 1)
|
|
95
|
+
return readings[0];
|
|
96
|
+
const [first, second] = readings.map((reading) => describePoint(reading.point()));
|
|
97
|
+
throw new Error(`LambderTemplatingEngine: the branches of ${apartSince} leave the template in different positions` +
|
|
98
|
+
`${first === second ? "" : ` (${first} after one, ${second} after the other)`}, and ${token} comes before they meet again. ` +
|
|
99
|
+
BRANCH_ADVICE);
|
|
100
|
+
};
|
|
101
|
+
let readCursor = 0;
|
|
102
|
+
let walkedToEnd = true;
|
|
103
|
+
for (const token of tokens) {
|
|
104
|
+
readOn(source.slice(readCursor, token.index));
|
|
105
|
+
readCursor = token.index + token[0].length;
|
|
106
|
+
const [, , selfClosingName, openSlot, openSlotName, closeSlot, closeSlotName, openIf, openIfNegation, openIfName, elseTag, , closeIfNegation, closeIfName] = token;
|
|
107
|
+
const slotName = selfClosingName ?? openSlotName;
|
|
108
|
+
if (slotName !== undefined) {
|
|
109
|
+
const [point, otherPoint] = readings.map((reading) => reading.point());
|
|
110
|
+
if (otherPoint && (otherPoint.position !== point.position || otherPoint.attributeName !== point.attributeName
|
|
111
|
+
|| otherPoint.quote !== point.quote || otherPoint.tagName !== point.tagName)) {
|
|
112
|
+
throw new Error(`LambderTemplatingEngine: slot "${slotName}" comes after ${apartSince}, whose branches leave the template in different ` +
|
|
113
|
+
`positions: the slot is ${describePoint(point)} after one, ${describePoint(otherPoint)} after the other. ${BRANCH_ADVICE}`);
|
|
114
|
+
}
|
|
115
|
+
assertSafeSlotPosition(point, slotName);
|
|
116
|
+
if (point.position === "quoted" && URL_ATTRIBUTE_NAMES.has(point.attributeName)) {
|
|
117
|
+
// No token holds a quote character, so the nearest quotes
|
|
118
|
+
// around the slot are its value's own. A quote only one
|
|
119
|
+
// branch holds leaves that block crossing the value's edge,
|
|
120
|
+
// which the build below refuses.
|
|
121
|
+
const start = source.lastIndexOf(point.quote, token.index) + 1;
|
|
122
|
+
const end = source.indexOf(point.quote, token.index);
|
|
123
|
+
if (end === -1) {
|
|
124
|
+
throw new Error(`LambderTemplatingEngine: slot "${slotName}" is inside a quoted ${point.attributeName} value that never closes.`);
|
|
125
|
+
}
|
|
126
|
+
urlValues.set(start, { start, end, attributeName: point.attributeName });
|
|
127
|
+
}
|
|
128
|
+
if (openSlot) {
|
|
129
|
+
const valueReading = requireOneReading(token[0]).copy();
|
|
130
|
+
valueReading.skipValue(slotName);
|
|
131
|
+
blocks.push({ opening: token[0], key: `slot:${slotName}`, secondStart: valueReading, firstEnd: null });
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
for (const reading of readings)
|
|
135
|
+
reading.skipValue(slotName);
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (openIf) {
|
|
140
|
+
blocks.push({ opening: token[0], key: `if:${openIfNegation}${openIfName}`, secondStart: requireOneReading(token[0]).copy(), firstEnd: null });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const block = blocks.pop();
|
|
144
|
+
const closingKey = closeSlot ? `slot:${closeSlotName}` : `if:${closeIfNegation}${closeIfName}`;
|
|
145
|
+
if (!block || (elseTag ? !block.key.startsWith("if:") || block.firstEnd !== null : block.key !== closingKey)) {
|
|
146
|
+
walkedToEnd = false;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
const reading = requireOneReading(token[0]);
|
|
150
|
+
if (elseTag) {
|
|
151
|
+
block.firstEnd = reading;
|
|
152
|
+
readings = [block.secondStart];
|
|
153
|
+
blocks.push(block);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
readings = block.firstEnd ? [block.firstEnd, reading] : [reading, block.secondStart];
|
|
157
|
+
apartSince = block.opening;
|
|
158
|
+
}
|
|
159
|
+
if (walkedToEnd)
|
|
160
|
+
readOn(source.slice(readCursor));
|
|
161
|
+
// In source order already: the values were met in token order, and two
|
|
162
|
+
// never overlap.
|
|
163
|
+
const boundaries = [...urlValues.values()].flatMap((range) => [
|
|
164
|
+
{ index: range.start, range, opens: true },
|
|
165
|
+
{ index: range.end, range, opens: false },
|
|
166
|
+
]);
|
|
25
167
|
const root = { kind: "root", name: "", negated: false, nodes: [], elseNodes: [], inElse: false };
|
|
26
168
|
const stack = [root];
|
|
27
169
|
const top = () => stack[stack.length - 1];
|
|
@@ -29,21 +171,42 @@ const parseTemplate = (source) => {
|
|
|
29
171
|
const frame = top();
|
|
30
172
|
(frame.inElse ? frame.elseNodes : frame.nodes).push(node);
|
|
31
173
|
};
|
|
32
|
-
TOKEN_PATTERN.lastIndex = 0;
|
|
33
174
|
let cursor = 0;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
175
|
+
const emitTextUpTo = (index) => {
|
|
176
|
+
if (index > cursor)
|
|
177
|
+
emit({ type: "text", value: source.slice(cursor, index) });
|
|
178
|
+
cursor = index;
|
|
179
|
+
};
|
|
180
|
+
let nextBoundary = 0;
|
|
181
|
+
const passBoundariesUpTo = (index) => {
|
|
182
|
+
for (; nextBoundary < boundaries.length && boundaries[nextBoundary].index <= index; nextBoundary += 1) {
|
|
183
|
+
const { index: boundaryIndex, range, opens } = boundaries[nextBoundary];
|
|
184
|
+
emitTextUpTo(boundaryIndex);
|
|
185
|
+
if (opens) {
|
|
186
|
+
stack.push({ kind: "urlValue", name: range.attributeName, negated: false, nodes: [], elseNodes: [], inElse: false });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const frame = stack.pop();
|
|
190
|
+
if (frame.kind !== "urlValue") {
|
|
191
|
+
throw new Error(`LambderTemplatingEngine: <!--${frame.kind}:${frame.name}--> opens inside a quoted ${range.attributeName} value holding a slot ` +
|
|
192
|
+
`and closes outside it. Keep the block wholly inside the value, or the value wholly inside the block.`);
|
|
193
|
+
}
|
|
194
|
+
emit({ type: "urlValue", nodes: frame.nodes });
|
|
38
195
|
}
|
|
39
|
-
|
|
196
|
+
};
|
|
197
|
+
for (const match of tokens) {
|
|
198
|
+
passBoundariesUpTo(match.index);
|
|
199
|
+
emitTextUpTo(match.index);
|
|
200
|
+
cursor = match.index + match[0].length;
|
|
40
201
|
const [, selfClosingSlot, selfClosingName, openSlot, openSlotName, closeSlot, closeSlotName, openIf, openIfNegation, openIfName, elseTag, closeIf, closeIfNegation, closeIfName] = match;
|
|
202
|
+
if ((closeSlot || elseTag || closeIf) && top().kind === "urlValue") {
|
|
203
|
+
throw new Error(`LambderTemplatingEngine: ${match[0]} belongs to a block opened outside the quoted ${top().name} value holding a slot it sits in. ` +
|
|
204
|
+
`Keep the block wholly inside the value, or the value wholly inside the block.`);
|
|
205
|
+
}
|
|
41
206
|
if (selfClosingSlot) {
|
|
42
|
-
assertSafeSlotPosition(source, match.index, selfClosingName);
|
|
43
207
|
emit({ type: "slot", name: selfClosingName, defaultNodes: [] });
|
|
44
208
|
}
|
|
45
209
|
else if (openSlot) {
|
|
46
|
-
assertSafeSlotPosition(source, match.index, openSlotName);
|
|
47
210
|
stack.push({ kind: "slot", name: openSlotName, negated: false, nodes: [], elseNodes: [], inElse: false });
|
|
48
211
|
}
|
|
49
212
|
else if (closeSlot) {
|
|
@@ -72,35 +235,46 @@ const parseTemplate = (source) => {
|
|
|
72
235
|
emit({ type: "if", name: frame.name, negated: frame.negated, thenNodes: frame.nodes, elseNodes: frame.elseNodes });
|
|
73
236
|
}
|
|
74
237
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
238
|
+
passBoundariesUpTo(source.length);
|
|
239
|
+
emitTextUpTo(source.length);
|
|
78
240
|
if (stack.length !== 1) {
|
|
79
241
|
const frame = top();
|
|
80
242
|
throw new Error(`LambderTemplatingEngine: unclosed <!--${frame.kind}:${frame.name}--> block.`);
|
|
81
243
|
}
|
|
82
244
|
return root.nodes;
|
|
83
245
|
};
|
|
84
|
-
const renderNodes = (nodes, data) => {
|
|
85
|
-
let out = "";
|
|
246
|
+
const renderNodes = (nodes, data, output) => {
|
|
86
247
|
for (const node of nodes) {
|
|
87
248
|
if (node.type === "text") {
|
|
88
|
-
|
|
249
|
+
output.html += node.value;
|
|
89
250
|
}
|
|
90
251
|
else if (node.type === "slot") {
|
|
91
252
|
const value = Object.prototype.hasOwnProperty.call(data, node.name) ? data[node.name] : undefined;
|
|
92
|
-
|
|
253
|
+
if (value === undefined) {
|
|
254
|
+
renderNodes(node.defaultNodes, data, output);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
if (output.firstDataIndex === -1)
|
|
258
|
+
output.firstDataIndex = output.html.length;
|
|
259
|
+
output.html += renderHtmlValue(value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (node.type === "urlValue") {
|
|
263
|
+
// Rendered on its own, so the check reads the value whole and
|
|
264
|
+
// knows which part of it the template wrote.
|
|
265
|
+
const value = { html: "", firstDataIndex: -1 };
|
|
266
|
+
renderNodes(node.nodes, data, value);
|
|
267
|
+
output.html += safeUrlValue(value.html, value.firstDataIndex);
|
|
93
268
|
}
|
|
94
269
|
else {
|
|
95
270
|
// Own properties only, the same lookup the slot branch uses:
|
|
96
|
-
// through the prototype, <!--if:toString-->
|
|
97
|
-
//
|
|
271
|
+
// through the prototype, <!--if:toString--> would be true on
|
|
272
|
+
// every render.
|
|
98
273
|
const value = Object.prototype.hasOwnProperty.call(data, node.name) ? data[node.name] : undefined;
|
|
99
274
|
const condition = !!value !== node.negated;
|
|
100
|
-
|
|
275
|
+
renderNodes(condition ? node.thenNodes : node.elseNodes, data, output);
|
|
101
276
|
}
|
|
102
277
|
}
|
|
103
|
-
return out;
|
|
104
278
|
};
|
|
105
279
|
const collectNames = (nodes, slots, conditions) => {
|
|
106
280
|
for (const node of nodes) {
|
|
@@ -113,6 +287,9 @@ const collectNames = (nodes, slots, conditions) => {
|
|
|
113
287
|
collectNames(node.thenNodes, slots, conditions);
|
|
114
288
|
collectNames(node.elseNodes, slots, conditions);
|
|
115
289
|
}
|
|
290
|
+
else if (node.type === "urlValue") {
|
|
291
|
+
collectNames(node.nodes, slots, conditions);
|
|
292
|
+
}
|
|
116
293
|
}
|
|
117
294
|
};
|
|
118
295
|
export class LambderTemplatingEngine {
|
|
@@ -144,7 +321,9 @@ export class LambderTemplatingEngine {
|
|
|
144
321
|
}
|
|
145
322
|
/** Render with escaped-by-default data; unknown keys ignored, omitted slots keep defaults. */
|
|
146
323
|
render(data = {}) {
|
|
147
|
-
|
|
324
|
+
const output = { html: "", firstDataIndex: -1 };
|
|
325
|
+
renderNodes(this.nodes, data, output);
|
|
326
|
+
return output.html;
|
|
148
327
|
}
|
|
149
328
|
}
|
|
150
329
|
/** Wrap the <title> content and the pre-</head> position in virtual slot markers. */
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ export default Lambder;
|
|
|
3
3
|
export { initLambder } from './core/Lambder.js';
|
|
4
4
|
export type { LambderCreatedHook } from './core/Lambder.js';
|
|
5
5
|
export { default as LambderCaller } from "./client/LambderCaller.js";
|
|
6
|
-
export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs,
|
|
6
|
+
export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs, LambderLogListHandler } from "./client/LambderCaller.js";
|
|
7
|
+
export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
|
|
8
|
+
export type { LambderIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
|
|
7
9
|
export { lambderFetchTransport } from "./client/lambderFetchTransport.js";
|
|
8
10
|
export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFailure } from "./shared/transport/LambderApiTransport.js";
|
|
9
11
|
export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
|
|
@@ -34,13 +36,14 @@ export { compareDottedVersions, isDottedVersion } from "./shared/wire/LambderVer
|
|
|
34
36
|
export { buildApiEnvelope, envelopeAnswer, refusalAnswer, validationAnswer, apiNotFoundAnswer, sessionExpiredAnswer, versionExpiredAnswer, invalidPayloadAnswer, crashAnswer, API_ANSWER_CONTENT_TYPE, } from "./api/LambderApiEnvelope.js";
|
|
35
37
|
export type { LambderApiEnvelopeConfig, LambderValidationAnswerBody } from "./api/LambderApiEnvelope.js";
|
|
36
38
|
export { LambderApiValidationRefusal, isLambderApiValidationRefusal } from "./api/LambderApiValidationRefusal.js";
|
|
39
|
+
export { LambderApiOutputValidationError } from "./api/LambderApiOutputValidationError.js";
|
|
37
40
|
export { LambderInvokeError, isLambderInvokeError } from "./invoke/LambderInvokeOutcome.js";
|
|
38
41
|
export type { LambderInvokeOutcome, LambderInvokeFailure, LambderInvokeValidationFailure, LambderInvokeCrashFailure, LambderInvokePayloadTooLargeFailure, LambderInvokeEnvelopeFailure, LambderInvokeDeliveryFailure, LambderInvokeFailureReason, LambderInvokeFunctionError, } from "./invoke/LambderInvokeOutcome.js";
|
|
39
42
|
export { default as LambderInvokeCaller, LAMBDER_INVOKE_MAX_EVENT_BYTES, } from "./invoke/LambderInvokeCaller.js";
|
|
40
43
|
export type { LambderInvokeCallerOptions, LambderInvokeCallOptions, LambderInvokeFailureHandler, LambderInvokeLogListHandler, LambderInvokeTransport, LambderInvokeTransportResult, LambderInvokeRequestInit, LambderInvokeEventInit, } from "./invoke/LambderInvokeCaller.js";
|
|
41
44
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
42
45
|
export type { LambderCrashDetail, LambderCrashCause } from "./shared/wire/LambderCrashDetail.js";
|
|
43
|
-
export { LambderApiRefusal, isLambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
46
|
+
export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
44
47
|
export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderAppRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
|
|
45
48
|
export { default as LambderResponseBuilder } from "./core/LambderResponseBuilder.js";
|
|
46
49
|
export { default as LambderResolver } from "./core/LambderResolver.js";
|
|
@@ -49,7 +52,7 @@ export type { LambderSessionManagerOptions } from "./session/LambderSessionManag
|
|
|
49
52
|
export { default as LambderSessionController } from "./session/LambderSessionController.js";
|
|
50
53
|
export { DEFAULT_SESSION_TOKEN_COOKIE_KEY, DEFAULT_SESSION_CSRF_COOKIE_KEY } from "./shared/wire/LambderSessionCookieNames.js";
|
|
51
54
|
export type { LambderSessionControllerOptions, LambderSessionRequestInfo } from "./session/LambderSessionController.js";
|
|
52
|
-
export type { LambderSessionStore, LambderSessionRecord } from "./shared/contracts/LambderSessionStore.js";
|
|
55
|
+
export type { LambderSessionStore, LambderSessionRecord, LambderSessionChanges, LambderSessionUpdateResult } from "./shared/contracts/LambderSessionStore.js";
|
|
53
56
|
export { LambderMemorySessionStore } from "./stores/LambderMemorySessionStore.js";
|
|
54
57
|
export { LambderWebCrypto, LambderPlainSessionCrypto, isWebCryptoAvailable } from "./session/LambderSessionCrypto.js";
|
|
55
58
|
export type { LambderSessionCrypto } from "./session/LambderSessionCrypto.js";
|
|
@@ -65,7 +68,7 @@ export type { LambderRouteMatcher, LambderRouteConditionFn, LambderRouteConditio
|
|
|
65
68
|
export type { LambderCorsConfig } from "./core/LambderCors.js";
|
|
66
69
|
export type { LambderCreateOptions, LambderSessionOptions, LambderActionTools, LambderHandler, } from "./core/LambderCreateOptions.js";
|
|
67
70
|
export type { LambderIndexHtmlOptions } from "./core/LambderIndexHtml.js";
|
|
68
|
-
export type { LambderRouteHandler, LambderSessionRouteHandler, LambderActionHandler, LambderActionFilter, LambderHookEvent, LambderBeforeRenderHook, LambderAfterRenderHook, LambderFallbackHook, LambderGlobalErrorHandler, LambderFallbackHandler, LambderInputValidationHandler, } from "./core/LambderCreateOptions.js";
|
|
71
|
+
export type { LambderRouteHandler, LambderSessionRouteHandler, LambderActionHandler, LambderActionFilter, LambderHookEvent, LambderBeforeRenderHook, LambderAfterRenderHook, LambderFallbackHook, LambderGlobalErrorHandler, LambderFallbackHandler, LambderInputValidationHandler, LambderCrashOptions, LambderCrashReporter, LambderCrashSite, } from "./core/LambderCreateOptions.js";
|
|
69
72
|
export type { LambderPublicFilesOptions } from "./core/LambderPublicFiles.js";
|
|
70
73
|
export { LambderFiles } from "./core/LambderFiles.js";
|
|
71
74
|
export type { LambderFilesOption, LambderFileMemoryCacheOption, LambderReadFile } from "./core/LambderFiles.js";
|
|
@@ -83,8 +86,11 @@ export { compressText, restoreBytes, restoreText, LambderCompressionError, LAMBD
|
|
|
83
86
|
export type { LambderRestoreFailure, LambderRestoreBound } from "./shared/wire/LambderCompressionCodec.js";
|
|
84
87
|
export { LambderSessionDataRefreshError, LambderSessionReadError } from "./session/LambderSessionManager.js";
|
|
85
88
|
export { LambderSessionNotFoundError, LambderSessionAmbiguousError } from "./session/LambderSessionController.js";
|
|
89
|
+
export type { LambderCache, LambderCacheKey, LambderCacheSetOptions, LambderCacheListOptions } from "./shared/contracts/LambderCache.js";
|
|
86
90
|
export { LambderDdbCache } from "./stores/LambderDdbCache.js";
|
|
87
|
-
export type {
|
|
91
|
+
export type { LambderDdbCacheOptions, LambderDdbCacheGetOrSetOptions, } from "./stores/LambderDdbCache.js";
|
|
92
|
+
export { LambderMemoryCache } from "./stores/LambderMemoryCache.js";
|
|
93
|
+
export type { LambderMemoryCacheOptions } from "./stores/LambderMemoryCache.js";
|
|
88
94
|
export { RATE_LIMIT_WINDOWS } from "./shared/contracts/LambderRateLimiter.js";
|
|
89
95
|
export type { LambderRateLimiter, LambderRateLimitWindow, LambderRateLimitPolicy, LambderRateLimitExceeded, LambderRateLimitResult, } from "./shared/contracts/LambderRateLimiter.js";
|
|
90
96
|
export { LambderDdbRateLimiter } from "./stores/LambderDdbRateLimiter.js";
|
|
@@ -96,19 +102,20 @@ export type { LambderDdbIdempotencyStoreOptions } from "./stores/LambderDdbIdemp
|
|
|
96
102
|
export { LambderMemoryIdempotencyStore } from "./stores/LambderMemoryIdempotencyStore.js";
|
|
97
103
|
export { lambderGuard, lambderRateLimitKey } from "./core/LambderPolicyBuilders.js";
|
|
98
104
|
export { lambderGuardBuilder } from "./api/LambderApiGuards.js";
|
|
99
|
-
export type { LambderGuardBuilder, LambderApiGuard, LambderGuardMeta, LambderGuardMetaMap, LambderAllowedGuardNames, LambderParamlessGuardNames, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf, } from "./api/LambderApiGuards.js";
|
|
105
|
+
export type { LambderGuardBuilder, LambderApiGuard, LambderGuardMeta, LambderGuardMetaMap, LambderGuardRunAt, LambderAllowedGuardNames, LambderParamlessGuardNames, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf, } from "./api/LambderApiGuards.js";
|
|
100
106
|
export { lambderRateLimitKeyBuilder, rateLimitRefusal, DEFAULT_RATE_LIMIT_REFUSAL } from "./api/LambderApiRateLimits.js";
|
|
101
107
|
export type { LambderRateLimitKeyBuilder } from "./api/LambderApiRateLimits.js";
|
|
102
|
-
export type { LambderRateLimitKeyFn, LambderRateLimitPer, LambderRateLimitBudget, LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderAllowedPolicyNames, LambderRateLimitOption, } from "./api/LambderApiRateLimits.js";
|
|
108
|
+
export type { LambderContextRateLimit, LambderContextRateLimitCheck, LambderRateLimitCheckResult, LambderChargeablePolicyNames, LambderChargeKeyArgs, LambderRateLimitKeyFn, LambderRateLimitPer, LambderRateLimitBudget, LambderRateLimitChargeAt, LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderAllowedPolicyNames, LambderRateLimitOption, } from "./api/LambderApiRateLimits.js";
|
|
103
109
|
export type { LambderApiIdempotencyConfig } from "./api/LambderApiIdempotency.js";
|
|
104
110
|
export type { LambderGuardsOptionValue, LambderRateLimitOverride, LambderRateLimitOptionValue, LambderApiIdempotencyOption, } from "./shared/wire/LambderApiOptionValues.js";
|
|
105
111
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
106
112
|
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
107
|
-
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderApiNullAnswerConfig, LambderContractEntry, LambderMergeContract, LambderFlattenContract, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
113
|
+
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderApiNullAnswerConfig, LambderContractEntry, LambderMergeContract, LambderFlattenContract, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderJsonOutputOf, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
108
114
|
export type { LambderRenderContext, LambderSessionRenderContext, LambderHttpEvent, LambderHttpEventFormat } from "./core/LambderContext.js";
|
|
109
115
|
export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiCallFailure, LambderApiValidationFailure, LambderApiEnvelopeFailure, LambderApiHttpAnswer, } from "./shared/wire/LambderApiOutcome.js";
|
|
110
116
|
export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
|
|
111
117
|
export { createContext, isV2HttpEvent } from "./core/LambderContext.js";
|
|
118
|
+
export type { LambderContextOptions } from "./core/LambderContext.js";
|
|
112
119
|
export { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, DEFAULT_MAX_RESTORED_PAYLOAD_BYTES, DEFAULT_INVOKE_REQUEST_COMPRESSION_SETTINGS, compressPayloadBrotli, } from "./shared/wire/LambderRequestPayload.js";
|
|
113
120
|
export type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload, LambderRequestCompressionOption, LambderRequestCompressionSettings, } from "./shared/wire/LambderRequestPayload.js";
|
|
114
121
|
export { serializeCookie, serializeClearCookie, resolveCookieDomain } from "./shared/wire/LambderCookie.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import Lambder from './core/Lambder.js';
|
|
|
2
2
|
export default Lambder;
|
|
3
3
|
export { initLambder } from './core/Lambder.js';
|
|
4
4
|
export { default as LambderCaller } from "./client/LambderCaller.js";
|
|
5
|
+
export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
|
|
5
6
|
// Transports: how a caller reaches a server (fetch in production, a handler in-process for tests, a cookie jar over either)
|
|
6
7
|
export { lambderFetchTransport } from "./client/lambderFetchTransport.js";
|
|
7
8
|
export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFailure } from "./shared/transport/LambderApiTransport.js";
|
|
@@ -23,13 +24,14 @@ export { RELOAD_LOOP_WINDOW_MS } from "./client/LambderReloadLoopBreaker.js";
|
|
|
23
24
|
export { compareDottedVersions, isDottedVersion } from "./shared/wire/LambderVersionOrder.js";
|
|
24
25
|
export { buildApiEnvelope, envelopeAnswer, refusalAnswer, validationAnswer, apiNotFoundAnswer, sessionExpiredAnswer, versionExpiredAnswer, invalidPayloadAnswer, crashAnswer, API_ANSWER_CONTENT_TYPE, } from "./api/LambderApiEnvelope.js";
|
|
25
26
|
export { LambderApiValidationRefusal, isLambderApiValidationRefusal } from "./api/LambderApiValidationRefusal.js";
|
|
27
|
+
export { LambderApiOutputValidationError } from "./api/LambderApiOutputValidationError.js";
|
|
26
28
|
// Calling a Lambder app from another lambda (server-only: the Lambda SDK, zlib)
|
|
27
29
|
export { LambderInvokeError, isLambderInvokeError } from "./invoke/LambderInvokeOutcome.js";
|
|
28
30
|
export { default as LambderInvokeCaller, LAMBDER_INVOKE_MAX_EVENT_BYTES, } from "./invoke/LambderInvokeCaller.js";
|
|
29
31
|
// A crash described for a caller allowed to see it (the envelope's `crash` field)
|
|
30
32
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
31
33
|
// Typed API refusals (isomorphic: shared code may throw them from anywhere)
|
|
32
|
-
export { LambderApiRefusal, isLambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
34
|
+
export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
33
35
|
export { default as LambderResponseBuilder } from "./core/LambderResponseBuilder.js";
|
|
34
36
|
export { default as LambderResolver } from "./core/LambderResolver.js";
|
|
35
37
|
export { default as LambderSessionManager } from "./session/LambderSessionManager.js";
|
|
@@ -55,8 +57,8 @@ export { resolveCompressionOption, LAMBDER_ENCODINGS } from "./shared/wire/Lambd
|
|
|
55
57
|
export { compressText, restoreBytes, restoreText, LambderCompressionError, LAMBDER_RESTORE_FAILURES, } from "./shared/wire/LambderCompressionCodec.js";
|
|
56
58
|
export { LambderSessionDataRefreshError, LambderSessionReadError } from "./session/LambderSessionManager.js";
|
|
57
59
|
export { LambderSessionNotFoundError, LambderSessionAmbiguousError } from "./session/LambderSessionController.js";
|
|
58
|
-
// DynamoDB-backed compressed cache (standalone, server-only)
|
|
59
60
|
export { LambderDdbCache } from "./stores/LambderDdbCache.js";
|
|
61
|
+
export { LambderMemoryCache } from "./stores/LambderMemoryCache.js";
|
|
60
62
|
// Fixed-window rate limiting: the shared vocabulary, the DynamoDB limiter and the in-memory one
|
|
61
63
|
export { RATE_LIMIT_WINDOWS } from "./shared/contracts/LambderRateLimiter.js";
|
|
62
64
|
export { LambderDdbRateLimiter } from "./stores/LambderDdbRateLimiter.js";
|
|
@@ -74,8 +76,7 @@ export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
|
|
|
74
76
|
export { createContext, isV2HttpEvent } from "./core/LambderContext.js";
|
|
75
77
|
// Request payload compression: the wire format LambderCaller and the server share.
|
|
76
78
|
export { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, DEFAULT_MAX_RESTORED_PAYLOAD_BYTES,
|
|
77
|
-
// The Brotli twin of the browser's compressPayloadGzip,
|
|
78
|
-
// rather than inside LambderInvokeCaller; the root entry's name is unchanged.
|
|
79
|
+
// The Brotli twin of the browser's compressPayloadGzip, and its defaults.
|
|
79
80
|
DEFAULT_INVOKE_REQUEST_COMPRESSION_SETTINGS, compressPayloadBrotli, } from "./shared/wire/LambderRequestPayload.js";
|
|
80
81
|
// Cookies (res.setCookie / res.clearCookie build on these; exported for code holding a LambderResponse)
|
|
81
82
|
export { serializeCookie, serializeClearCookie, resolveCookieDomain } from "./shared/wire/LambderCookie.js";
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
* Calling a Lambder app from another lambda, or from any server code that
|
|
3
3
|
* holds AWS credentials and lambda:InvokeFunction on it.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
5
|
+
* Builds the payload-format-2.0 event API Gateway would have built, invokes
|
|
6
|
+
* the function with it directly, and reads the response object Lambder
|
|
7
|
+
* returns. The callee is an unmodified Lambder app, so everything it offers
|
|
8
|
+
* over HTTP (zod validation, the inferred contract, refusals, guards,
|
|
9
|
+
* idempotency keys, Brotli answers, logList, the crash detail its error
|
|
10
|
+
* handler chooses to send) applies unchanged. The callee tells an invoke
|
|
11
|
+
* apart by the event's requestContext.apiId, which no gateway lets a client
|
|
12
|
+
* write, and reads no forwarding header on one, so `clientIp` and `host` are
|
|
13
|
+
* the only address and host it sees. The x-lambder-invoke header is a marker
|
|
14
|
+
* for guards and hooks, never an authorization: the IAM grant is the
|
|
15
|
+
* authorization.
|
|
16
16
|
*
|
|
17
17
|
* Server-only (zlib, the Lambda SDK), so it is exported from the root entry
|
|
18
18
|
* and never from lambder/client. The SDK is an optional peer dependency
|
|
@@ -72,23 +72,18 @@ type LambderInvokeCallerBaseOptions = {
|
|
|
72
72
|
/** Function name or ARN. */
|
|
73
73
|
functionName: string;
|
|
74
74
|
/**
|
|
75
|
-
* A ready client, e.g. one shared with the rest of the app.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* re-executes a callee whose response was merely lost. Build it with
|
|
80
|
-
* `{ maxAttempts: 1 }`, or send an `idempotencyKey` and let the callee
|
|
81
|
-
* settle the repeat.
|
|
75
|
+
* A ready client, e.g. one shared with the rest of the app. `clientConfig`
|
|
76
|
+
* does not apply to it, so it keeps its own `maxAttempts` (the SDK's 3 by
|
|
77
|
+
* default), and a retry re-executes a callee whose response was merely
|
|
78
|
+
* lost: build it with `{ maxAttempts: 1 }`, or send an `idempotencyKey`.
|
|
82
79
|
*/
|
|
83
80
|
client?: LambdaClient;
|
|
84
81
|
/**
|
|
85
82
|
* Otherwise the client is created from this on the first call (region,
|
|
86
|
-
* credentials, maxAttempts). `maxAttempts` defaults to 1
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* attempt. Raise it deliberately if the callee is idempotent, or send an
|
|
91
|
-
* `idempotencyKey` and let the callee settle it.
|
|
83
|
+
* credentials, maxAttempts). `maxAttempts` defaults to 1 rather than the
|
|
84
|
+
* SDK's 3, since a lost response means the callee already ran and a retry
|
|
85
|
+
* would run it twice. Raise it only for an idempotent callee, or send an
|
|
86
|
+
* `idempotencyKey`.
|
|
92
87
|
*/
|
|
93
88
|
clientConfig?: LambdaClientConfig;
|
|
94
89
|
/** Must match the callee's apiPath. Default: "/api". */
|
|
@@ -118,10 +113,10 @@ type LambderInvokeCallerBaseOptions = {
|
|
|
118
113
|
/** Receives each answer's logList. Default: console.log with the function and api name. A throw is logged and otherwise ignored. */
|
|
119
114
|
onLogList?: LambderInvokeLogListHandler;
|
|
120
115
|
/**
|
|
121
|
-
* Called
|
|
122
|
-
* apiOutcome() returns, so failures are reported in one place
|
|
123
|
-
*
|
|
124
|
-
*
|
|
116
|
+
* Called and awaited for every failed call before api() throws or
|
|
117
|
+
* apiOutcome() returns, so failures are reported in one place, before the
|
|
118
|
+
* lambda answers. A throw inside it is logged and otherwise ignored, so
|
|
119
|
+
* apiOutcome() never throws.
|
|
125
120
|
*/
|
|
126
121
|
onFailure?: LambderInvokeFailureHandler;
|
|
127
122
|
/** The session token cookie's name, when a session is carried and the callee uses a non-default `tokenCookieKey`. The CSRF value rides in the envelope's `token` field, which has no name to configure. */
|
|
@@ -195,10 +190,9 @@ export default class LambderInvokeCaller<TContract extends LambderApiContractSha
|
|
|
195
190
|
* Lambda would: a thrown error becomes a FunctionError payload. For
|
|
196
191
|
* tests that want the real handlers behind the real envelope.
|
|
197
192
|
*
|
|
198
|
-
* It honours the signal
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* caller's answer. Ignoring it made timeoutMs a no-op here.
|
|
193
|
+
* It honours the signal by ending the wait, as lambderHandlerTransport
|
|
194
|
+
* does: an in-process call cannot be cancelled, so the handler runs to
|
|
195
|
+
* completion regardless, but timeoutMs still frees the caller.
|
|
202
196
|
*/
|
|
203
197
|
static localTransport(handler: (event: APIGatewayProxyEventV2, context: Context) => Promise<unknown>, context?: Partial<Context>): LambderInvokeTransport;
|
|
204
198
|
private loadSdk;
|
|
@@ -208,25 +202,26 @@ export default class LambderInvokeCaller<TContract extends LambderApiContractSha
|
|
|
208
202
|
/** Builds the failure and its error, reports it once, and hands it back. */
|
|
209
203
|
private failureOutcome;
|
|
210
204
|
private surfaceLogs;
|
|
211
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* One call, one outcome. Never throws; api() is what throws. A key scope
|
|
207
|
+
* is told how the attempt ended, as it is on LambderCaller.
|
|
208
|
+
*/
|
|
212
209
|
private dispatch;
|
|
210
|
+
private dispatchAttempt;
|
|
213
211
|
/**
|
|
214
212
|
* Full-fidelity call: resolves to a discriminated LambderInvokeOutcome
|
|
215
|
-
* instead of throwing
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* taken as a type parameter, so a call site cannot replace it by
|
|
219
|
-
* annotating what it assigns to.
|
|
213
|
+
* instead of throwing, for sites that degrade gracefully. The output type
|
|
214
|
+
* comes from the contract rather than a type parameter, so a call site
|
|
215
|
+
* cannot replace it by annotating what it assigns to.
|
|
220
216
|
*/
|
|
221
217
|
apiOutcome<TApiName extends keyof TContract & string = string>(apiName: TApiName, ...rest: LambderCallArgs<TContract, TApiName, TProvidedGuards, LambderInvokeCallOptions>): Promise<LambderInvokeOutcome<LambderContractOutputOf<TContract, TApiName>>>;
|
|
222
218
|
/**
|
|
223
219
|
* The declared output, or a thrown LambderInvokeError carrying the
|
|
224
220
|
* outcome. A failed dependency is a failed request: the throw reaches the
|
|
225
221
|
* app's global error handler with the callee's error as its cause. The
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* arrives.
|
|
222
|
+
* resolver lets a handler answer null only when the output allows it or
|
|
223
|
+
* beside a reason (LambderApiAnswer), so null arrives only for a
|
|
224
|
+
* nullable output.
|
|
230
225
|
*/
|
|
231
226
|
api<TApiName extends keyof TContract & string = string>(apiName: TApiName, ...rest: LambderCallArgs<TContract, TApiName, TProvidedGuards, LambderInvokeCallOptions>): Promise<LambderContractOutputOf<TContract, TApiName>>;
|
|
232
227
|
/**
|