lambder 14.1.1 → 15.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 +429 -0
- package/README.md +14 -17
- package/dist/api/LambderApiDeclarations.d.ts +11 -5
- package/dist/api/LambderApiDefinition.d.ts +2 -1
- package/dist/api/LambderApiEnvelope.d.ts +2 -2
- package/dist/api/LambderApiEnvelope.js +8 -2
- package/dist/api/LambderApiGuards.d.ts +44 -5
- package/dist/api/LambderApiGuards.js +27 -0
- package/dist/api/LambderApiIdempotency.d.ts +14 -7
- package/dist/api/LambderApiIdempotency.js +39 -27
- package/dist/api/LambderApiPipeline.d.ts +25 -11
- package/dist/api/LambderApiPipeline.js +34 -17
- package/dist/api/LambderApiRateLimits.d.ts +28 -21
- package/dist/api/LambderApiRateLimits.js +55 -35
- package/dist/api/LambderApiRefusals.d.ts +10 -4
- package/dist/api/LambderApiRefusals.js +31 -5
- package/dist/api/LambderApiRequest.d.ts +21 -1
- package/dist/api/LambderApiRequest.js +31 -2
- package/dist/build/checkApiRefusals.d.ts +8 -3
- package/dist/build/checkApiRefusals.js +80 -29
- package/dist/build/contractPrinterProcess.d.ts +11 -0
- package/dist/build/contractPrinterProcess.js +17 -0
- package/dist/build/generateApiFiles.d.ts +26 -5
- package/dist/build/generateApiFiles.js +68 -7
- package/dist/build/writeApiSchemas.d.ts +109 -0
- package/dist/build/writeApiSchemas.js +308 -0
- package/dist/build.d.ts +3 -1
- package/dist/build.js +2 -0
- package/dist/client/LambderCaller.d.ts +25 -11
- package/dist/client/LambderCaller.js +57 -49
- package/dist/client/LambderUploadRunner.d.ts +55 -27
- package/dist/client/LambderUploadRunner.js +89 -34
- package/dist/client.d.ts +6 -6
- package/dist/client.js +2 -2
- package/dist/core/Lambder.d.ts +31 -21
- package/dist/core/Lambder.js +79 -23
- package/dist/core/LambderCallSummary.d.ts +1 -1
- package/dist/core/LambderContext.d.ts +59 -7
- package/dist/core/LambderContext.js +31 -15
- package/dist/core/LambderCrashHandling.d.ts +5 -0
- package/dist/core/LambderCrashHandling.js +11 -1
- package/dist/core/LambderCreateOptions.d.ts +28 -17
- package/dist/core/LambderCreateOptions.js +14 -7
- package/dist/core/LambderResolver.d.ts +3 -2
- package/dist/core/LambderResponseBuilder.d.ts +20 -9
- package/dist/core/LambderResponseBuilder.js +39 -8
- package/dist/core/LambderTemplatingEngine.d.ts +24 -6
- package/dist/core/LambderTemplatingEngine.js +23 -3
- package/dist/index.d.ts +15 -12
- package/dist/index.js +7 -5
- package/dist/invoke/LambderInvokeCaller.d.ts +25 -4
- package/dist/invoke/LambderInvokeCaller.js +47 -9
- package/dist/invoke/LambderInvokeOutcome.d.ts +7 -6
- package/dist/mock/LambderMockAdapterDoor.d.ts +40 -0
- package/dist/mock/LambderMockAdapterDoor.js +10 -0
- package/dist/mock/LambderMockApiSchemas.d.ts +28 -0
- package/dist/mock/LambderMockApiSchemas.js +150 -0
- package/dist/mock/LambderMockApp.d.ts +56 -48
- package/dist/mock/LambderMockApp.js +107 -62
- package/dist/mock/LambderMockBrowserCookies.js +18 -5
- package/dist/mock/LambderMockCreateOptions.d.ts +51 -4
- package/dist/mock/LambderMockFailureInjector.d.ts +12 -3
- package/dist/mock/LambderMockFailureInjector.js +12 -8
- package/dist/mock/LambderMockTypes.d.ts +8 -4
- package/dist/mock/lambderMockInvokeTransport.js +2 -9
- package/dist/mock/lambderMockMswHandler.d.ts +19 -13
- package/dist/mock/lambderMockMswHandler.js +25 -22
- package/dist/mock.d.ts +1 -0
- package/dist/secrets/LambderOneShotSecrets.d.ts +1 -1
- package/dist/secrets/LambderOneShotSecrets.js +6 -36
- package/dist/secrets/LambderPasswordHasher.d.ts +56 -0
- package/dist/secrets/LambderPasswordHasher.js +168 -0
- package/dist/session/LambderSessionController.d.ts +37 -4
- package/dist/session/LambderSessionController.js +36 -6
- package/dist/session/LambderSessionManager.d.ts +33 -3
- package/dist/session/LambderSessionManager.js +55 -16
- package/dist/shared/LambderHtml.d.ts +23 -4
- package/dist/shared/LambderHtml.js +17 -5
- package/dist/shared/LambderI18n.d.ts +106 -16
- package/dist/shared/LambderI18n.js +259 -74
- package/dist/shared/contracts/LambderCache.d.ts +14 -1
- package/dist/shared/contracts/LambderRateLimiter.d.ts +5 -6
- package/dist/shared/transport/LambderApiTransport.d.ts +3 -2
- package/dist/shared/util/LambderBackoffTimer.d.ts +24 -13
- package/dist/shared/util/LambderBackoffTimer.js +40 -12
- package/dist/shared/util/LambderCallAbort.d.ts +22 -6
- package/dist/shared/util/LambderCallAbort.js +37 -9
- package/dist/shared/util/LambderErrorReportMark.d.ts +4 -0
- package/dist/shared/util/LambderErrorReportMark.js +22 -0
- package/dist/shared/util/LambderKeyFieldDigest.d.ts +46 -0
- package/dist/shared/util/LambderKeyFieldDigest.js +75 -0
- package/dist/shared/util/LambderSignedClaims.d.ts +19 -0
- package/dist/shared/util/LambderSignedClaims.js +54 -0
- package/dist/shared/util/LambderSwappableInstances.d.ts +29 -0
- package/dist/shared/util/LambderSwappableInstances.js +54 -0
- package/dist/shared/util/LambderTestingDoors.d.ts +6 -2
- package/dist/shared/util/LambderTestingDoors.js +6 -2
- package/dist/shared/util/LambderTextDigest.d.ts +5 -5
- package/dist/shared/util/LambderTextDigest.js +5 -5
- package/dist/shared/wire/LambderApiContract.d.ts +1 -1
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +9 -1
- package/dist/shared/wire/LambderApiOptionEntries.js +15 -0
- package/dist/shared/wire/LambderApiOptionValues.d.ts +3 -2
- package/dist/shared/wire/LambderApiOutcome.d.ts +37 -19
- package/dist/shared/wire/LambderApiOutcome.js +1 -1
- package/dist/shared/wire/LambderApiRefusal.d.ts +39 -12
- package/dist/shared/wire/LambderApiRefusal.js +3 -3
- package/dist/shared/wire/LambderApiSchemaEntries.d.ts +21 -0
- package/dist/shared/wire/LambderApiSchemaEntries.js +18 -0
- package/dist/shared/wire/LambderCallOptions.d.ts +3 -3
- package/dist/shared/wire/LambderCallOutcome.d.ts +2 -1
- package/dist/shared/wire/LambderCallOutcome.js +3 -2
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +5 -3
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +5 -3
- package/dist/shared/wire/LambderInvokeApiId.d.ts +3 -3
- package/dist/shared/wire/LambderInvokeApiId.js +3 -3
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +7 -6
- package/dist/shared/wire/LambderOutcomeAssertions.js +1 -1
- package/dist/stores/LambderCacheValues.d.ts +2 -5
- package/dist/stores/LambderDdbCache.d.ts +15 -9
- package/dist/stores/LambderDdbCache.js +25 -6
- package/dist/stores/LambderDdbIdempotencyStore.d.ts +6 -4
- package/dist/stores/LambderDdbIdempotencyStore.js +32 -52
- package/dist/stores/LambderDdbOneShotSecretStore.d.ts +11 -0
- package/dist/stores/LambderDdbOneShotSecretStore.js +17 -5
- package/dist/stores/LambderDdbSdk.d.ts +59 -29
- package/dist/stores/LambderDdbSdk.js +116 -44
- package/dist/stores/LambderDdbSessionStore.d.ts +26 -11
- package/dist/stores/LambderDdbSessionStore.js +133 -68
- package/dist/stores/LambderMemoryCache.d.ts +2 -2
- package/dist/stores/LambderS3FileSource.d.ts +3 -2
- package/dist/stores/LambderS3FileSource.js +11 -13
- package/dist/stores/LambderS3UploadBucket.d.ts +12 -0
- package/dist/stores/LambderS3UploadBucket.js +23 -0
- package/dist/stores/LambderStorageBackedCache.d.ts +2 -2
- package/dist/stores/LambderStoredText.d.ts +26 -0
- package/dist/stores/LambderStoredText.js +25 -0
- package/dist/testing/LambderConformanceRunner.d.ts +9 -2
- package/dist/testing/LambderConformanceRunner.js +9 -2
- package/dist/testing/LambderTestApp.d.ts +65 -7
- package/dist/testing/LambderTestApp.js +101 -6
- package/dist/testing/LambderTestVisitor.d.ts +43 -12
- package/dist/testing/LambderTestVisitor.js +35 -11
- package/dist/testing/bootLambdaPackage.d.ts +105 -0
- package/dist/testing/bootLambdaPackage.js +265 -0
- package/dist/testing/lambdaPackageBootProcess.d.ts +39 -0
- package/dist/testing/lambdaPackageBootProcess.js +130 -0
- package/dist/testing/lambderCacheConformance.d.ts +26 -0
- package/dist/testing/lambderCacheConformance.js +287 -0
- package/dist/testing/lambderCacheStorageConformance.d.ts +25 -0
- package/dist/testing/lambderCacheStorageConformance.js +159 -0
- package/dist/testing/lambderSessionStoreConformance.d.ts +3 -3
- package/dist/testing/lambderSessionStoreConformance.js +3 -3
- package/dist/testing.d.ts +7 -1
- package/dist/testing.js +5 -0
- package/package.json +1 -6
- package/dist/shared/util/boundKeyField.d.ts +0 -20
- package/dist/shared/util/boundKeyField.js +0 -34
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { checkUploadRule, } from "../shared/contracts/LambderUploadBucket.js";
|
|
2
2
|
import { LambderBackoffTimer } from "../shared/util/LambderBackoffTimer.js";
|
|
3
3
|
import { sha256Base64Of } from "../shared/util/LambderTextDigest.js";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* How an upload failed: `reason` for a screen to word, the underlying error
|
|
6
|
+
* as `cause`, and the failed outcome of the app's own call as `callFailure`
|
|
7
|
+
* when that call is what ended the upload, so a screen words a refusal by its
|
|
8
|
+
* code (`callFailure.refusal.code`) as it would on the call itself.
|
|
9
|
+
*/
|
|
5
10
|
export class LambderUploadError extends Error {
|
|
6
11
|
reason;
|
|
12
|
+
/** The ticket or confirm call's failure outcome, when the upload ended on one. */
|
|
13
|
+
callFailure;
|
|
7
14
|
constructor(reason, options = {}) {
|
|
8
15
|
super(options.detail ? `${reason}: ${options.detail}` : reason, { cause: options.cause });
|
|
9
16
|
this.name = "LambderUploadError";
|
|
10
17
|
this.reason = reason;
|
|
18
|
+
if (options.callFailure)
|
|
19
|
+
this.callFailure = options.callFailure;
|
|
11
20
|
}
|
|
12
21
|
}
|
|
13
22
|
/**
|
|
@@ -19,19 +28,38 @@ export class LambderUploadError extends Error {
|
|
|
19
28
|
const TICKET_RENEWAL_LIMIT = 2;
|
|
20
29
|
/** S3's refusals that are the connection's or the service's fault rather than the file's, which its own SDK retries too. */
|
|
21
30
|
const TRANSIENT_STORAGE_CODES = new Set(["RequestTimeout", "SlowDown", "InternalError", "ServiceUnavailable"]);
|
|
31
|
+
/**
|
|
32
|
+
* The ticket and confirm call failures that got no usable answer, so the same
|
|
33
|
+
* call may well pass on another try: no answer at all, the call's own
|
|
34
|
+
* timeout, a 5xx. Every other failure is the endpoint's answer (a refusal, a
|
|
35
|
+
* rejected input, an expired session), and the same call would get it again.
|
|
36
|
+
*/
|
|
37
|
+
const RETRIED_CALL_REASONS = new Set(["network", "timeout", "server"]);
|
|
38
|
+
/** One line on what a failed call said, for the message of the upload error it ends in. */
|
|
39
|
+
const describeCallFailure = (failure) => {
|
|
40
|
+
const said = failure.refusal !== undefined ? failure.refusal.content
|
|
41
|
+
: "error" in failure ? failure.error.message
|
|
42
|
+
: "";
|
|
43
|
+
return said ? `${failure.reason}: ${said}` : failure.reason;
|
|
44
|
+
};
|
|
22
45
|
export class LambderUploadRunner {
|
|
23
46
|
options;
|
|
24
|
-
attempts
|
|
47
|
+
/** How many times a step is tried again after its first try: the storageRetry option's attempts, less that first one. */
|
|
48
|
+
retries;
|
|
25
49
|
/** The ladder one upload's waits climb; each upload() builds a timer of its own from it, so two uploads never share a count. */
|
|
26
50
|
backoff;
|
|
27
51
|
stallTimeoutMs;
|
|
28
52
|
constructor(options) {
|
|
29
53
|
this.options = options;
|
|
30
|
-
this.
|
|
54
|
+
this.retries = Math.max(1, options.storageRetry?.attempts ?? 4) - 1;
|
|
31
55
|
const baseDelayMs = options.storageRetry?.baseDelayMs ?? 1_000;
|
|
32
56
|
const maxDelayMs = options.storageRetry?.maxDelayMs ?? 15_000;
|
|
33
57
|
// A longest wait below the shortest is every wait at the shortest.
|
|
34
58
|
this.backoff = { baseMs: baseDelayMs, maxMs: Math.max(baseDelayMs, maxDelayMs) };
|
|
59
|
+
// Each upload climbs a timer of its own; one built here checks the
|
|
60
|
+
// ladder where the runner is configured, so a storageRetry the timer
|
|
61
|
+
// refuses (a baseDelayMs of 0) fails now rather than on the first upload.
|
|
62
|
+
new LambderBackoffTimer(this.backoff);
|
|
35
63
|
this.stallTimeoutMs = options.stallTimeoutMs ?? 60_000;
|
|
36
64
|
}
|
|
37
65
|
/** For a file input's `accept`, so the picker only offers what the rule takes. */
|
|
@@ -56,11 +84,11 @@ export class LambderUploadRunner {
|
|
|
56
84
|
if (rejection)
|
|
57
85
|
throw new LambderUploadError(rejection);
|
|
58
86
|
const report = (phase, sentBytes = 0) => onProgress?.({ phase, sentBytes, totalBytes: file.size });
|
|
59
|
-
const
|
|
87
|
+
const stopIfAborted = () => {
|
|
60
88
|
if (signal?.aborted)
|
|
61
|
-
throw new LambderUploadError("
|
|
89
|
+
throw new LambderUploadError("aborted");
|
|
62
90
|
};
|
|
63
|
-
|
|
91
|
+
stopIfAborted();
|
|
64
92
|
report("hashing");
|
|
65
93
|
const fileFacts = {
|
|
66
94
|
fileName: file.name,
|
|
@@ -73,28 +101,64 @@ export class LambderUploadRunner {
|
|
|
73
101
|
throw new LambderUploadError("fileUnreadable", { cause });
|
|
74
102
|
}))),
|
|
75
103
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
104
|
+
stopIfAborted();
|
|
105
|
+
// One timer for the whole upload, the app's calls and storage alike.
|
|
106
|
+
// A step that succeeds resets it, so each step has every attempt and
|
|
107
|
+
// its first wait is the shortest again. Every retry so far waited on
|
|
108
|
+
// the timer once, so its count is the retries the step has spent.
|
|
109
|
+
const backoff = new LambderBackoffTimer(this.backoff);
|
|
110
|
+
const waitToRetry = async (lastFailure) => {
|
|
111
|
+
if (backoff.retries >= this.retries)
|
|
112
|
+
throw new LambderUploadError("networkFailed", lastFailure);
|
|
113
|
+
// The wait rejects only for the signal: nothing else cancels it.
|
|
114
|
+
await backoff.wait(signal).catch((cause) => {
|
|
115
|
+
throw new LambderUploadError("aborted", { cause });
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* One of the app's calls through to its output. A failure with no
|
|
120
|
+
* usable answer is tried again on the timer, the same request each
|
|
121
|
+
* time (so a confirm asks about the ticket it was issued); any other
|
|
122
|
+
* failure ends the upload as `refused`, and one after the upload's
|
|
123
|
+
* signal aborted as the abort. A throw is the app's own code failing
|
|
124
|
+
* rather than the call, which no retry cures.
|
|
125
|
+
*/
|
|
126
|
+
const callApp = async (call, refused) => {
|
|
127
|
+
for (;;) {
|
|
128
|
+
stopIfAborted();
|
|
129
|
+
let outcome;
|
|
130
|
+
try {
|
|
131
|
+
outcome = await call();
|
|
132
|
+
}
|
|
133
|
+
catch (cause) {
|
|
134
|
+
throw new LambderUploadError(signal?.aborted ? "aborted" : refused, { cause });
|
|
135
|
+
}
|
|
136
|
+
if (outcome.ok) {
|
|
137
|
+
backoff.reset();
|
|
138
|
+
return outcome.payload;
|
|
139
|
+
}
|
|
140
|
+
const ended = { callFailure: outcome, detail: describeCallFailure(outcome), ...("error" in outcome ? { cause: outcome.error } : {}) };
|
|
141
|
+
if (outcome.reason === "aborted" || signal?.aborted)
|
|
142
|
+
throw new LambderUploadError("aborted", ended);
|
|
143
|
+
if (!RETRIED_CALL_REASONS.has(outcome.reason))
|
|
144
|
+
throw new LambderUploadError(refused, ended);
|
|
145
|
+
await waitToRetry(ended);
|
|
84
146
|
}
|
|
85
147
|
};
|
|
148
|
+
const requestTicket = () => {
|
|
149
|
+
report("requesting");
|
|
150
|
+
return callApp(() => this.options.requestTicket(fileFacts, { signal }), "ticketRefused");
|
|
151
|
+
};
|
|
86
152
|
let issued = await requestTicket();
|
|
87
|
-
const backoff = new LambderBackoffTimer(this.backoff);
|
|
88
|
-
let failedAttempts = 0;
|
|
89
153
|
let ticketRenewals = 0;
|
|
90
154
|
for (;;) {
|
|
91
|
-
|
|
155
|
+
stopIfAborted();
|
|
92
156
|
report("uploading");
|
|
93
157
|
const outcome = await this.send(issued.ticket, file, signal, (sentBytes) => report("uploading", sentBytes));
|
|
94
158
|
if (outcome.kind === "stored")
|
|
95
159
|
break;
|
|
96
|
-
if (outcome.kind === "
|
|
97
|
-
throw new LambderUploadError("
|
|
160
|
+
if (outcome.kind === "aborted")
|
|
161
|
+
throw new LambderUploadError("aborted");
|
|
98
162
|
if (outcome.kind === "rejected") {
|
|
99
163
|
// An expired ticket needs no wait and costs no attempt, only a
|
|
100
164
|
// new ticket; any other refusal is the file's, and final.
|
|
@@ -105,20 +169,11 @@ export class LambderUploadRunner {
|
|
|
105
169
|
}
|
|
106
170
|
// The ticket is kept through a network retry, so a flaky
|
|
107
171
|
// connection does not leave the app a record per attempt.
|
|
108
|
-
|
|
109
|
-
throw new LambderUploadError("networkFailed");
|
|
110
|
-
// The wait rejects only for the signal: nothing else cancels it.
|
|
111
|
-
await backoff.wait(signal).catch((cause) => {
|
|
112
|
-
throw new LambderUploadError("cancelled", { cause });
|
|
113
|
-
});
|
|
172
|
+
await waitToRetry({});
|
|
114
173
|
}
|
|
174
|
+
backoff.reset();
|
|
115
175
|
report("confirming", file.size);
|
|
116
|
-
|
|
117
|
-
return await this.options.confirmUpload(issued.reference, { signal });
|
|
118
|
-
}
|
|
119
|
-
catch (cause) {
|
|
120
|
-
throw new LambderUploadError(signal?.aborted ? "cancelled" : "confirmRefused", { cause });
|
|
121
|
-
}
|
|
176
|
+
return await callApp(() => this.options.confirmUpload(issued, { signal }), "confirmRefused");
|
|
122
177
|
}
|
|
123
178
|
/** Forgets a confirmed upload through the app's endpoint, when it declared one. */
|
|
124
179
|
async discard(receipt) {
|
|
@@ -127,7 +182,7 @@ export class LambderUploadRunner {
|
|
|
127
182
|
/** One upload of the file to storage, in the ticket's form. Never throws: every ending is an outcome. */
|
|
128
183
|
send(ticket, file, signal, onSent) {
|
|
129
184
|
if (signal?.aborted)
|
|
130
|
-
return Promise.resolve({ kind: "
|
|
185
|
+
return Promise.resolve({ kind: "aborted" });
|
|
131
186
|
let request;
|
|
132
187
|
if (ticket.method === "PUT") {
|
|
133
188
|
request = { method: "PUT", url: ticket.uploadUrl, body: file, headers: ticket.headers };
|
|
@@ -176,7 +231,7 @@ const sendWithXhr = ({ method, url, body, headers }, fileBytes, stallTimeoutMs,
|
|
|
176
231
|
settle(rejectedOutcome(request.status, request.responseText));
|
|
177
232
|
};
|
|
178
233
|
request.onerror = () => settle({ kind: "unreachable" });
|
|
179
|
-
request.onabort = () => settle(stalled ? { kind: "unreachable" } : { kind: "
|
|
234
|
+
request.onabort = () => settle(stalled ? { kind: "unreachable" } : { kind: "aborted" });
|
|
180
235
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
181
236
|
watchForStall();
|
|
182
237
|
try {
|
|
@@ -196,7 +251,7 @@ const sendWithFetch = async ({ method, url, body, headers }, fileBytes, signal,
|
|
|
196
251
|
response = await fetch(url, { method, body, headers, signal });
|
|
197
252
|
}
|
|
198
253
|
catch {
|
|
199
|
-
return signal?.aborted ? { kind: "
|
|
254
|
+
return signal?.aborted ? { kind: "aborted" } : { kind: "unreachable" };
|
|
200
255
|
}
|
|
201
256
|
if (response.ok) {
|
|
202
257
|
onSent(fileBytes);
|
package/dist/client.d.ts
CHANGED
|
@@ -19,17 +19,17 @@ export { apiNameKeyOf, lookupApiSignature, readApiSignature, API_SIGNATURE_HEX_L
|
|
|
19
19
|
export type { LambderApiSignatureMap } from "./shared/wire/LambderApiSignatureMap.js";
|
|
20
20
|
export { RELOAD_LOOP_WINDOW_MS } from "./client/LambderReloadLoopBreaker.js";
|
|
21
21
|
export { compareDottedVersions, isDottedVersion } from "./shared/wire/LambderVersionOrder.js";
|
|
22
|
-
export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiCallFailure, LambderApiValidationFailure, LambderApiEnvelopeFailure, LambderApiHttpAnswer, } from "./shared/wire/LambderApiOutcome.js";
|
|
22
|
+
export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiFailure, LambderApiCallFailure, LambderApiValidationFailure, LambderApiEnvelopeFailure, LambderApiHttpAnswer, } from "./shared/wire/LambderApiOutcome.js";
|
|
23
23
|
export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs, LambderLogListHandler, LambderCallerEndpoint, LambderCallerGroupCalls, LambderCallerMembers, } from "./client/LambderCaller.js";
|
|
24
24
|
export { apiCallPath, splitApiName, LAMBDER_RESERVED_GROUP_NAMES, LAMBDER_RESERVED_ACTION_NAMES } from "./shared/wire/LambderApiNames.js";
|
|
25
25
|
export type { LambderContractGroupsOf, LambderContractNamesInGroup, LambderContractActionOf } from "./shared/wire/LambderApiGroupCalls.js";
|
|
26
26
|
export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
|
|
27
27
|
export type { LambderIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
|
|
28
28
|
export { LambderApiRefusal, isLambderApiRefusal, isLambderRefusalCode, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
29
|
-
export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderPlainRefusalMessage, LambderUncheckedRefusalMessage, LambderRefusalCode, LambderRefuseOptions, LambderDeclaredRefuseOptions, LambderDeclaredRefuse, } from "./shared/wire/LambderApiRefusal.js";
|
|
29
|
+
export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderPlainRefusalMessage, LambderRateLimitRefusalData, LambderUncheckedRefusalMessage, LambderRefusalCode, LambderRefuseOptions, LambderDeclaredRefuseOptions, LambderDeclaredRefuse, } from "./shared/wire/LambderApiRefusal.js";
|
|
30
30
|
export { isObjectPayload } from "./shared/wire/LambderObjectPayload.js";
|
|
31
31
|
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiSuccessEnvelope, LambderApiRefusalEnvelope, LambderRefusalEnvelopeFields, LambderApiRefusalConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderContractRefusalsOf, LambderContractRefusalMessage, LambderContractRefusals, LambderContractAnyRefusalMessage, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
32
|
-
export { apiGuardParam, apiGuardParamExportName } from "./shared/wire/LambderApiOptionEntries.js";
|
|
32
|
+
export { apiGuardParam, apiGuardParamExportName, apisWithGuard } from "./shared/wire/LambderApiOptionEntries.js";
|
|
33
33
|
export type { LambderApiOptionEntries, LambderApiOptionEntry, LambderRateLimitPolicyEntry, LambderGuardDeclarationEntry, LambderApisWithGuard, LambderApisGuardedBy, LambderApisWithMode, LambderGuardParamOf, LambderApiGuardParam, } from "./shared/wire/LambderApiOptionEntries.js";
|
|
34
34
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
35
35
|
export type { LambderCrashDetail, LambderCrashCause } from "./shared/wire/LambderCrashDetail.js";
|
|
@@ -37,13 +37,13 @@ export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_
|
|
|
37
37
|
export type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload, LambderRequestCompressionOption, LambderRequestCompressionSettings, } from "./shared/wire/LambderRequestPayload.js";
|
|
38
38
|
export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption.js";
|
|
39
39
|
export type { LambderCompressionOption, LambderCompressionSettingsBase } from "./shared/wire/LambderCompressionOption.js";
|
|
40
|
-
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./shared/LambderHtml.js";
|
|
40
|
+
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue, type LambderJsonScriptOptions } from "./shared/LambderHtml.js";
|
|
41
41
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
42
|
-
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
42
|
+
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nReadonlyInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nDictionaryEntry, LambderI18nPluralEntry, LambderI18nPluralCategory, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
43
43
|
export type { LambderHttpStatusCode, LambderRefusalStatusCode } from "./shared/wire/LambderHttpStatus.js";
|
|
44
44
|
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
45
45
|
export type { LambderBackoffTimerOptions } from "./shared/util/LambderBackoffTimer.js";
|
|
46
|
-
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
46
|
+
export { LambderSignedClaims, keyedDigest, randomSecret, randomCode } from "./shared/util/LambderSignedClaims.js";
|
|
47
47
|
export type { LambderSignedClaimsOptions } from "./shared/util/LambderSignedClaims.js";
|
|
48
48
|
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
49
49
|
export { LambderUploadRunner, LambderUploadError } from "./client/LambderUploadRunner.js";
|
package/dist/client.js
CHANGED
|
@@ -28,7 +28,7 @@ export { LambderApiRefusal, isLambderApiRefusal, isLambderRefusalCode, refuse, r
|
|
|
28
28
|
export { isObjectPayload } from "./shared/wire/LambderObjectPayload.js";
|
|
29
29
|
// The server's declared options as plain data (the generated options module's
|
|
30
30
|
// entry types) and the readers a client derives its own facts from.
|
|
31
|
-
export { apiGuardParam, apiGuardParamExportName } from "./shared/wire/LambderApiOptionEntries.js";
|
|
31
|
+
export { apiGuardParam, apiGuardParamExportName, apisWithGuard } from "./shared/wire/LambderApiOptionEntries.js";
|
|
32
32
|
// A crash described for a caller allowed to see it (the envelope's `crash` field; pure, no Node built-ins).
|
|
33
33
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
34
34
|
// Request payload compression (browser-safe: gzip via CompressionStream, no Node built-ins).
|
|
@@ -45,7 +45,7 @@ export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
|
45
45
|
// Signed claims tokens, for the isomorphic code that verifies them where a
|
|
46
46
|
// server's secret is at hand (an edge Worker, a shared backend package);
|
|
47
47
|
// never in a page, which holds no secret to verify with.
|
|
48
|
-
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
48
|
+
export { LambderSignedClaims, keyedDigest, randomSecret, randomCode } from "./shared/util/LambderSignedClaims.js";
|
|
49
49
|
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
50
50
|
// Direct uploads: the runner that takes a file from the browser straight to
|
|
51
51
|
// storage, and the vocabulary it shares with the server's bucket.
|
package/dist/core/Lambder.d.ts
CHANGED
|
@@ -78,11 +78,12 @@ type LambderApiHandlerContext<TApp extends LambderAppTypes, TPayload, TGuardsOpt
|
|
|
78
78
|
* ```
|
|
79
79
|
*/
|
|
80
80
|
export default class Lambder<TApp extends LambderAppTypes = LambderPlainAppTypes, _TContract extends Record<string, any> = {}> {
|
|
81
|
-
|
|
81
|
+
/** Where API calls go: `{apiPath}/{group}/{action}`. */
|
|
82
|
+
readonly apiPath: string;
|
|
82
83
|
/** Stamped on every API answer's envelope as apiVersion. Informational: a client's staleness is judged per endpoint by its signature, see apiSignatures(). */
|
|
83
|
-
apiVersion: null | string;
|
|
84
|
+
readonly apiVersion: null | string;
|
|
84
85
|
/** The instance's file reader (source + caches), or null without the files option. */
|
|
85
|
-
files: LambderFiles | null;
|
|
86
|
+
readonly files: LambderFiles | null;
|
|
86
87
|
/**
|
|
87
88
|
* Type property for extracting the API contract: every registered API's
|
|
88
89
|
* input, output, mode and declared options, as a client calls it.
|
|
@@ -125,7 +126,6 @@ export default class Lambder<TApp extends LambderAppTypes = LambderPlainAppTypes
|
|
|
125
126
|
/** Every API's refusals option as written, for apiOptionEntries(); its definition holds the resolved set. */
|
|
126
127
|
private readonly refusalOptions;
|
|
127
128
|
private hookList;
|
|
128
|
-
private createdHooks;
|
|
129
129
|
private initPromise;
|
|
130
130
|
private globalErrorHandler;
|
|
131
131
|
private routeFallbackHandler;
|
|
@@ -411,6 +411,17 @@ export default class Lambder<TApp extends LambderAppTypes = LambderPlainAppTypes
|
|
|
411
411
|
* endpoint and never moves when registrations are reordered.
|
|
412
412
|
*/
|
|
413
413
|
apiOptionEntries(): Promise<LambderApiOptionEntries>;
|
|
414
|
+
/**
|
|
415
|
+
* Every registered API's input and output schemas by name, sorted by
|
|
416
|
+
* name: what writeApiSchemas (lambder/build) writes as JSON Schema to a
|
|
417
|
+
* module the mock validates its calls against. A build-time view, as
|
|
418
|
+
* apiSignatureEntries() is: it comes off the server instance, which a
|
|
419
|
+
* generator imports and a client never does.
|
|
420
|
+
*/
|
|
421
|
+
apiSchemaEntries(): Promise<Record<string, {
|
|
422
|
+
input: z.ZodType;
|
|
423
|
+
output: z.ZodType;
|
|
424
|
+
}>>;
|
|
414
425
|
getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder;
|
|
415
426
|
private getResolver;
|
|
416
427
|
getHandler(): LambderHandler;
|
|
@@ -510,8 +521,8 @@ export default class Lambder<TApp extends LambderAppTypes = LambderPlainAppTypes
|
|
|
510
521
|
* The answer to a request that needed a session and has none, whether it
|
|
511
522
|
* never had one or it ended while the request held it: an API call gets
|
|
512
523
|
* the protocol's sessionExpired envelope, as the pipeline gives a session
|
|
513
|
-
* API, and anything else the setSessionExpiredRouteHandler answer, a
|
|
514
|
-
* by default.
|
|
524
|
+
* API, and anything else the setSessionExpiredRouteHandler answer, a
|
|
525
|
+
* plain-text 401 by default, as the framework's own 404 and 500 are.
|
|
515
526
|
*/
|
|
516
527
|
private sessionMissingResponse;
|
|
517
528
|
/**
|
|
@@ -569,13 +580,21 @@ export default class Lambder<TApp extends LambderAppTypes = LambderPlainAppTypes
|
|
|
569
580
|
*/
|
|
570
581
|
private apiErrorResponse;
|
|
571
582
|
}
|
|
583
|
+
/** The options create() takes: the constructor's, less the two declareRefusals() supplies. */
|
|
584
|
+
export type LambderInitCreateOptions<TSessionData> = Omit<LambderCreateOptions<TSessionData>, "refusals" | "requireRefusalCodes">;
|
|
572
585
|
/**
|
|
573
|
-
* The canonical way to create an instance:
|
|
574
|
-
* then create with the full configuration
|
|
575
|
-
* guard and idempotency types are inferred
|
|
576
|
-
* is born fully typed, and the endpoints
|
|
577
|
-
* typed against it. There are no ordering
|
|
578
|
-
* half-configured.
|
|
586
|
+
* The entry point of an app, and the canonical way to create an instance:
|
|
587
|
+
* fix the session data type first, then create with the full configuration
|
|
588
|
+
* in one declaration. The policy, guard and idempotency types are inferred
|
|
589
|
+
* from the options, so the instance is born fully typed, and the endpoints
|
|
590
|
+
* declared with its defineApi are typed against it. There are no ordering
|
|
591
|
+
* rules, and nothing can be half-configured.
|
|
592
|
+
*
|
|
593
|
+
* Beside create() it hands out the builders that share the session data
|
|
594
|
+
* type (guard, rateLimitKey, refuse). `declareRefusals()` binds the app's
|
|
595
|
+
* refusal vocabulary too, so a guard's ctx.refuse and the init's own refuse
|
|
596
|
+
* are typed to it before any instance exists, and create() gives it to the
|
|
597
|
+
* instance for every API's refusals option to name codes from.
|
|
579
598
|
*
|
|
580
599
|
* ```typescript
|
|
581
600
|
* // app.ts (imports no api modules, so modules can import from it)
|
|
@@ -604,15 +623,6 @@ export default class Lambder<TApp extends LambderAppTypes = LambderPlainAppTypes
|
|
|
604
623
|
* session type in the first call lets the second infer everything else.
|
|
605
624
|
* `new Lambder(options)` serves untyped or session-data-free instances.
|
|
606
625
|
*/
|
|
607
|
-
/** The options create() takes: the constructor's, less the two declareRefusals() supplies. */
|
|
608
|
-
export type LambderInitCreateOptions<TSessionData> = Omit<LambderCreateOptions<TSessionData>, "refusals" | "requireRefusalCodes">;
|
|
609
|
-
/**
|
|
610
|
-
* The entry point of an app: binds the session data type, and hands out the
|
|
611
|
-
* builders and create() that share it. `declareRefusals()` binds the app's
|
|
612
|
-
* refusal vocabulary too, so a guard's ctx.refuse and the init's own refuse
|
|
613
|
-
* are typed to it before any instance exists, and create() gives it to the
|
|
614
|
-
* instance for every API's refusals option to name codes from.
|
|
615
|
-
*/
|
|
616
626
|
export declare const initLambder: <TSessionData = any>() => {
|
|
617
627
|
/**
|
|
618
628
|
* Declares the app's refusal vocabulary: every code once, with the schema
|
package/dist/core/Lambder.js
CHANGED
|
@@ -80,6 +80,12 @@ export default class Lambder {
|
|
|
80
80
|
// Construction
|
|
81
81
|
// Everything an instance is, fixed before the first registration.
|
|
82
82
|
// =====================================================================
|
|
83
|
+
// Fixed at construction, because what was built from them keeps its own
|
|
84
|
+
// copy: the pipeline and the crash handling stamp the apiVersion they
|
|
85
|
+
// were given, servePublicFiles serves through the reader it was given,
|
|
86
|
+
// and the callers of an instance post to the apiPath they were built
|
|
87
|
+
// with. A later write would leave the instance disagreeing with itself.
|
|
88
|
+
/** Where API calls go: `{apiPath}/{group}/{action}`. */
|
|
83
89
|
apiPath;
|
|
84
90
|
/** Stamped on every API answer's envelope as apiVersion. Informational: a client's staleness is judged per endpoint by its signature, see apiSignatures(). */
|
|
85
91
|
apiVersion;
|
|
@@ -126,8 +132,7 @@ export default class Lambder {
|
|
|
126
132
|
refusalVocabulary;
|
|
127
133
|
/** Every API's refusals option as written, for apiOptionEntries(); its definition holds the resolved set. */
|
|
128
134
|
refusalOptions = new Map();
|
|
129
|
-
hookList = { "beforeRender": [], "afterRender": [], "fallback": [] };
|
|
130
|
-
createdHooks = [];
|
|
135
|
+
hookList = { "created": [], "beforeRender": [], "afterRender": [], "fallback": [] };
|
|
131
136
|
initPromise = null;
|
|
132
137
|
globalErrorHandler = null;
|
|
133
138
|
routeFallbackHandler = null;
|
|
@@ -211,6 +216,7 @@ export default class Lambder {
|
|
|
211
216
|
rateLimits: options.rateLimits,
|
|
212
217
|
guards: options.guards,
|
|
213
218
|
idempotency: options.idempotency,
|
|
219
|
+
atRestSecret: session?.sessionSalt,
|
|
214
220
|
});
|
|
215
221
|
this.trustedClientIpHeaders = options.trustedClientIpHeaders ?? [];
|
|
216
222
|
this.trustedHostHeaders = options.trustedHostHeaders ?? [];
|
|
@@ -539,14 +545,11 @@ export default class Lambder {
|
|
|
539
545
|
this.apiHandlers.set(name, { output: api.output, compress: api.compress, handler: api.handler });
|
|
540
546
|
}
|
|
541
547
|
addHook(hookEvent, hookFn, priority = 0) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
this.hookList[hookEvent].push({ priority, hookFn });
|
|
548
|
-
this.hookList[hookEvent].sort((a, b) => a.priority - b.priority);
|
|
549
|
-
}
|
|
548
|
+
// In priority order, lower first; the sort is stable, so hooks of one
|
|
549
|
+
// priority run in the order they were added. A created hook runs once,
|
|
550
|
+
// lazily, before the first request or event is handled.
|
|
551
|
+
this.hookList[hookEvent].push({ priority, hookFn });
|
|
552
|
+
this.hookList[hookEvent].sort((a, b) => a.priority - b.priority);
|
|
550
553
|
return this;
|
|
551
554
|
}
|
|
552
555
|
addAction(filter, actionFn) {
|
|
@@ -723,6 +726,23 @@ export default class Lambder {
|
|
|
723
726
|
}
|
|
724
727
|
return { apis, rateLimitPolicies, guards };
|
|
725
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Every registered API's input and output schemas by name, sorted by
|
|
731
|
+
* name: what writeApiSchemas (lambder/build) writes as JSON Schema to a
|
|
732
|
+
* module the mock validates its calls against. A build-time view, as
|
|
733
|
+
* apiSignatureEntries() is: it comes off the server instance, which a
|
|
734
|
+
* generator imports and a client never does.
|
|
735
|
+
*/
|
|
736
|
+
async apiSchemaEntries() {
|
|
737
|
+
await this.loadApiGroups();
|
|
738
|
+
const entries = {};
|
|
739
|
+
for (const name of [...this.apiDefinitions.keys()].sort()) {
|
|
740
|
+
// Both are always there on the server: registration takes them from the declaration, which requires them.
|
|
741
|
+
const { input, output } = this.apiDefinitions.get(name);
|
|
742
|
+
entries[name] = { input: input, output: output };
|
|
743
|
+
}
|
|
744
|
+
return entries;
|
|
745
|
+
}
|
|
726
746
|
getResponseBuilder(ctx) {
|
|
727
747
|
return new LambderResponseBuilder({
|
|
728
748
|
files: this.files,
|
|
@@ -759,8 +779,8 @@ export default class Lambder {
|
|
|
759
779
|
ensureInitialized() {
|
|
760
780
|
if (!this.initPromise) {
|
|
761
781
|
const pending = (async () => {
|
|
762
|
-
for (const
|
|
763
|
-
await hookFn(this);
|
|
782
|
+
for (const hook of this.hookList["created"]) {
|
|
783
|
+
await hook.hookFn(this);
|
|
764
784
|
}
|
|
765
785
|
})();
|
|
766
786
|
this.initPromise = pending;
|
|
@@ -828,11 +848,12 @@ export default class Lambder {
|
|
|
828
848
|
for (const hook of this.hookList["fallback"]) {
|
|
829
849
|
await hook.hookFn(currentCtx, resolver);
|
|
830
850
|
}
|
|
831
|
-
// A request under apiPath is the API's to answer, whatever it
|
|
832
|
-
// for. A root apiPath shares every path with the site,
|
|
833
|
-
//
|
|
851
|
+
// A request at or under apiPath is the API's to answer, whatever it
|
|
852
|
+
// asked for. A root apiPath shares every path with the site, the site
|
|
853
|
+
// root included, so there only the calls are.
|
|
834
854
|
const apiArea = this.apiPath.replace(/\/+$/, "");
|
|
835
|
-
const isAPI = currentCtx.api !== null
|
|
855
|
+
const isAPI = currentCtx.api !== null
|
|
856
|
+
|| (apiArea !== "" && (currentCtx.path === this.apiPath || currentCtx.path.startsWith(`${apiArea}/`)));
|
|
836
857
|
if (isAPI) {
|
|
837
858
|
if (this.apiFallbackHandler)
|
|
838
859
|
return await this.apiFallbackHandler(currentCtx, resolver);
|
|
@@ -871,8 +892,9 @@ export default class Lambder {
|
|
|
871
892
|
return responseFromAnswer(versionExpiredAnswer(this.apiVersion));
|
|
872
893
|
// The protocol's own pre-pass, run here rather than left to the
|
|
873
894
|
// pipeline so that hooks and route matching see a plain payload,
|
|
874
|
-
// and so a stale client
|
|
875
|
-
// not the name it asked
|
|
895
|
+
// and so a stale client, or a body that is no envelope, is
|
|
896
|
+
// answered before any of them, whether or not the name it asked
|
|
897
|
+
// for exists.
|
|
876
898
|
const prepared = await this.pipeline.prepare(ctx.api);
|
|
877
899
|
if (prepared)
|
|
878
900
|
return responseFromAnswer(prepared);
|
|
@@ -1141,14 +1163,14 @@ export default class Lambder {
|
|
|
1141
1163
|
* The answer to a request that needed a session and has none, whether it
|
|
1142
1164
|
* never had one or it ended while the request held it: an API call gets
|
|
1143
1165
|
* the protocol's sessionExpired envelope, as the pipeline gives a session
|
|
1144
|
-
* API, and anything else the setSessionExpiredRouteHandler answer, a
|
|
1145
|
-
* by default.
|
|
1166
|
+
* API, and anything else the setSessionExpiredRouteHandler answer, a
|
|
1167
|
+
* plain-text 401 by default, as the framework's own 404 and 500 are.
|
|
1146
1168
|
*/
|
|
1147
1169
|
async sessionMissingResponse(ctx, resolver) {
|
|
1148
1170
|
if (ctx.api)
|
|
1149
1171
|
return responseFromAnswer(sessionExpiredAnswer(this.apiVersion, ctx.logList));
|
|
1150
1172
|
if (!this.sessionExpiredRouteHandler)
|
|
1151
|
-
return resolver.
|
|
1173
|
+
return resolver.text("Session required.", { statusCode: 401 });
|
|
1152
1174
|
try {
|
|
1153
1175
|
return await this.sessionExpiredRouteHandler(ctx, resolver);
|
|
1154
1176
|
}
|
|
@@ -1306,11 +1328,45 @@ const lambderInitOf = (declared) => ({
|
|
|
1306
1328
|
},
|
|
1307
1329
|
});
|
|
1308
1330
|
/**
|
|
1309
|
-
* The entry point of an app
|
|
1310
|
-
*
|
|
1331
|
+
* The entry point of an app, and the canonical way to create an instance:
|
|
1332
|
+
* fix the session data type first, then create with the full configuration
|
|
1333
|
+
* in one declaration. The policy, guard and idempotency types are inferred
|
|
1334
|
+
* from the options, so the instance is born fully typed, and the endpoints
|
|
1335
|
+
* declared with its defineApi are typed against it. There are no ordering
|
|
1336
|
+
* rules, and nothing can be half-configured.
|
|
1337
|
+
*
|
|
1338
|
+
* Beside create() it hands out the builders that share the session data
|
|
1339
|
+
* type (guard, rateLimitKey, refuse). `declareRefusals()` binds the app's
|
|
1311
1340
|
* refusal vocabulary too, so a guard's ctx.refuse and the init's own refuse
|
|
1312
1341
|
* are typed to it before any instance exists, and create() gives it to the
|
|
1313
1342
|
* instance for every API's refusals option to name codes from.
|
|
1343
|
+
*
|
|
1344
|
+
* ```typescript
|
|
1345
|
+
* // app.ts (imports no api modules, so modules can import from it)
|
|
1346
|
+
* export const lambderApp = initLambder<SessionData>().create({
|
|
1347
|
+
* apiPath: "/api",
|
|
1348
|
+
* session: { store: new LambderDdbSessionStore({ tableName: "app-session", region: "us-east-1" }), sessionSalt: "..." },
|
|
1349
|
+
* rateLimits: { limiter, policies },
|
|
1350
|
+
* guards,
|
|
1351
|
+
* idempotency: { store },
|
|
1352
|
+
* });
|
|
1353
|
+
* export const { defineApi, defineApiGroup, lazyApiGroup } = lambderApp;
|
|
1354
|
+
*
|
|
1355
|
+
* // orders.ts
|
|
1356
|
+
* export const orderApis = defineApiGroup("orders", {
|
|
1357
|
+
* place: defineApi({ input, output, guards: "signedIn" }, async (ctx) => ...),
|
|
1358
|
+
* });
|
|
1359
|
+
*
|
|
1360
|
+
* // index.ts: registration only
|
|
1361
|
+
* const lambder = lambderApp.registerApiGroups(orderApis).addHook(...);
|
|
1362
|
+
* export const handler = lambder.getHandler();
|
|
1363
|
+
* ```
|
|
1364
|
+
*
|
|
1365
|
+
* Curried because TypeScript type arguments are all-or-nothing per call:
|
|
1366
|
+
* passing the session data type to `new Lambder<S>(...)` would silently
|
|
1367
|
+
* widen the inferred policy and guard types to their {} defaults. Fixing the
|
|
1368
|
+
* session type in the first call lets the second infer everything else.
|
|
1369
|
+
* `new Lambder(options)` serves untyped or session-data-free instances.
|
|
1314
1370
|
*/
|
|
1315
1371
|
export const initLambder = () => ({
|
|
1316
1372
|
...lambderInitOf(null),
|
|
@@ -27,7 +27,7 @@ export type LambderCallSummary = {
|
|
|
27
27
|
coldStart: boolean;
|
|
28
28
|
/** The invocation's request id, the one Lambda's own log lines for it carry. */
|
|
29
29
|
requestId: string | null;
|
|
30
|
-
/** The request id of the invocation that called this one over a
|
|
30
|
+
/** The request id of the invocation that called this one over a Lambda invoke (LambderInvokeCaller), which carries it; null otherwise. */
|
|
31
31
|
parentRequestId: string | null;
|
|
32
32
|
};
|
|
33
33
|
/**
|