lambder 9.0.2 → 10.0.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 +195 -0
- package/README.md +29 -14
- package/dist/api/LambderApiDefinition.d.ts +9 -0
- package/dist/api/LambderApiEnvelope.d.ts +22 -10
- package/dist/api/LambderApiEnvelope.js +57 -22
- package/dist/api/LambderApiGuards.d.ts +77 -22
- package/dist/api/LambderApiGuards.js +13 -1
- package/dist/api/LambderApiIdempotency.js +4 -4
- package/dist/api/LambderApiOutputValidationError.d.ts +10 -4
- package/dist/api/LambderApiOutputValidationError.js +14 -6
- package/dist/api/LambderApiPipeline.d.ts +2 -1
- package/dist/api/LambderApiPipeline.js +10 -2
- package/dist/api/LambderApiRateLimits.d.ts +15 -11
- package/dist/api/LambderApiRateLimits.js +31 -7
- package/dist/api/LambderApiRefusals.d.ts +204 -0
- package/dist/api/LambderApiRefusals.js +223 -0
- package/dist/api/LambderApiSignature.d.ts +13 -4
- package/dist/api/LambderApiSignature.js +21 -4
- package/dist/build/writeApiOptions.js +7 -5
- package/dist/client/LambderCaller.d.ts +28 -17
- package/dist/client/LambderCaller.js +19 -15
- package/dist/client/LambderUploadRunner.d.ts +5 -5
- package/dist/client/LambderUploadRunner.js +25 -16
- package/dist/client.d.ts +6 -5
- package/dist/client.js +2 -1
- package/dist/core/Lambder.d.ts +121 -23
- package/dist/core/Lambder.js +105 -51
- package/dist/core/LambderContext.d.ts +1 -1
- package/dist/core/LambderContext.js +5 -0
- package/dist/core/LambderCreateOptions.d.ts +58 -15
- package/dist/core/LambderCreateOptions.js +10 -0
- package/dist/core/LambderPolicyBuilders.d.ts +11 -8
- package/dist/core/LambderPolicyBuilders.js +10 -8
- package/dist/core/LambderResolver.d.ts +1 -1
- package/dist/core/LambderResolver.js +1 -1
- package/dist/core/LambderResponseBuilder.d.ts +11 -9
- package/dist/core/LambderResponseBuilder.js +12 -11
- package/dist/index.d.ts +13 -9
- package/dist/index.js +6 -2
- package/dist/invoke/LambderInvokeCaller.d.ts +5 -6
- package/dist/invoke/LambderInvokeCaller.js +13 -12
- package/dist/invoke/LambderInvokeOutcome.d.ts +32 -23
- package/dist/invoke/LambderInvokeOutcome.js +4 -4
- package/dist/mock/LambderMockApp.d.ts +86 -24
- package/dist/mock/LambderMockApp.js +108 -16
- package/dist/mock/LambderMockCallRecorder.js +6 -3
- package/dist/mock/LambderMockCreateOptions.d.ts +4 -5
- package/dist/mock/LambderMockFailureInjector.d.ts +9 -2
- package/dist/mock/LambderMockFailureInjector.js +18 -4
- package/dist/mock/LambderMockTypes.d.ts +34 -11
- package/dist/mock.d.ts +4 -3
- package/dist/mock.js +4 -3
- package/dist/shared/contracts/LambderUploadBucket.d.ts +18 -3
- package/dist/shared/contracts/LambderUploadBucket.js +2 -2
- package/dist/shared/util/LambderTypeUtilities.d.ts +11 -0
- package/dist/shared/wire/LambderApiContract.d.ts +96 -31
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +19 -12
- package/dist/shared/wire/LambderApiOptionValues.d.ts +15 -6
- package/dist/shared/wire/LambderApiOptionValues.js +3 -3
- package/dist/shared/wire/LambderApiOutcome.d.ts +49 -27
- package/dist/shared/wire/LambderApiOutcome.js +28 -11
- package/dist/shared/wire/LambderApiRefusal.d.ts +127 -42
- package/dist/shared/wire/LambderApiRefusal.js +15 -7
- package/dist/shared/wire/LambderCrashDetail.d.ts +1 -1
- package/dist/shared/wire/LambderCrashDetail.js +1 -1
- package/dist/shared/wire/LambderHttpStatus.d.ts +6 -0
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +3 -2
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +5 -5
- package/dist/shared/wire/LambderObjectPayload.d.ts +15 -0
- package/dist/shared/wire/LambderObjectPayload.js +24 -0
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +52 -7
- package/dist/shared/wire/LambderOutcomeAssertions.js +34 -8
- package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -1
- package/dist/shared/wire/LambderUploadObjectFields.js +22 -1
- package/dist/shared/wire/LambderUploadSchemas.d.ts +8 -2
- package/dist/shared/wire/LambderUploadSchemas.js +18 -7
- package/dist/stores/LambderMemoryUploadBucket.d.ts +21 -7
- package/dist/stores/LambderMemoryUploadBucket.js +60 -12
- package/dist/stores/LambderS3UploadBucket.d.ts +22 -3
- package/dist/stores/LambderS3UploadBucket.js +51 -4
- package/dist/testing/LambderTestApp.d.ts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { LambderHttpStatusCode } from "./LambderHttpStatus.js";
|
|
2
2
|
export type LambderApiRefusalOptions = {
|
|
3
3
|
/**
|
|
4
|
-
* User-facing failure detail placed on the API envelope's `
|
|
4
|
+
* User-facing failure detail placed on the API envelope's `refusal`
|
|
5
5
|
* field: a refusal message (`{ type: "warning", content: "..." }`, with
|
|
6
6
|
* an app's own `code`) or a plain string, which becomes an "error"
|
|
7
7
|
* message with that content. Defaults to the error message, so a bare
|
|
8
8
|
* `throw new LambderApiRefusal("...")` is still visible to the client.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
refusal?: LambderUncheckedRefusalMessage | string;
|
|
11
11
|
/** Sets the envelope's `notAuthorized` flag (routed to the caller's notAuthorizedHandler). */
|
|
12
12
|
notAuthorized?: boolean;
|
|
13
13
|
/** Sets the envelope's `sessionExpired` flag (the caller clears session cookies and calls sessionExpiredHandler). */
|
|
@@ -27,7 +27,7 @@ export type LambderApiRefusalOptions = {
|
|
|
27
27
|
* A typed refusal: "this request is denied/invalid" as opposed to "the server
|
|
28
28
|
* crashed". Throw it from anywhere in an API call's call stack (handlers,
|
|
29
29
|
* hooks, guards or nested helpers) and the render pipeline maps it onto the
|
|
30
|
-
* structured API envelope (its
|
|
30
|
+
* structured API envelope (its refusal, notAuthorized and
|
|
31
31
|
* sessionExpired) instead of routing it through setGlobalErrorHandler, so refusals never reach crash
|
|
32
32
|
* logging and clients receive a parseable response.
|
|
33
33
|
*
|
|
@@ -45,8 +45,8 @@ export declare class LambderApiRefusal extends Error {
|
|
|
45
45
|
* across them while this marker does not. The pipeline checks the brand.
|
|
46
46
|
*/
|
|
47
47
|
readonly isLambderApiRefusal = true;
|
|
48
|
-
/** What the envelope's
|
|
49
|
-
readonly
|
|
48
|
+
/** What the envelope's refusal carries: always a message object, a plain string given to the options made into one. */
|
|
49
|
+
readonly refusal: LambderUncheckedRefusalMessage;
|
|
50
50
|
readonly notAuthorized?: boolean;
|
|
51
51
|
readonly sessionExpired?: boolean;
|
|
52
52
|
readonly statusCode?: LambderHttpStatusCode;
|
|
@@ -55,44 +55,70 @@ export declare class LambderApiRefusal extends Error {
|
|
|
55
55
|
}
|
|
56
56
|
/** Brand-based type guard (see LambderApiRefusal.isLambderApiRefusal). */
|
|
57
57
|
export declare const isLambderApiRefusal: (err: unknown) => err is LambderApiRefusal;
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* translate on it and never string-match `content`, which stays the
|
|
62
|
-
* human-readable fallback for codes a client does not know yet. The
|
|
63
|
-
* framework's own refusals carry a LambderRefusalCode; apps put their own
|
|
64
|
-
* typed vocabulary in `code`. The caller's errorMessageHandler receives the
|
|
65
|
-
* object as-is, typed as LambderAppRefusalMessage; a server that wrote a
|
|
66
|
-
* plain string reaches it as `{ type: "error", content }` (refusalMessageOf).
|
|
67
|
-
*/
|
|
68
|
-
export type LambderRefusalMessage<TAppCode extends string = never> = {
|
|
58
|
+
/** What every refusal message carries, whatever its code. */
|
|
59
|
+
type LambderRefusalMessageFields = {
|
|
60
|
+
/** Rendering intent for the client: how loud to be about it. */
|
|
69
61
|
type: "warning" | "error" | "info";
|
|
62
|
+
/** Optional heading shown above the content. */
|
|
63
|
+
title?: string;
|
|
70
64
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* Parameterized rather than widened with `string & {}`: a union with
|
|
75
|
-
* `string` in it does not narrow, so a `switch(message.code)` could not
|
|
76
|
-
* end in a `default: never` exhaustiveness check, which is what the codes
|
|
77
|
-
* exist for. A client that reads its own vocabulary declares it
|
|
78
|
-
* (`LambderRefusalMessage<"app/not-verified" | ...>`) and gets a checked
|
|
79
|
-
* switch; a value an app WRITES takes LambderAppRefusalMessage, where any
|
|
80
|
-
* code is welcome.
|
|
65
|
+
* The human-readable text: what a client shows when it has nothing better.
|
|
66
|
+
* A client that translates reads the code and the data instead, and falls
|
|
67
|
+
* back to this for a code it does not know.
|
|
81
68
|
*/
|
|
82
|
-
code?: LambderRefusalCode | TAppCode;
|
|
83
|
-
title?: string;
|
|
84
69
|
content: string;
|
|
85
70
|
};
|
|
86
71
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* LambderRefusalMessage itself defaults to the framework's codes alone, so a
|
|
91
|
-
* reader's switch over it is exhaustive.
|
|
72
|
+
* A refusal no app declared: one the framework wrote itself (a
|
|
73
|
+
* LambderRefusalCode), or an app's `refuse("...")` without a code. It never
|
|
74
|
+
* carries data.
|
|
92
75
|
*/
|
|
93
|
-
export type
|
|
76
|
+
export type LambderPlainRefusalMessage = LambderRefusalMessageFields & {
|
|
77
|
+
code?: LambderRefusalCode;
|
|
78
|
+
data?: undefined;
|
|
79
|
+
};
|
|
94
80
|
/**
|
|
95
|
-
*
|
|
81
|
+
* One declared code's message: the code, and its data when the code declares
|
|
82
|
+
* data. TDeclaration is the code's value in a contract entry's `refusals`:
|
|
83
|
+
* `{ data: D }` or `{}`.
|
|
84
|
+
*/
|
|
85
|
+
type LambderDeclaredRefusalMessage<TCode extends string, TDeclaration> = LambderRefusalMessageFields & {
|
|
86
|
+
code: TCode;
|
|
87
|
+
} & (TDeclaration extends {
|
|
88
|
+
data: infer TData;
|
|
89
|
+
} ? {
|
|
90
|
+
data: TData;
|
|
91
|
+
} : {
|
|
92
|
+
data?: undefined;
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* The refusal message a reader receives, discriminated by `code`.
|
|
96
|
+
*
|
|
97
|
+
* TRefusals maps each code the endpoint declares to its contract value
|
|
98
|
+
* (`{ data: D }` or `{}`; see LambderContractRefusalsOf). Every declared code
|
|
99
|
+
* is one arm, carrying its own data type, and the framework's codes and the
|
|
100
|
+
* uncoded refusal are one more arm with no data. A `switch (message.code)`
|
|
101
|
+
* therefore narrows `data` in each case, and a `default: never` holds: the
|
|
102
|
+
* server refuses to send a code the endpoint did not declare (see
|
|
103
|
+
* LambderApiRefusalValidationError), so no other code can arrive.
|
|
104
|
+
*
|
|
105
|
+
* Left without an argument it is the framework's own vocabulary alone.
|
|
106
|
+
*/
|
|
107
|
+
export type LambderRefusalMessage<TRefusals = {}> = 0 extends 1 & TRefusals ? LambderUncheckedRefusalMessage : LambderPlainRefusalMessage | {
|
|
108
|
+
[TCode in keyof TRefusals & string]: LambderDeclaredRefusalMessage<TCode, TRefusals[TCode]>;
|
|
109
|
+
}[keyof TRefusals & string];
|
|
110
|
+
/**
|
|
111
|
+
* The refusal shape an app writes, and the widest a reader can be handed:
|
|
112
|
+
* any code (the framework's still autocomplete) and any data. What
|
|
113
|
+
* LambderApiRefusal carries and refusalMessageOf reads; a reader that knows
|
|
114
|
+
* the endpoint reads the narrower LambderRefusalMessage instead.
|
|
115
|
+
*/
|
|
116
|
+
export type LambderUncheckedRefusalMessage = LambderRefusalMessageFields & {
|
|
117
|
+
code?: LambderRefusalCode | (string & {});
|
|
118
|
+
data?: unknown;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* An refusal as the one shape a reader handles: a plain string becomes
|
|
96
122
|
* an "error" message with that content, and a value that is not a message
|
|
97
123
|
* at all becomes one describing it. The envelope writer applies it, so a
|
|
98
124
|
* Lambder server only ever sends the object. A reader applies it too,
|
|
@@ -100,7 +126,7 @@ export type LambderAppRefusalMessage = LambderRefusalMessage<string & {}>;
|
|
|
100
126
|
* mock answer (an MSW handler, a test double) or a proxy that wrote its own
|
|
101
127
|
* body can put anything there.
|
|
102
128
|
*/
|
|
103
|
-
export declare const refusalMessageOf: (message: unknown) =>
|
|
129
|
+
export declare const refusalMessageOf: (message: unknown) => LambderUncheckedRefusalMessage;
|
|
104
130
|
/**
|
|
105
131
|
* Codes the framework stamps on the refusals it authors itself, under the
|
|
106
132
|
* reserved `lambder/` prefix so app codes never collide. Compare against
|
|
@@ -108,7 +134,7 @@ export declare const refusalMessageOf: (message: unknown) => LambderAppRefusalMe
|
|
|
108
134
|
* than retyping the strings.
|
|
109
135
|
*/
|
|
110
136
|
export declare const LAMBDER_REFUSAL_CODES: {
|
|
111
|
-
/** A rate-limit policy refused (429). A policy's own
|
|
137
|
+
/** A rate-limit policy refused (429). A policy's own message carries it. */
|
|
112
138
|
readonly rateLimited: "lambder/rate-limited";
|
|
113
139
|
/** The original of an idempotent request is still processing (409). */
|
|
114
140
|
readonly duplicateInFlight: "lambder/duplicate-in-flight";
|
|
@@ -130,18 +156,31 @@ export declare const LAMBDER_REFUSAL_CODES: {
|
|
|
130
156
|
readonly uploadTooLarge: "lambder/upload-too-large";
|
|
131
157
|
};
|
|
132
158
|
export type LambderRefusalCode = (typeof LAMBDER_REFUSAL_CODES)[keyof typeof LAMBDER_REFUSAL_CODES];
|
|
159
|
+
/** Whether a code is one the framework stamps on its own refusals. */
|
|
160
|
+
export declare const isLambderRefusalCode: (code: unknown) => code is LambderRefusalCode;
|
|
133
161
|
export type LambderRefuseOptions = {
|
|
134
|
-
/** Rendering intent for the client's
|
|
162
|
+
/** Rendering intent for the client's refusalHandler. Default: "warning". */
|
|
135
163
|
type?: LambderRefusalMessage["type"];
|
|
136
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Machine-readable identity of the refusal, for clients to branch and
|
|
166
|
+
* translate on: a code the endpoint declares in its `refusals` option (or
|
|
167
|
+
* through one of its guards). A code it does not declare is a crash, not
|
|
168
|
+
* a refusal; see LambderApiRefusalValidationError.
|
|
169
|
+
*/
|
|
137
170
|
code?: string;
|
|
171
|
+
/**
|
|
172
|
+
* The code's data, when the code declares data: parsed through the
|
|
173
|
+
* declared schema before it is sent, as an output is. A refusal without
|
|
174
|
+
* a declared code carries none.
|
|
175
|
+
*/
|
|
176
|
+
data?: unknown;
|
|
138
177
|
/** Optional heading shown above the content. */
|
|
139
178
|
title?: string;
|
|
140
|
-
/** Sets the envelope's notAuthorized flag (routed to the caller's notAuthorizedHandler). */
|
|
179
|
+
/** Sets the envelope's notAuthorized flag (routed to the caller's notAuthorizedHandler). A declared code's flag is its declaration's, and a refusal with such a code that sets it here is a crash. */
|
|
141
180
|
notAuthorized?: boolean;
|
|
142
|
-
/** Sets the envelope's sessionExpired flag. */
|
|
181
|
+
/** Sets the envelope's sessionExpired flag. Not for a refusal with a declared code, whose flags are its declaration's. */
|
|
143
182
|
sessionExpired?: boolean;
|
|
144
|
-
/** HTTP status of the refusal. Default 200; avoid 5xx (caller treats as crash) and 422 (reserved for validation). */
|
|
183
|
+
/** HTTP status of the refusal. Default 200; avoid 5xx (caller treats as crash) and 422 (reserved for validation). A declared code's status is its declaration's, and a refusal with such a code that sets one here is a crash. */
|
|
145
184
|
statusCode?: LambderHttpStatusCode;
|
|
146
185
|
/** Extra response headers on the refusal (e.g. Retry-After). */
|
|
147
186
|
headers?: Record<string, string>;
|
|
@@ -159,5 +198,51 @@ export type LambderRefuseOptions = {
|
|
|
159
198
|
* The const carries the annotation so TypeScript applies never-return
|
|
160
199
|
* control-flow narrowing at call sites (`if (!row) refuse(...)` implies
|
|
161
200
|
* `row` is defined afterwards).
|
|
201
|
+
*
|
|
202
|
+
* Its `code` and `data` are checked when the refusal is rendered, against the
|
|
203
|
+
* endpoint it answers. Inside an API handler, `ctx.refuse` takes the same
|
|
204
|
+
* arguments typed to that endpoint's declared codes (LambderDeclaredRefuse).
|
|
162
205
|
*/
|
|
163
206
|
export declare const refuse: (content: string, options?: LambderRefuseOptions) => never;
|
|
207
|
+
/** refuse()'s options other than the refusal's identity. */
|
|
208
|
+
type LambderRefuseFlagOptions = Omit<LambderRefuseOptions, "code" | "data">;
|
|
209
|
+
/** What a raise site of a declared code still chooses: the code's declaration owns its status and its flags. */
|
|
210
|
+
type LambderDeclaredCodeOptions = Omit<LambderRefuseFlagOptions, "statusCode" | "notAuthorized" | "sessionExpired">;
|
|
211
|
+
/**
|
|
212
|
+
* refuse()'s options where the endpoint's declarations are known: `code` is
|
|
213
|
+
* one of TRefusals' codes, `data` is required when that code declares data
|
|
214
|
+
* and absent otherwise, and the status and flags are the code's
|
|
215
|
+
* declaration's, so they cannot be set here. TRefusals maps each code to
|
|
216
|
+
* `{ data: D }` or `{}`, D being what the handler hands over (the schema's
|
|
217
|
+
* input form on the server, the wire form in the mock). A discriminated
|
|
218
|
+
* union rather than a generic over the code, so a code widened to a union
|
|
219
|
+
* cannot loosen `data`. With TCodeRequired (declareRefusals's requireCodes)
|
|
220
|
+
* the uncoded arm is gone: every refusal names a code.
|
|
221
|
+
*/
|
|
222
|
+
export type LambderDeclaredRefuseOptions<TRefusals, TCodeRequired extends boolean = false> = (TCodeRequired extends true ? never : LambderRefuseFlagOptions & {
|
|
223
|
+
code?: undefined;
|
|
224
|
+
data?: undefined;
|
|
225
|
+
}) | {
|
|
226
|
+
[TCode in keyof TRefusals & string]: LambderDeclaredCodeOptions & {
|
|
227
|
+
code: TCode;
|
|
228
|
+
} & (TRefusals[TCode] extends {
|
|
229
|
+
data: infer TData;
|
|
230
|
+
} ? {
|
|
231
|
+
data: TData;
|
|
232
|
+
} : {
|
|
233
|
+
data?: undefined;
|
|
234
|
+
});
|
|
235
|
+
}[keyof TRefusals & string];
|
|
236
|
+
/**
|
|
237
|
+
* The refuse an API handler's context carries, typed to the endpoint's
|
|
238
|
+
* declared codes (a guard's to the guard's, the init's to the whole
|
|
239
|
+
* vocabulary). It throws what refuse() throws. Where the app requires codes,
|
|
240
|
+
* the options are not optional: a refusal is a code.
|
|
241
|
+
*
|
|
242
|
+
* TypeScript narrows after a never-returning call only when every name in it
|
|
243
|
+
* carries an explicit annotation, which a handler's contextually typed `ctx`
|
|
244
|
+
* does not. Where the code after it relies on the refusal, write
|
|
245
|
+
* `return ctx.refuse(...)`; the free refuse() narrows on its own.
|
|
246
|
+
*/
|
|
247
|
+
export type LambderDeclaredRefuse<TRefusals, TCodeRequired extends boolean = false> = TCodeRequired extends true ? (content: string, options: LambderDeclaredRefuseOptions<TRefusals, true>) => never : (content: string, options?: LambderDeclaredRefuseOptions<TRefusals, false>) => never;
|
|
248
|
+
export {};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* A typed refusal: "this request is denied/invalid" as opposed to "the server
|
|
3
3
|
* crashed". Throw it from anywhere in an API call's call stack (handlers,
|
|
4
4
|
* hooks, guards or nested helpers) and the render pipeline maps it onto the
|
|
5
|
-
* structured API envelope (its
|
|
5
|
+
* structured API envelope (its refusal, notAuthorized and
|
|
6
6
|
* sessionExpired) instead of routing it through setGlobalErrorHandler, so refusals never reach crash
|
|
7
7
|
* logging and clients receive a parseable response.
|
|
8
8
|
*
|
|
@@ -20,8 +20,8 @@ export class LambderApiRefusal extends Error {
|
|
|
20
20
|
* across them while this marker does not. The pipeline checks the brand.
|
|
21
21
|
*/
|
|
22
22
|
isLambderApiRefusal = true;
|
|
23
|
-
/** What the envelope's
|
|
24
|
-
|
|
23
|
+
/** What the envelope's refusal carries: always a message object, a plain string given to the options made into one. */
|
|
24
|
+
refusal;
|
|
25
25
|
notAuthorized;
|
|
26
26
|
sessionExpired;
|
|
27
27
|
statusCode;
|
|
@@ -29,7 +29,7 @@ export class LambderApiRefusal extends Error {
|
|
|
29
29
|
constructor(message, options = {}) {
|
|
30
30
|
super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
|
|
31
31
|
this.name = "LambderApiRefusal";
|
|
32
|
-
this.
|
|
32
|
+
this.refusal = refusalMessageOf(options.refusal ?? message);
|
|
33
33
|
this.notAuthorized = options.notAuthorized;
|
|
34
34
|
this.sessionExpired = options.sessionExpired;
|
|
35
35
|
this.statusCode = options.statusCode;
|
|
@@ -40,7 +40,7 @@ export class LambderApiRefusal extends Error {
|
|
|
40
40
|
export const isLambderApiRefusal = (err) => err instanceof Error && err.isLambderApiRefusal === true;
|
|
41
41
|
const REFUSAL_MESSAGE_TYPES = ["warning", "error", "info"];
|
|
42
42
|
/**
|
|
43
|
-
* An
|
|
43
|
+
* An refusal as the one shape a reader handles: a plain string becomes
|
|
44
44
|
* an "error" message with that content, and a value that is not a message
|
|
45
45
|
* at all becomes one describing it. The envelope writer applies it, so a
|
|
46
46
|
* Lambder server only ever sends the object. A reader applies it too,
|
|
@@ -71,7 +71,7 @@ export const refusalMessageOf = (message) => {
|
|
|
71
71
|
* than retyping the strings.
|
|
72
72
|
*/
|
|
73
73
|
export const LAMBDER_REFUSAL_CODES = {
|
|
74
|
-
/** A rate-limit policy refused (429). A policy's own
|
|
74
|
+
/** A rate-limit policy refused (429). A policy's own message carries it. */
|
|
75
75
|
rateLimited: "lambder/rate-limited",
|
|
76
76
|
/** The original of an idempotent request is still processing (409). */
|
|
77
77
|
duplicateInFlight: "lambder/duplicate-in-flight",
|
|
@@ -92,6 +92,9 @@ export const LAMBDER_REFUSAL_CODES = {
|
|
|
92
92
|
/** An upload bucket would not sign a ticket for a file larger than the rule accepts. */
|
|
93
93
|
uploadTooLarge: "lambder/upload-too-large",
|
|
94
94
|
};
|
|
95
|
+
const FRAMEWORK_REFUSAL_CODES = new Set(Object.values(LAMBDER_REFUSAL_CODES));
|
|
96
|
+
/** Whether a code is one the framework stamps on its own refusals. */
|
|
97
|
+
export const isLambderRefusalCode = (code) => typeof code === "string" && FRAMEWORK_REFUSAL_CODES.has(code);
|
|
95
98
|
/**
|
|
96
99
|
* Refuse the current API call: a routine business "no" (not found, invalid
|
|
97
100
|
* input, not allowed) with a user-facing message. Throws a LambderApiRefusal
|
|
@@ -103,14 +106,19 @@ export const LAMBDER_REFUSAL_CODES = {
|
|
|
103
106
|
* The const carries the annotation so TypeScript applies never-return
|
|
104
107
|
* control-flow narrowing at call sites (`if (!row) refuse(...)` implies
|
|
105
108
|
* `row` is defined afterwards).
|
|
109
|
+
*
|
|
110
|
+
* Its `code` and `data` are checked when the refusal is rendered, against the
|
|
111
|
+
* endpoint it answers. Inside an API handler, `ctx.refuse` takes the same
|
|
112
|
+
* arguments typed to that endpoint's declared codes (LambderDeclaredRefuse).
|
|
106
113
|
*/
|
|
107
114
|
export const refuse = (content, options = {}) => {
|
|
108
115
|
throw new LambderApiRefusal(content, {
|
|
109
|
-
|
|
116
|
+
refusal: {
|
|
110
117
|
type: options.type ?? "warning",
|
|
111
118
|
...(options.code !== undefined ? { code: options.code } : {}),
|
|
112
119
|
...(options.title !== undefined ? { title: options.title } : {}),
|
|
113
120
|
content,
|
|
121
|
+
...(options.data !== undefined ? { data: options.data } : {}),
|
|
114
122
|
},
|
|
115
123
|
notAuthorized: options.notAuthorized,
|
|
116
124
|
sessionExpired: options.sessionExpired,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* one, a developer holding a debug cookie) can get the error's name, message,
|
|
7
7
|
* stack, cause chain and where it happened, to store in its own error log
|
|
8
8
|
* and find the right CloudWatch stream. The envelope carries it in `crash`
|
|
9
|
-
* beside
|
|
9
|
+
* beside refusal; LambderInvokeCaller rebuilds an Error from it as the
|
|
10
10
|
* `cause` of the error it throws, so a reporter that walks causes sees the
|
|
11
11
|
* callee's stack unaided.
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* one, a developer holding a debug cookie) can get the error's name, message,
|
|
7
7
|
* stack, cause chain and where it happened, to store in its own error log
|
|
8
8
|
* and find the right CloudWatch stream. The envelope carries it in `crash`
|
|
9
|
-
* beside
|
|
9
|
+
* beside refusal; LambderInvokeCaller rebuilds an Error from it as the
|
|
10
10
|
* `cause` of the error it throws, so a reporter that walks causes sees the
|
|
11
11
|
* callee's stack unaided.
|
|
12
12
|
*
|
|
@@ -9,3 +9,9 @@
|
|
|
9
9
|
* to typecheck a status union.
|
|
10
10
|
*/
|
|
11
11
|
export type LambderHttpStatusCode = 100 | 101 | 200 | 201 | 202 | 203 | 204 | 206 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 408 | 409 | 410 | 412 | 413 | 415 | 416 | 418 | 422 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504;
|
|
12
|
+
/**
|
|
13
|
+
* The status a refusal with a declared code may leave with: not a 5xx, which a
|
|
14
|
+
* reader files as a server failure, not 422, which is input validation's, and
|
|
15
|
+
* not a 1xx.
|
|
16
|
+
*/
|
|
17
|
+
export type LambderRefusalStatusCode = Exclude<LambderHttpStatusCode, 100 | 101 | 422 | 500 | 501 | 502 | 503 | 504>;
|
|
@@ -7,7 +7,8 @@ export type LambderIdempotentAttemptOutcome = {
|
|
|
7
7
|
ok: boolean;
|
|
8
8
|
reason?: string;
|
|
9
9
|
status?: number;
|
|
10
|
-
|
|
10
|
+
/** The refusal's message, when the answer carried one: only its code is read. */
|
|
11
|
+
refusal?: {
|
|
11
12
|
code?: string;
|
|
12
13
|
};
|
|
13
14
|
};
|
|
@@ -43,7 +44,7 @@ export declare const createIdempotencyKey: () => string;
|
|
|
43
44
|
* - A success settles it, and so does a key refused as reused for another
|
|
44
45
|
* request, since that key can never carry this one.
|
|
45
46
|
* - A refusal of this request (a rejected input, not authorized, an
|
|
46
|
-
*
|
|
47
|
+
* refusal) settles it, unless another attempt under the same key is
|
|
47
48
|
* still in flight or went unanswered. That attempt may run or have run the
|
|
48
49
|
* operation, and guards, validation and rate limits refuse before the
|
|
49
50
|
* replay record is claimed, so the refusal of a retry or a double-tap says
|
|
@@ -5,7 +5,7 @@ export const IDEMPOTENT_ATTEMPT_NOT_SENT = { ok: false, reason: "notSent" };
|
|
|
5
5
|
* Refusals that answer this request itself, so the same request would get
|
|
6
6
|
* the same answer again and what the person sends next is a new operation.
|
|
7
7
|
*/
|
|
8
|
-
const REFUSAL_REASONS = new Set(["validation", "notAuthorized", "
|
|
8
|
+
const REFUSAL_REASONS = new Set(["validation", "notAuthorized", "refusal"]);
|
|
9
9
|
/** Answers that never reached the operation: the same request may pass later. */
|
|
10
10
|
const UNTRIED_REASONS = new Set(["sessionExpired", "versionExpired", "payloadTooLarge", "notSent"]);
|
|
11
11
|
/**
|
|
@@ -45,7 +45,7 @@ let beginAttemptOf;
|
|
|
45
45
|
* - A success settles it, and so does a key refused as reused for another
|
|
46
46
|
* request, since that key can never carry this one.
|
|
47
47
|
* - A refusal of this request (a rejected input, not authorized, an
|
|
48
|
-
*
|
|
48
|
+
* refusal) settles it, unless another attempt under the same key is
|
|
49
49
|
* still in flight or went unanswered. That attempt may run or have run the
|
|
50
50
|
* operation, and guards, validation and rate limits refuse before the
|
|
51
51
|
* replay record is claimed, so the refusal of a retry or a double-tap says
|
|
@@ -98,13 +98,13 @@ export class LambderIdempotencyKeyScope {
|
|
|
98
98
|
if (key !== this.#key)
|
|
99
99
|
return;
|
|
100
100
|
this.#inFlight -= 1;
|
|
101
|
-
const code = outcome.
|
|
101
|
+
const code = outcome.refusal?.code;
|
|
102
102
|
if (outcome.ok || code === LAMBDER_REFUSAL_CODES.idempotencyKeyReused) {
|
|
103
103
|
this.rotate();
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
|
-
// A rate limit refuses before the claim,
|
|
107
|
-
//
|
|
106
|
+
// A rate limit refuses before the claim, and its status names it
|
|
107
|
+
// as surely as its code.
|
|
108
108
|
if (UNTRIED_REASONS.has(outcome.reason ?? "") || outcome.status === 429 || code === LAMBDER_REFUSAL_CODES.rateLimited)
|
|
109
109
|
return;
|
|
110
110
|
if (REFUSAL_REASONS.has(outcome.reason ?? "") && code !== LAMBDER_REFUSAL_CODES.duplicateInFlight) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a value is what an API answers with or a refusal carries as data:
|
|
3
|
+
* an array, or an object that goes through JSON as an object. A Date or a
|
|
4
|
+
* class with a toJSON of its own is written as whatever toJSON returns (a
|
|
5
|
+
* string, for a Date), so it does not count.
|
|
6
|
+
*
|
|
7
|
+
* The rule is what makes `caller.api()` truthy exactly on success: an
|
|
8
|
+
* answer is never null, false, 0 or "", so a falsy result can only mean the
|
|
9
|
+
* call failed. The server checks a handler's parsed output with it, and a
|
|
10
|
+
* reader checks what it received, so a body no Lambder handler wrote (a
|
|
11
|
+
* hand-built one, a proxy's) cannot pass for a success.
|
|
12
|
+
*/
|
|
13
|
+
export declare const isObjectPayload: (value: unknown) => value is object;
|
|
14
|
+
/** What a value that is not an object payload is, for an error message: its kind, never the value itself. */
|
|
15
|
+
export declare const describePayloadKind: (value: unknown) => string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a value is what an API answers with or a refusal carries as data:
|
|
3
|
+
* an array, or an object that goes through JSON as an object. A Date or a
|
|
4
|
+
* class with a toJSON of its own is written as whatever toJSON returns (a
|
|
5
|
+
* string, for a Date), so it does not count.
|
|
6
|
+
*
|
|
7
|
+
* The rule is what makes `caller.api()` truthy exactly on success: an
|
|
8
|
+
* answer is never null, false, 0 or "", so a falsy result can only mean the
|
|
9
|
+
* call failed. The server checks a handler's parsed output with it, and a
|
|
10
|
+
* reader checks what it received, so a body no Lambder handler wrote (a
|
|
11
|
+
* hand-built one, a proxy's) cannot pass for a success.
|
|
12
|
+
*/
|
|
13
|
+
export const isObjectPayload = (value) => Array.isArray(value)
|
|
14
|
+
|| (value !== null && typeof value === "object" && typeof value.toJSON !== "function");
|
|
15
|
+
/** What a value that is not an object payload is, for an error message: its kind, never the value itself. */
|
|
16
|
+
export const describePayloadKind = (value) => {
|
|
17
|
+
if (value === null)
|
|
18
|
+
return "null";
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
return "nothing";
|
|
21
|
+
if (typeof value === "object")
|
|
22
|
+
return "an object that JSON writes as something else (a Date, or a class with a toJSON of its own)";
|
|
23
|
+
return `a ${typeof value}`;
|
|
24
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Three assertions over a call's outcome, for tests.
|
|
3
3
|
*
|
|
4
4
|
* An outcome is a discriminated union, so a test that expects a refusal must
|
|
5
5
|
* narrow before it can read what the refusal carries: check `ok`, branch on
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* These narrow through an `asserts` signature, so the lines after one read
|
|
12
12
|
* the arm it proved, and they throw a plain Error naming what the outcome
|
|
13
|
-
* was. No test runner is imported: the same
|
|
13
|
+
* was. No test runner is imported: the same functions serve vitest, jest
|
|
14
14
|
* and node:test, from `lambder/testing` over a real server and from
|
|
15
15
|
* `lambder/mock` over a mock one. Pure and dependency-free, like the outcome
|
|
16
16
|
* vocabulary they read.
|
|
@@ -44,10 +44,34 @@ type LambderFailureWithReason<TOutcome, TReason> = TOutcome extends {
|
|
|
44
44
|
} ? [TReason & TArmReason] extends [never] ? never : TOutcome & {
|
|
45
45
|
reason: TReason & TArmReason;
|
|
46
46
|
} : never;
|
|
47
|
+
/** The refusal message a failure of this outcome union carries: the endpoint's, with its declared codes. */
|
|
48
|
+
type LambderRefusalMessageOf<TOutcome> = TOutcome extends {
|
|
49
|
+
ok: false;
|
|
50
|
+
refusal?: infer TMessage;
|
|
51
|
+
} ? TMessage : never;
|
|
52
|
+
/** Every code a failure of this outcome union can be refused with: the endpoint's declared codes and the framework's. */
|
|
53
|
+
type LambderRefusalCodeOf<TOutcome> = LambderRefusalMessageOf<TOutcome> extends infer TMessage ? TMessage extends {
|
|
54
|
+
code?: infer TCode;
|
|
55
|
+
} ? Exclude<TCode, undefined> & string : never : never;
|
|
56
|
+
/**
|
|
57
|
+
* The arm of a refusal message that carries code C: the declared code's own
|
|
58
|
+
* arm, with its data. A framework code has no arm of its own (it shares the
|
|
59
|
+
* uncoded one), and neither does any code of a message typed as any code, so
|
|
60
|
+
* those are the message with the code pinned.
|
|
61
|
+
*/
|
|
62
|
+
type LambderRefusalWithCode<TMessage, TCode> = [
|
|
63
|
+
Extract<TMessage, {
|
|
64
|
+
code: TCode;
|
|
65
|
+
}>
|
|
66
|
+
] extends [never] ? TMessage & {
|
|
67
|
+
code: TCode;
|
|
68
|
+
} : Extract<TMessage, {
|
|
69
|
+
code: TCode;
|
|
70
|
+
}>;
|
|
47
71
|
/** What else a failure is expected to carry, beside its reason. */
|
|
48
|
-
export type LambderExpectedFailure = {
|
|
49
|
-
/** The refusal's machine-readable code (`
|
|
50
|
-
code?:
|
|
72
|
+
export type LambderExpectedFailure<TCode extends string = string> = {
|
|
73
|
+
/** The refusal's machine-readable code (`refusal.code`): one the endpoint declares, or a LAMBDER_REFUSAL_CODES value. */
|
|
74
|
+
code?: TCode;
|
|
51
75
|
/** The HTTP status the answer came with. */
|
|
52
76
|
status?: number;
|
|
53
77
|
};
|
|
@@ -72,8 +96,29 @@ export declare function assertApiSuccess<TOutcome extends LambderOutcomeShape>(o
|
|
|
72
96
|
*
|
|
73
97
|
* ```typescript
|
|
74
98
|
* assertApiFailure(await member.apiOutcome("org.delete", { id }), "notAuthorized");
|
|
75
|
-
* assertApiFailure(await guest.apiOutcome("signup", form), "
|
|
99
|
+
* assertApiFailure(await guest.apiOutcome("signup", form), "refusal", { code: LAMBDER_REFUSAL_CODES.rateLimited, status: 429 });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function assertApiFailure<TOutcome extends LambderOutcomeShape, TReason extends LambderFailureReasonOf<TOutcome> = LambderFailureReasonOf<TOutcome>>(outcome: TOutcome, reason?: TReason, expected?: LambderExpectedFailure<LambderRefusalCodeOf<TOutcome>>): asserts outcome is LambderFailureWithReason<TOutcome, TReason>;
|
|
103
|
+
/**
|
|
104
|
+
* Asserts that a call was refused with the given code, whichever reason it
|
|
105
|
+
* arrived under (a refusal flagged notAuthorized carries its code too), and
|
|
106
|
+
* narrows the outcome's refusal to that code's message, so its `data`
|
|
107
|
+
* reads with the type the code declares.
|
|
108
|
+
*
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const outcome = await visitor.apiOutcome("order.pay", { orderId });
|
|
111
|
+
* assertApiRefusal(outcome, "wallet-short");
|
|
112
|
+
* expect(outcome.refusal.data.available).toBe(1250);
|
|
76
113
|
* ```
|
|
114
|
+
*
|
|
115
|
+
* The code is checked against the outcome's own codes, so one the endpoint
|
|
116
|
+
* does not declare is a compile error rather than an assertion that can
|
|
117
|
+
* never pass.
|
|
77
118
|
*/
|
|
78
|
-
export declare function
|
|
119
|
+
export declare function assertApiRefusal<TOutcome extends LambderOutcomeShape, const TCode extends LambderRefusalCodeOf<TOutcome>>(outcome: TOutcome, code: TCode): asserts outcome is Extract<TOutcome, {
|
|
120
|
+
ok: false;
|
|
121
|
+
}> & {
|
|
122
|
+
refusal: LambderRefusalWithCode<LambderRefusalMessageOf<TOutcome>, TCode>;
|
|
123
|
+
};
|
|
79
124
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Three assertions over a call's outcome, for tests.
|
|
3
3
|
*
|
|
4
4
|
* An outcome is a discriminated union, so a test that expects a refusal must
|
|
5
5
|
* narrow before it can read what the refusal carries: check `ok`, branch on
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* These narrow through an `asserts` signature, so the lines after one read
|
|
12
12
|
* the arm it proved, and they throw a plain Error naming what the outcome
|
|
13
|
-
* was. No test runner is imported: the same
|
|
13
|
+
* was. No test runner is imported: the same functions serve vitest, jest
|
|
14
14
|
* and node:test, from `lambder/testing` over a real server and from
|
|
15
15
|
* `lambder/mock` over a mock one. Pure and dependency-free, like the outcome
|
|
16
16
|
* vocabulary they read.
|
|
@@ -49,8 +49,8 @@ const describeOutcome = (outcome) => {
|
|
|
49
49
|
const details = [];
|
|
50
50
|
if (failure.status !== undefined)
|
|
51
51
|
details.push(`status ${failure.status}`);
|
|
52
|
-
if (failure.
|
|
53
|
-
details.push(`
|
|
52
|
+
if (failure.refusal !== undefined)
|
|
53
|
+
details.push(`refusal ${describeValue(failure.refusal)}`);
|
|
54
54
|
if (failure.zodError !== undefined)
|
|
55
55
|
details.push(`zodError ${describeValue(failure.zodError.message)}`);
|
|
56
56
|
// The error's own message, and its cause when it has one: an in-process
|
|
@@ -84,7 +84,7 @@ export function assertApiSuccess(outcome) {
|
|
|
84
84
|
*
|
|
85
85
|
* ```typescript
|
|
86
86
|
* assertApiFailure(await member.apiOutcome("org.delete", { id }), "notAuthorized");
|
|
87
|
-
* assertApiFailure(await guest.apiOutcome("signup", form), "
|
|
87
|
+
* assertApiFailure(await guest.apiOutcome("signup", form), "refusal", { code: LAMBDER_REFUSAL_CODES.rateLimited, status: 429 });
|
|
88
88
|
* ```
|
|
89
89
|
*/
|
|
90
90
|
export function assertApiFailure(outcome, reason, expected = {}) {
|
|
@@ -101,12 +101,38 @@ export function assertApiFailure(outcome, reason, expected = {}) {
|
|
|
101
101
|
if (reason !== undefined && outcome.reason !== reason)
|
|
102
102
|
return refuse();
|
|
103
103
|
if (expected.code !== undefined) {
|
|
104
|
-
// Only the structured
|
|
105
|
-
const
|
|
106
|
-
const code =
|
|
104
|
+
// Only the structured refusal carries a code; a plain string has none to match.
|
|
105
|
+
const refusal = outcome.refusal;
|
|
106
|
+
const code = refusal && typeof refusal === "object" ? refusal.code : undefined;
|
|
107
107
|
if (code !== expected.code)
|
|
108
108
|
return refuse();
|
|
109
109
|
}
|
|
110
110
|
if (expected.status !== undefined && outcome.status !== expected.status)
|
|
111
111
|
return refuse();
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Asserts that a call was refused with the given code, whichever reason it
|
|
115
|
+
* arrived under (a refusal flagged notAuthorized carries its code too), and
|
|
116
|
+
* narrows the outcome's refusal to that code's message, so its `data`
|
|
117
|
+
* reads with the type the code declares.
|
|
118
|
+
*
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const outcome = await visitor.apiOutcome("order.pay", { orderId });
|
|
121
|
+
* assertApiRefusal(outcome, "wallet-short");
|
|
122
|
+
* expect(outcome.refusal.data.available).toBe(1250);
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* The code is checked against the outcome's own codes, so one the endpoint
|
|
126
|
+
* does not declare is a compile error rather than an assertion that can
|
|
127
|
+
* never pass.
|
|
128
|
+
*/
|
|
129
|
+
// `const`: the constraint depends on TOutcome, and without it the code
|
|
130
|
+
// argument widens to that whole constraint instead of staying the literal
|
|
131
|
+
// passed, which would narrow to every declared code at once.
|
|
132
|
+
export function assertApiRefusal(outcome, code) {
|
|
133
|
+
const refusal = outcome.ok ? undefined : outcome.refusal;
|
|
134
|
+
const received = refusal && typeof refusal === "object" ? refusal.code : undefined;
|
|
135
|
+
if (received !== code) {
|
|
136
|
+
throw new Error(`Expected the call to be refused with code "${code}", but it was ${describeOutcome(outcome)}.`, { cause: errorOf(outcome) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LambderUploadObjectOptions } from "../contracts/LambderUploadBucket.js";
|
|
2
2
|
/**
|
|
3
|
-
* What a ticket's form carries for the stored object, in the fields S3's
|
|
3
|
+
* What a POST ticket's form carries for the stored object, in the fields S3's
|
|
4
4
|
* presigned POST reads them from: the tag set as the XML `tagging` field,
|
|
5
5
|
* each metadata entry as `x-amz-meta-<name>` (lowercased, as S3 keeps it),
|
|
6
6
|
* `Cache-Control` and `Content-Disposition`. Both buckets build their
|
|
@@ -8,3 +8,12 @@ import type { LambderUploadObjectOptions } from "../contracts/LambderUploadBucke
|
|
|
8
8
|
* every field is pinned by the ticket like the key and the checksum.
|
|
9
9
|
*/
|
|
10
10
|
export declare const uploadObjectFormFields: (object: LambderUploadObjectOptions | undefined) => Record<string, string>;
|
|
11
|
+
/**
|
|
12
|
+
* What a PUT ticket's headers carry for the stored object, as a presigned
|
|
13
|
+
* PUT sends them: the tag set as the URL-encoded `x-amz-tagging`, each
|
|
14
|
+
* metadata entry as `x-amz-meta-<name>` (lowercased), `cache-control` and
|
|
15
|
+
* `content-disposition`. Names are lowercase, as a signature lists them. Both
|
|
16
|
+
* buckets build their PUT tickets' headers with this, and every one is signed
|
|
17
|
+
* into the URL like the checksum.
|
|
18
|
+
*/
|
|
19
|
+
export declare const uploadObjectHeaders: (object: LambderUploadObjectOptions | undefined) => Record<string, string>;
|