lambder 7.3.1 → 8.1.1
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 +1047 -3
- package/README.md +46 -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 +68 -62
- package/dist/api/LambderApiIdempotency.js +214 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +47 -38
- package/dist/api/LambderApiPipeline.js +122 -63
- package/dist/api/LambderApiRateLimits.d.ts +201 -54
- package/dist/api/LambderApiRateLimits.js +185 -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/ContractTypePrinter.d.ts +85 -0
- package/dist/build/ContractTypePrinter.js +402 -0
- package/dist/build/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/moduleLocation.d.ts +11 -0
- package/dist/build/moduleLocation.js +6 -0
- package/dist/build/writeApiContract.d.ts +78 -0
- package/dist/build/writeApiContract.js +302 -0
- package/dist/build/writeApiSignatures.d.ts +114 -0
- package/dist/build/writeApiSignatures.js +217 -0
- package/dist/build/writeFileAtomically.d.ts +8 -0
- package/dist/build/writeFileAtomically.js +22 -0
- package/dist/build.d.ts +14 -0
- package/dist/build.js +11 -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/LambderUploadRunner.d.ts +96 -0
- package/dist/client/LambderUploadRunner.js +234 -0
- package/dist/client/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +9 -3
- package/dist/client.js +6 -1
- package/dist/core/Lambder.d.ts +143 -79
- package/dist/core/Lambder.js +350 -231
- 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 +21 -7
- package/dist/core/LambderFiles.js +62 -34
- 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 +25 -8
- package/dist/index.js +13 -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 +29 -9
- package/dist/invoke/LambderLambdaEvent.js +40 -22
- package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
- package/dist/invoke/lambderHandlerTransport.js +15 -18
- 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 +43 -33
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
- package/dist/mock/lambderMockUploadMswHandler.js +28 -0
- package/dist/mock.d.ts +4 -1
- package/dist/mock.js +6 -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 +124 -46
- package/dist/session/LambderSessionManager.js +262 -137
- 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/contracts/LambderUploadBucket.d.ts +154 -0
- package/dist/shared/contracts/LambderUploadBucket.js +74 -0
- 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/LambderContentDisposition.d.ts +10 -0
- package/dist/shared/util/LambderContentDisposition.js +13 -0
- 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/LambderTextDigest.d.ts +7 -5
- package/dist/shared/util/LambderTextDigest.js +11 -5
- 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 +98 -53
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
- package/dist/shared/wire/LambderApiRefusal.js +42 -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 +6 -7
- package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
- package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
- package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
- package/dist/shared/wire/LambderUploadRefusal.js +18 -0
- package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
- package/dist/shared/wire/LambderUploadSchemas.js +30 -0
- 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 +80 -38
- 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/LambderMemoryUploadBucket.d.ts +99 -0
- package/dist/stores/LambderMemoryUploadBucket.js +219 -0
- package/dist/stores/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
- package/dist/stores/LambderS3UploadBucket.js +144 -0
- package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
- package/dist/stores/LambderSdkInstallHint.js +14 -0
- package/dist/testing/LambderTestApp.d.ts +23 -25
- package/dist/testing/LambderTestApp.js +22 -24
- package/dist/testing/LambderTestVisitor.d.ts +10 -12
- package/dist/testing/LambderTestVisitor.js +15 -15
- package/dist/testing.d.ts +3 -0
- package/dist/testing.js +2 -0
- package/package.json +26 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
- package/dist/api/LambderApiPolicyEngine.js +0 -85
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- package/dist/shared/util/LambderKeyFields.js +0 -34
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/** What an app accepts for one kind of upload, declared once and read by both sides. */
|
|
2
|
+
export type LambderUploadRule = {
|
|
3
|
+
/** The largest file accepted, in bytes. */
|
|
4
|
+
maxBytes: number;
|
|
5
|
+
/** The accepted content types, exact, with no wildcards: `["application/pdf"]`. */
|
|
6
|
+
mimeTypes: readonly string[];
|
|
7
|
+
};
|
|
8
|
+
/** What the browser says about a file before any of its bytes move. */
|
|
9
|
+
export type LambderUploadFileFacts = {
|
|
10
|
+
fileName: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
byteSize: number;
|
|
13
|
+
/** SHA-256 of the file's bytes as base64, the form storage checks an upload against: 43 characters and one pad. */
|
|
14
|
+
sha256Base64: string;
|
|
15
|
+
};
|
|
16
|
+
/** Everything the browser needs to post one file to storage, and until when. */
|
|
17
|
+
export type LambderUploadTicket = {
|
|
18
|
+
uploadUrl: string;
|
|
19
|
+
/** Sent as form fields ahead of the file, which storage wants last. */
|
|
20
|
+
formFields: Record<string, string>;
|
|
21
|
+
/** Epoch milliseconds after which storage refuses the ticket. */
|
|
22
|
+
expiresAt: number;
|
|
23
|
+
};
|
|
24
|
+
/** What a bucket holds under a key, compared with what the browser said it would upload. */
|
|
25
|
+
export type LambderUploadVerdict = {
|
|
26
|
+
verified: true;
|
|
27
|
+
}
|
|
28
|
+
/** `objectMissing`: nothing was posted. `factsMismatch`: something else sits under the key. */
|
|
29
|
+
| {
|
|
30
|
+
verified: false;
|
|
31
|
+
reason: "objectMissing" | "factsMismatch";
|
|
32
|
+
};
|
|
33
|
+
/** How a browser presents an object it reads: in place (a PDF in its viewer) or saved as a file, under a name. */
|
|
34
|
+
export type LambderUploadContentDisposition = {
|
|
35
|
+
disposition: "inline" | "attachment";
|
|
36
|
+
/** The name the file is saved or shown under; any characters, encoded for the header. Without one, the browser takes the key's last segment. */
|
|
37
|
+
fileName?: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* What storage keeps beside an object's bytes. A ticket pins every one of
|
|
41
|
+
* these in its signed policy, so the browser posts them unchanged.
|
|
42
|
+
*/
|
|
43
|
+
export type LambderUploadObjectOptions = {
|
|
44
|
+
/**
|
|
45
|
+
* The object's tags, at most ten. They are how an object gets a time to
|
|
46
|
+
* live: S3 has no expiry per object, and a lifecycle rule keyed on a tag
|
|
47
|
+
* (`retention: "30d"` expiring after 30 days) deletes what carries it.
|
|
48
|
+
* Keys up to 128 characters, values up to 256.
|
|
49
|
+
*/
|
|
50
|
+
tags?: Record<string, string>;
|
|
51
|
+
/** User metadata, kept as `x-amz-meta-<name>` and returned with every read of the object. Names are lowercased; printable ASCII, 2 KB in all. */
|
|
52
|
+
metadata?: Record<string, string>;
|
|
53
|
+
/** The Cache-Control every read of the object answers with, for one served through a CDN. */
|
|
54
|
+
cacheControl?: string;
|
|
55
|
+
/** How a browser presents the object by default; a download link can say otherwise. */
|
|
56
|
+
contentDisposition?: LambderUploadContentDisposition;
|
|
57
|
+
};
|
|
58
|
+
/** The longest a ticket or a download link may live: S3's limit for a signature, seven days. */
|
|
59
|
+
export declare const UPLOAD_SIGNATURE_MAX_SECONDS: number;
|
|
60
|
+
/** Throws unless a lifetime is a positive number of seconds within S3's seven days. */
|
|
61
|
+
export declare const assertSignatureLifetime: (seconds: number, name: string) => void;
|
|
62
|
+
/** Throws when object options break a limit S3 holds them to, so the mistake shows where the app wrote it rather than as a refused post. */
|
|
63
|
+
export declare const assertObjectOptions: (options: LambderUploadObjectOptions | undefined) => void;
|
|
64
|
+
/** What a rule holds against a file, known before any of it is sent. */
|
|
65
|
+
export type LambderUploadRuleVerdict = "fileEmpty" | "fileTypeRejected" | "fileTooLarge";
|
|
66
|
+
/** A rule's verdict on a file, or null when it may be uploaded: the check the browser makes before hashing and the bucket makes before signing. */
|
|
67
|
+
export declare const checkUploadRule: (rule: LambderUploadRule, file: {
|
|
68
|
+
mimeType: string;
|
|
69
|
+
byteSize: number;
|
|
70
|
+
}) => LambderUploadRuleVerdict | null;
|
|
71
|
+
/**
|
|
72
|
+
* Throws when an object key would not pin the key a ticket writes to: S3
|
|
73
|
+
* substitutes the uploaded file's own name for `${filename}` in a presigned
|
|
74
|
+
* POST's key, so a key holding it lets the browser choose where under it the
|
|
75
|
+
* file lands. A key is the app's, built from its own ids, so this is a
|
|
76
|
+
* programming error rather than a refusal.
|
|
77
|
+
*/
|
|
78
|
+
export declare const assertPinnedObjectKey: (objectKey: string) => void;
|
|
79
|
+
/**
|
|
80
|
+
* Object storage a browser uploads to directly, with tickets the server
|
|
81
|
+
* signs, and that the server reads, writes and deletes through for the rest
|
|
82
|
+
* of an object's life. One instance per bucket. LambderS3UploadBucket in
|
|
83
|
+
* production; LambderMemoryUploadBucket in tests and the mock runtime.
|
|
84
|
+
*
|
|
85
|
+
* The object key is always chosen by the app from its own ids. It never
|
|
86
|
+
* comes from the browser, and a ticket pins it, so a ticket cannot be used to
|
|
87
|
+
* write anywhere else.
|
|
88
|
+
*/
|
|
89
|
+
export interface LambderUploadBucket {
|
|
90
|
+
/**
|
|
91
|
+
* Signs a ticket for exactly the file the browser described, or refuses
|
|
92
|
+
* (a LambderApiRefusal, code `lambder/upload-empty`,
|
|
93
|
+
* `lambder/upload-type-rejected` or `lambder/upload-too-large`) when the
|
|
94
|
+
* rule does not accept it. Storage then enforces every fact: the post
|
|
95
|
+
* fails unless the body has that byte size, that content type and that
|
|
96
|
+
* SHA-256, so what verifies later is what was described here. `object`
|
|
97
|
+
* is what the stored object carries besides, pinned the same way, and
|
|
98
|
+
* `lifetimeSeconds` overrides the bucket's ticket lifetime for this one.
|
|
99
|
+
*/
|
|
100
|
+
issueUploadTicket(options: {
|
|
101
|
+
objectKey: string;
|
|
102
|
+
fileFacts: LambderUploadFileFacts;
|
|
103
|
+
uploadRule: LambderUploadRule;
|
|
104
|
+
lifetimeSeconds?: number;
|
|
105
|
+
object?: LambderUploadObjectOptions;
|
|
106
|
+
}): Promise<LambderUploadTicket>;
|
|
107
|
+
/**
|
|
108
|
+
* Asks storage what it holds under the key. The browser saying "done"
|
|
109
|
+
* proves nothing, so an app calls this before its record counts as
|
|
110
|
+
* uploaded.
|
|
111
|
+
*/
|
|
112
|
+
verifyUploadedObject(options: {
|
|
113
|
+
objectKey: string;
|
|
114
|
+
fileFacts: Pick<LambderUploadFileFacts, "byteSize" | "sha256Base64">;
|
|
115
|
+
}): Promise<LambderUploadVerdict>;
|
|
116
|
+
/**
|
|
117
|
+
* A link the browser can read the object with (a preview, a download)
|
|
118
|
+
* until it expires: `lifetimeSeconds`, or the bucket's link lifetime.
|
|
119
|
+
* `contentDisposition` decides for this link whether the browser shows
|
|
120
|
+
* the file or saves it, and under what name.
|
|
121
|
+
*/
|
|
122
|
+
issueDownloadUrl(options: {
|
|
123
|
+
objectKey: string;
|
|
124
|
+
lifetimeSeconds?: number;
|
|
125
|
+
contentDisposition?: LambderUploadContentDisposition;
|
|
126
|
+
}): Promise<string>;
|
|
127
|
+
/** The object's bytes, for work the server does on a file itself. Throws when the key holds nothing. */
|
|
128
|
+
readObject(objectKey: string): Promise<Uint8Array>;
|
|
129
|
+
/**
|
|
130
|
+
* Stores bytes the server produced, with their SHA-256 checked by storage
|
|
131
|
+
* on the way in, as a ticket checks a browser's upload. Pass the digest
|
|
132
|
+
* when it is already known; otherwise it is computed. `object` is what
|
|
133
|
+
* the object carries besides, as for a ticket.
|
|
134
|
+
*/
|
|
135
|
+
writeObject(options: {
|
|
136
|
+
objectKey: string;
|
|
137
|
+
body: Uint8Array;
|
|
138
|
+
mimeType: string;
|
|
139
|
+
sha256Base64?: string;
|
|
140
|
+
object?: LambderUploadObjectOptions;
|
|
141
|
+
}): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* A second object with the same bytes, made inside storage: nothing is
|
|
144
|
+
* read into the function, so a file of any size copies in one call. For
|
|
145
|
+
* records that must each own their file, so deleting one never takes the
|
|
146
|
+
* other's. The copy carries the source's type, metadata and tags.
|
|
147
|
+
*/
|
|
148
|
+
copyObject(options: {
|
|
149
|
+
fromObjectKey: string;
|
|
150
|
+
toObjectKey: string;
|
|
151
|
+
}): Promise<void>;
|
|
152
|
+
/** Removes the object. Deleting a key that holds nothing is not an error. */
|
|
153
|
+
deleteObject(objectKey: string): Promise<void>;
|
|
154
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Direct uploads: a file that travels from the browser straight to object
|
|
3
|
+
* storage, never through the app's function.
|
|
4
|
+
*
|
|
5
|
+
* An API payload tops out near a few megabytes once a file is base64, and a
|
|
6
|
+
* Lambda's request body at six, so anything larger (a scanned lease, a
|
|
7
|
+
* signed PDF, a video) is posted by the browser to the bucket itself, with a
|
|
8
|
+
* ticket the server signed beforehand. The ticket pins everything about the
|
|
9
|
+
* upload: the key, the exact byte size, the content type and the SHA-256 of
|
|
10
|
+
* the bytes, all enforced by the storage, so the browser can only ever store
|
|
11
|
+
* the one file it described.
|
|
12
|
+
*
|
|
13
|
+
* The conversation is the same three steps whatever the app stores: the
|
|
14
|
+
* browser describes the file (LambderUploadFileFacts), the app's endpoint
|
|
15
|
+
* answers with a ticket (LambderUploadTicket), and after the post the app's
|
|
16
|
+
* confirm endpoint asks the bucket what arrived before its record counts as
|
|
17
|
+
* uploaded. The server half is a LambderUploadBucket, the browser half is
|
|
18
|
+
* LambderUploadRunner.
|
|
19
|
+
*
|
|
20
|
+
* This module is the vocabulary both halves share and the contract a bucket
|
|
21
|
+
* implements, and it imports nothing: the zod schemas an endpoint declares
|
|
22
|
+
* its input and output with are in wire/LambderUploadSchemas.ts.
|
|
23
|
+
*/
|
|
24
|
+
/** The longest a ticket or a download link may live: S3's limit for a signature, seven days. */
|
|
25
|
+
export const UPLOAD_SIGNATURE_MAX_SECONDS = 7 * 24 * 60 * 60;
|
|
26
|
+
/** Throws unless a lifetime is a positive number of seconds within S3's seven days. */
|
|
27
|
+
export const assertSignatureLifetime = (seconds, name) => {
|
|
28
|
+
if (!(Number.isFinite(seconds) && seconds > 0 && seconds <= UPLOAD_SIGNATURE_MAX_SECONDS)) {
|
|
29
|
+
throw new RangeError(`${name} must be a number of seconds above 0 and at most ${UPLOAD_SIGNATURE_MAX_SECONDS} (seven days): ${seconds}`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/** Throws when object options break a limit S3 holds them to, so the mistake shows where the app wrote it rather than as a refused post. */
|
|
33
|
+
export const assertObjectOptions = (options) => {
|
|
34
|
+
const tags = Object.entries(options?.tags ?? {});
|
|
35
|
+
if (tags.length > 10)
|
|
36
|
+
throw new RangeError(`An object carries at most 10 tags: ${tags.length}`);
|
|
37
|
+
for (const [key, value] of tags) {
|
|
38
|
+
if (!key || key.length > 128 || value.length > 256)
|
|
39
|
+
throw new RangeError(`A tag's key is 1 to 128 characters and its value at most 256: ${key}`);
|
|
40
|
+
}
|
|
41
|
+
let metadataBytes = 0;
|
|
42
|
+
for (const [name, value] of Object.entries(options?.metadata ?? {})) {
|
|
43
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name))
|
|
44
|
+
throw new RangeError(`A metadata name is letters, digits, "-" and "_": ${name}`);
|
|
45
|
+
if (!/^[\x20-\x7e]*$/.test(value))
|
|
46
|
+
throw new RangeError(`A metadata value is printable ASCII: ${name}`);
|
|
47
|
+
metadataBytes += name.length + value.length;
|
|
48
|
+
}
|
|
49
|
+
if (metadataBytes > 2048)
|
|
50
|
+
throw new RangeError(`An object's metadata is at most 2 KB: ${metadataBytes} bytes`);
|
|
51
|
+
};
|
|
52
|
+
/** A rule's verdict on a file, or null when it may be uploaded: the check the browser makes before hashing and the bucket makes before signing. */
|
|
53
|
+
export const checkUploadRule = (rule, file) => {
|
|
54
|
+
if (file.byteSize <= 0)
|
|
55
|
+
return "fileEmpty";
|
|
56
|
+
if (!rule.mimeTypes.includes(file.mimeType))
|
|
57
|
+
return "fileTypeRejected";
|
|
58
|
+
if (file.byteSize > rule.maxBytes)
|
|
59
|
+
return "fileTooLarge";
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Throws when an object key would not pin the key a ticket writes to: S3
|
|
64
|
+
* substitutes the uploaded file's own name for `${filename}` in a presigned
|
|
65
|
+
* POST's key, so a key holding it lets the browser choose where under it the
|
|
66
|
+
* file lands. A key is the app's, built from its own ids, so this is a
|
|
67
|
+
* programming error rather than a refusal.
|
|
68
|
+
*/
|
|
69
|
+
export const assertPinnedObjectKey = (objectKey) => {
|
|
70
|
+
if (!objectKey)
|
|
71
|
+
throw new Error("An upload's object key is empty");
|
|
72
|
+
if (objectKey.includes("${filename}"))
|
|
73
|
+
throw new Error(`An upload's object key may not hold \${filename}, which storage replaces with the uploaded file's name: ${objectKey}`);
|
|
74
|
+
};
|
|
@@ -16,9 +16,9 @@ export type LambderApiTransportRequest = {
|
|
|
16
16
|
token: string;
|
|
17
17
|
/**
|
|
18
18
|
* The cookie name the caller reads that token from. A transport that
|
|
19
|
-
* fills the token in itself (the cookie-jar decorator,
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* fills the token in itself (the cookie-jar decorator, with no document
|
|
20
|
+
* to read) needs the same name; taking it from the caller keeps the two
|
|
21
|
+
* from being configured apart.
|
|
22
22
|
*/
|
|
23
23
|
csrfCookieKey?: string;
|
|
24
24
|
siteHost: string;
|
|
@@ -36,10 +36,9 @@ export type LambderApiTransportRequest = {
|
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
38
|
* Why a transport could not deliver a call. `network` is the default reading
|
|
39
|
-
* of a rejection: nothing came back. `protocol`
|
|
40
|
-
* callee
|
|
41
|
-
*
|
|
42
|
-
* wiring fault and should not be reported to a developer as flaky
|
|
39
|
+
* of a rejection: nothing came back. `protocol` means the call reached the
|
|
40
|
+
* callee but produced no answer (what came back was not one, or the callee
|
|
41
|
+
* threw): a server or wiring fault, not to be reported as flaky
|
|
43
42
|
* connectivity. `timeout` belongs to the caller, which knows whether its own
|
|
44
43
|
* abort fired.
|
|
45
44
|
*/
|
|
@@ -60,27 +59,28 @@ export declare class LambderTransportFailure extends Error {
|
|
|
60
59
|
export declare const isLambderTransportFailure: (err: unknown) => err is LambderTransportFailure;
|
|
61
60
|
/**
|
|
62
61
|
* Delivers one call and hands back the answer in the accessor form
|
|
63
|
-
* resolveApiOutcome() reads. What a transport owes its caller
|
|
64
|
-
* but this contract stands between a call and a wrong outcome:
|
|
62
|
+
* resolveApiOutcome() reads. What a transport owes its caller:
|
|
65
63
|
*
|
|
66
64
|
* - **Any HTTP status is an answer.** A 4xx or 5xx resolves, status and body
|
|
67
65
|
* included, because resolveApiOutcome() is the one place that reads what a
|
|
68
|
-
* status means.
|
|
69
|
-
*
|
|
66
|
+
* status means. Rejecting on a status would throw away the envelope a
|
|
67
|
+
* refusal, a validation failure or a crash arrived in.
|
|
70
68
|
* - **A rejection is a transport failure.** The caller reports it as
|
|
71
|
-
* `network`
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* `cause`, where the caller's `outcome.error` carries it.
|
|
69
|
+
* `network`, as `timeout` when its own abort fired, or as the reason a
|
|
70
|
+
* thrown LambderTransportFailure names. That failure's `cause` carries the
|
|
71
|
+
* real error through to the caller's `outcome.error`.
|
|
75
72
|
* - **`request.signal` must be honoured**, by rejecting as soon as it aborts.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* obligation is to stop waiting, not to stop the callee.
|
|
73
|
+
* Without that, the caller's `timeoutMs` and per-call `signal` mean
|
|
74
|
+
* nothing and a call waits as long as the callee takes. Work already begun
|
|
75
|
+
* need not be cancellable (an in-process handler is not); the obligation
|
|
76
|
+
* is to stop waiting, not to stop the callee.
|
|
81
77
|
* - **Timeouts and retries belong to the caller.** A transport starts no
|
|
82
78
|
* clock of its own and retries nothing, so one call is one delivery
|
|
83
79
|
* attempt and an idempotency key means what it says.
|
|
80
|
+
* - **A transport that keeps the session's cookies itself reports its CSRF
|
|
81
|
+
* tokens** on the answer (`csrfTokens`), the one it posted and a read of the
|
|
82
|
+
* one it holds, since the caller otherwise judges a sessionExpired by
|
|
83
|
+
* document.cookie, which such a transport never writes.
|
|
84
84
|
*
|
|
85
85
|
* Four transports ship: fetch (lambderFetchTransport, the browser default),
|
|
86
86
|
* an in-process Lambder handler (lambderHandlerTransport, for tests), the
|
|
@@ -89,16 +89,16 @@ export declare const isLambderTransportFailure: (err: unknown) => err is Lambder
|
|
|
89
89
|
*/
|
|
90
90
|
export type LambderApiTransport = (request: LambderApiTransportRequest) => Promise<LambderApiHttpAnswer>;
|
|
91
91
|
/**
|
|
92
|
-
* The fields of the request envelope, in
|
|
93
|
-
*
|
|
92
|
+
* The fields of the request envelope, in wire order: the one statement of
|
|
93
|
+
* what a call sends, for every sender.
|
|
94
94
|
*
|
|
95
95
|
* Two senders write it. A transport hands the payload over as a value
|
|
96
96
|
* (buildTransportEnvelope, below); LambderInvokeCaller has already serialized
|
|
97
|
-
* its payload to decide
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
97
|
+
* its payload to decide on compression, and splices that JSON onto the end
|
|
98
|
+
* rather than parsing and stringifying it again (buildEnvelopeJson, in
|
|
99
|
+
* invoke/LambderLambdaEvent.ts). The splice builds on this function so a new
|
|
100
|
+
* envelope field cannot reach one sender and miss the other, which nothing on
|
|
101
|
+
* the wire would catch.
|
|
102
102
|
*/
|
|
103
103
|
export declare const buildEnvelopeFields: (fields: {
|
|
104
104
|
apiName: string;
|
|
@@ -15,16 +15,16 @@ export class LambderTransportFailure extends Error {
|
|
|
15
15
|
/** Brand-based type guard, so a duplicate install of the package still matches. */
|
|
16
16
|
export const isLambderTransportFailure = (err) => err instanceof Error && err.isLambderTransportFailure === true;
|
|
17
17
|
/**
|
|
18
|
-
* The fields of the request envelope, in
|
|
19
|
-
*
|
|
18
|
+
* The fields of the request envelope, in wire order: the one statement of
|
|
19
|
+
* what a call sends, for every sender.
|
|
20
20
|
*
|
|
21
21
|
* Two senders write it. A transport hands the payload over as a value
|
|
22
22
|
* (buildTransportEnvelope, below); LambderInvokeCaller has already serialized
|
|
23
|
-
* its payload to decide
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* its payload to decide on compression, and splices that JSON onto the end
|
|
24
|
+
* rather than parsing and stringifying it again (buildEnvelopeJson, in
|
|
25
|
+
* invoke/LambderLambdaEvent.ts). The splice builds on this function so a new
|
|
26
|
+
* envelope field cannot reach one sender and miss the other, which nothing on
|
|
27
|
+
* the wire would catch.
|
|
28
28
|
*/
|
|
29
29
|
export const buildEnvelopeFields = (fields) => ({
|
|
30
30
|
apiName: fields.apiName,
|
|
@@ -36,28 +36,23 @@ export declare const parseSetCookie: (header: string, now: number, requestPath?:
|
|
|
36
36
|
* in-process handler transport in a Node test, and the mock runtime's direct
|
|
37
37
|
* transport. It stores what an answer's Set-Cookie headers set, honours their
|
|
38
38
|
* expiry and deletion, and hands back the Cookie pairs the next request should
|
|
39
|
-
* carry. One jar is one browser
|
|
39
|
+
* carry. One jar is one browser.
|
|
40
40
|
*
|
|
41
|
-
* The rules
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* hand-rolled jar either trusts `Domain=co.uk` or bans every two-label domain.
|
|
41
|
+
* The rules (domain and path matching, default-path, Max-Age against Expires,
|
|
42
|
+
* Secure, HttpOnly, the __Host-/__Secure- prefixes) are tough-cookie's, the
|
|
43
|
+
* reference RFC 6265 implementation, imported for its public suffix list:
|
|
44
|
+
* counting labels can tell `Domain=com` is a registry suffix but not `co.uk`,
|
|
45
|
+
* so a hand-rolled jar either trusts `Domain=co.uk` or bans every two-label
|
|
46
|
+
* domain.
|
|
48
47
|
*
|
|
49
|
-
* What stays Lambder's is the shape of
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* anything: a jar pointed at a single host is the ordinary case, and refusing
|
|
55
|
-
* to answer it until it can name that host would make the common setup the
|
|
56
|
-
* awkward one.
|
|
48
|
+
* What stays Lambder's is the shape of a transport's questions: Set-Cookie
|
|
49
|
+
* header lists in (storeSetCookies), `name=value` pairs out (cookiePairs),
|
|
50
|
+
* and a target given as host and path, since a transport that never speaks
|
|
51
|
+
* HTTP has no URL. An omitted field matches anything, because a jar pointed
|
|
52
|
+
* at a single host is the ordinary case and should not have to name it.
|
|
57
53
|
*
|
|
58
|
-
* SameSite is stored but never consulted
|
|
59
|
-
* initiate this", and
|
|
60
|
-
* is same-site by construction.
|
|
54
|
+
* SameSite is stored but never consulted: it answers "did another site
|
|
55
|
+
* initiate this", and every transport call is same-site by construction.
|
|
61
56
|
*/
|
|
62
57
|
export declare class LambderCookieJar {
|
|
63
58
|
private readonly jar;
|
|
@@ -76,25 +71,24 @@ export declare class LambderCookieJar {
|
|
|
76
71
|
* came from. Its `host` is the sending host, which every Domain is checked
|
|
77
72
|
* against, and its `path` is the default Path of a cookie that names none.
|
|
78
73
|
*
|
|
79
|
-
* A Domain the sender is not under
|
|
80
|
-
* (RFC 6265 section 5.3 step 6), and so does a Domain
|
|
81
|
-
*
|
|
82
|
-
*
|
|
74
|
+
* A Domain the sender is not under voids the cookie rather than narrowing
|
|
75
|
+
* it (RFC 6265 section 5.3 step 6), and so does a public-suffix Domain.
|
|
76
|
+
* Otherwise evil.example.com could plant a cookie that bank.example.com
|
|
77
|
+
* is handed on the next call.
|
|
83
78
|
*/
|
|
84
79
|
storeSetCookies(headers: readonly string[], request?: LambderCookieTarget): void;
|
|
85
80
|
/** Every live cookie. */
|
|
86
81
|
list(): LambderStoredCookie[];
|
|
87
82
|
/**
|
|
88
83
|
* The Cookie header pairs the next request carries, as `name=value`, in
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
84
|
+
* RFC 6265 section 5.4 order: longest Path first, and among equal paths
|
|
85
|
+
* the one set first. Servers that read only the first value of a repeated
|
|
86
|
+
* name depend on that order, as does any test about which of two
|
|
87
|
+
* same-named cookies wins.
|
|
93
88
|
*
|
|
94
|
-
* Only
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* of the one host its jar talks to.
|
|
89
|
+
* Only cookies whose scope covers the target travel. An omitted target
|
|
90
|
+
* field matches anything, so a caller that cannot name its host still
|
|
91
|
+
* gets the cookies of the one host its jar talks to.
|
|
98
92
|
*/
|
|
99
93
|
cookiePairs(target?: LambderCookieTarget): string[];
|
|
100
94
|
/**
|
|
@@ -107,10 +101,9 @@ export declare class LambderCookieJar {
|
|
|
107
101
|
} & LambderCookieTarget): string | undefined;
|
|
108
102
|
/**
|
|
109
103
|
* The live cookies whose scope reaches this target, in RFC 6265 send
|
|
110
|
-
* order. Delegated to tough-cookie whenever
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* need one and ordered by the same rule.
|
|
104
|
+
* order. Delegated to tough-cookie whenever a host is known, the case
|
|
105
|
+
* worth getting exactly right; with no host, every cookie the jar holds
|
|
106
|
+
* is filtered by the rules that need none and ordered the same way.
|
|
114
107
|
*/
|
|
115
108
|
private matchingCookies;
|
|
116
109
|
/** Number of live cookies. */
|