@marktiderman/genesis-forms 0.8.0 → 0.9.0
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/README.md +299 -10
- package/dist/destinations.cjs +202 -39
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.d.cts +67 -21
- package/dist/destinations.d.ts +67 -21
- package/dist/destinations.js +202 -40
- package/dist/destinations.js.map +1 -1
- package/dist/{engine-Bj23YJlB.d.cts → engine-BaHPC2-1.d.cts} +2 -2
- package/dist/{engine-CzinTJph.d.ts → engine-Da99Tv-E.d.ts} +2 -2
- package/dist/index.cjs +996 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +288 -11
- package/dist/index.d.ts +288 -11
- package/dist/index.js +975 -168
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +563 -173
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -474
- package/dist/server.d.ts +3 -474
- package/dist/server.js +558 -174
- package/dist/server.js.map +1 -1
- package/dist/submit-protocol-CFi-0Qd0.d.ts +840 -0
- package/dist/submit-protocol-D0cOckmK.d.cts +840 -0
- package/dist/testing.cjs +250 -154
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -4
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +250 -154
- package/dist/testing.js.map +1 -1
- package/dist/{types-jqDKcmP4.d.cts → types-C0pUfew3.d.cts} +7 -1
- package/dist/{types-jqDKcmP4.d.ts → types-C0pUfew3.d.ts} +7 -1
- package/dist/{validate-DvfQHYnu.d.ts → validate-DpmBssFk.d.ts} +258 -140
- package/dist/{validate-irIPubxc.d.cts → validate-U5LoW3eR.d.cts} +258 -140
- package/package.json +2 -2
- package/dist/upload-protocol-BOe018kL.d.cts +0 -122
- package/dist/upload-protocol-BUO74WkS.d.ts +0 -122
package/dist/server.d.ts
CHANGED
|
@@ -1,474 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { S as SignedUpload } from './upload-protocol-BUO74WkS.js';
|
|
5
|
-
export { f as MAX_UPLOAD_REQUEST_BYTES, j as UploadGrant, k as UploadRefusal, l as UploadRequest } from './upload-protocol-BUO74WkS.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Edit links for a sent response: `<id>.<nonce>.<expires>.<sig>`, where sig is the
|
|
9
|
-
* base64url HMAC-SHA256 of `edit-link:v2:<id>.<nonce>.<expires>` under a server-only
|
|
10
|
-
* secret of at least 32 bytes.
|
|
11
|
-
*
|
|
12
|
-
* - id: the response's uuid.
|
|
13
|
-
* - nonce: the response's current edit nonce, kept by the app. The app refuses a link
|
|
14
|
-
* whose nonce is no longer the response's, so changing the nonce revokes every link
|
|
15
|
-
* made before (change it after each edit: only the newest link works).
|
|
16
|
-
* - expires: when the link stops working, in Unix seconds, about 30 days after it was
|
|
17
|
-
* made (`EDIT_LINK_TTL_SECONDS`).
|
|
18
|
-
*
|
|
19
|
-
* Without a usable secret (missing, or shorter than `MIN_EDIT_SECRET_BYTES`) there are no
|
|
20
|
-
* edit links: signing returns null and every token fails to verify.
|
|
21
|
-
*
|
|
22
|
-
* Web Crypto only, no storage: the app keeps the nonce and checks it. Moved up from
|
|
23
|
-
* The host app's src/lib/forms/edit-link.ts unchanged, so links it signed still verify.
|
|
24
|
-
*/
|
|
25
|
-
/** How long a new edit link works: 30 days. */
|
|
26
|
-
declare const EDIT_LINK_TTL_SECONDS: number;
|
|
27
|
-
/** The shortest secret edit links are signed with: 32 bytes (UTF-8), as long as the HMAC. */
|
|
28
|
-
declare const MIN_EDIT_SECRET_BYTES = 32;
|
|
29
|
-
/** The query parameter an edit link's token travels in: `?edit=<token>`. */
|
|
30
|
-
declare const EDIT_LINK_PARAM = "edit";
|
|
31
|
-
/** Whether `secret` can sign edit links: a string of at least `MIN_EDIT_SECRET_BYTES` bytes. */
|
|
32
|
-
declare function isUsableEditSecret(secret: string | null | undefined): secret is string;
|
|
33
|
-
/** What an edit link says: the response, its nonce when the link was made, and when it stops working. */
|
|
34
|
-
interface EditLinkClaims {
|
|
35
|
-
/** The response's id, in lower case. */
|
|
36
|
-
id: string;
|
|
37
|
-
nonce: string;
|
|
38
|
-
/** Unix seconds. */
|
|
39
|
-
expiresAt: number;
|
|
40
|
-
}
|
|
41
|
-
interface EditLinkOptions {
|
|
42
|
-
/** The current time in milliseconds (tests); `Date.now()` by default. */
|
|
43
|
-
now?: number | undefined;
|
|
44
|
-
/** How long the link works, in seconds; `EDIT_LINK_TTL_SECONDS` by default. */
|
|
45
|
-
ttlSeconds?: number | undefined;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* The edit token for a response and its current nonce, working for `ttlSeconds` from
|
|
49
|
-
* now; null when there is no usable secret (no edit links) or the id or nonce is malformed.
|
|
50
|
-
*/
|
|
51
|
-
declare function signEditToken(link: {
|
|
52
|
-
id: string;
|
|
53
|
-
nonce: string;
|
|
54
|
-
}, secret: string | null | undefined, options?: EditLinkOptions): Promise<string | null>;
|
|
55
|
-
/**
|
|
56
|
-
* What a token says, or null when it is malformed, its signature does not match, it has
|
|
57
|
-
* expired (or claims to last longer than `ttlSeconds` from now), or there is no usable
|
|
58
|
-
* secret. Whether the nonce is still the response's is the caller's check. The
|
|
59
|
-
* comparison is Web Crypto's HMAC verify, which runs in constant time.
|
|
60
|
-
*/
|
|
61
|
-
declare function verifyEditToken(token: string | null | undefined, secret: string | null | undefined, options?: EditLinkOptions): Promise<EditLinkClaims | null>;
|
|
62
|
-
/**
|
|
63
|
-
* A form link with the edit token added: `<formUrl>?edit=<token>` (or `&edit=` when it
|
|
64
|
-
* has a query). `param` names the parameter; `EDIT_LINK_PARAM` by default.
|
|
65
|
-
*/
|
|
66
|
-
declare function withEditToken(formUrl: string, token: string, param?: string): string;
|
|
67
|
-
interface EditLinkUrlOptions extends EditLinkOptions {
|
|
68
|
-
/** The query parameter; `EDIT_LINK_PARAM` by default. */
|
|
69
|
-
param?: string | undefined;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* The link that reopens a response for editing: `formUrl` (the page that shows the form,
|
|
73
|
-
* with any query it needs) plus a token signed for the response's current nonce. Null
|
|
74
|
-
* when there are no edit links (no usable secret) or the id or nonce is malformed.
|
|
75
|
-
*/
|
|
76
|
-
declare function editLinkUrl(formUrl: string, link: {
|
|
77
|
-
id: string;
|
|
78
|
-
nonce: string;
|
|
79
|
-
}, secret: string | null | undefined, options?: EditLinkUrlOptions): Promise<string | null>;
|
|
80
|
-
/** Where the edit-secret reader reports a missing or short secret. */
|
|
81
|
-
type EditSecretLog = (message: string) => void;
|
|
82
|
-
/** Reads the edit-link secret from an environment, warning once when there is none. */
|
|
83
|
-
interface EditSecretReader {
|
|
84
|
-
/** The secret, or null (no edit links) when it is missing or too short; warns once, never with the secret. */
|
|
85
|
-
(env: Record<string, string | undefined>, log?: EditSecretLog): string | null;
|
|
86
|
-
/** Tests only: warn again. */
|
|
87
|
-
reset(): void;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* A reader for the environment variable that holds the edit-link secret (such as
|
|
91
|
-
* `FEEDBACK_EDIT_SECRET`). Without one of at least `MIN_EDIT_SECRET_BYTES` bytes there are
|
|
92
|
-
* no edit links, and the first read says so through `log` (the call's, else the
|
|
93
|
-
* reader's, else `console.warn`); later reads stay quiet until `reset()`.
|
|
94
|
-
*/
|
|
95
|
-
declare function createEditSecretReader(options: {
|
|
96
|
-
variable: string;
|
|
97
|
-
log?: EditSecretLog | undefined;
|
|
98
|
-
}): EditSecretReader;
|
|
99
|
-
interface EditLinkRequestOptions {
|
|
100
|
-
/** The query parameter; `EDIT_LINK_PARAM` by default. */
|
|
101
|
-
param?: string | undefined;
|
|
102
|
-
/**
|
|
103
|
-
* Whether the URL is a page that shows a form (an edit link is only honoured there,
|
|
104
|
-
* such as `/f/<slug>`). Every URL by default.
|
|
105
|
-
*/
|
|
106
|
-
isFormPage?: ((url: URL) => boolean) | undefined;
|
|
107
|
-
}
|
|
108
|
-
/** Whether this request is a form page opened with an edit link (the parameter present, even empty). */
|
|
109
|
-
declare function isEditLinkRequest(url: URL, options?: EditLinkRequestOptions): boolean;
|
|
110
|
-
/**
|
|
111
|
-
* The response, with `Referrer-Policy: no-referrer` and `Cache-Control: private, no-store`
|
|
112
|
-
* added when the request carried an edit link; otherwise the same response, untouched.
|
|
113
|
-
* The link is a credential, so it never leaves in a Referer header (an HTTP header, not
|
|
114
|
-
* only the page's meta tag, so it holds before the page has parsed and for every request
|
|
115
|
-
* the page makes), and the page, prefilled with the response, is never cached.
|
|
116
|
-
*/
|
|
117
|
-
declare function withEditLinkHeaders(request: Request, response: Response, options?: EditLinkRequestOptions): Response;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* The pieces of a submit endpoint's rate limit that aren't storage: the client's
|
|
121
|
-
* address, the part of it that names one client, a key that never holds the address,
|
|
122
|
-
* and a check that fails open. The counter itself is the app's (`RateLimitStore`), so
|
|
123
|
-
* every server instance can share it (a row in Postgres, a key in Redis).
|
|
124
|
-
*
|
|
125
|
-
* Web Crypto only. Moved up from the host app's src/lib/feedback/rate-limit.server.ts;
|
|
126
|
-
* keys made here equal the ones it made, so a deploy doesn't reset anyone's count.
|
|
127
|
-
*/
|
|
128
|
-
/**
|
|
129
|
-
* The headers a proxy reports the client's address in, most trusted first. Trust only
|
|
130
|
-
* headers your proxy sets (and overwrites): a client can send any of them itself.
|
|
131
|
-
*/
|
|
132
|
-
declare const DEFAULT_TRUSTED_IP_HEADERS: readonly string[];
|
|
133
|
-
/**
|
|
134
|
-
* How many submits, saves as they go and photo uploads one client may make per scope
|
|
135
|
-
* (a practice, an account) in a window.
|
|
136
|
-
*
|
|
137
|
-
* - submit: 60 responses per 10 minutes: a class at one gym's Wi-Fi fits.
|
|
138
|
-
* - progress: saving answers as they go. A visit counts once, against `submit`, at its
|
|
139
|
-
* first save; its later saves (each Next, the send, a reload picking up where it was)
|
|
140
|
-
* count here, in the same window: 60 people answering 10 questions each on one Wi-Fi fit.
|
|
141
|
-
* - upload: 120 photos per 10 minutes: that class, a couple of photos each.
|
|
142
|
-
*/
|
|
143
|
-
declare const RATE_LIMIT_DEFAULTS: {
|
|
144
|
-
readonly submit: {
|
|
145
|
-
readonly limit: 60;
|
|
146
|
-
readonly windowSeconds: 600;
|
|
147
|
-
};
|
|
148
|
-
readonly progress: {
|
|
149
|
-
readonly limit: 900;
|
|
150
|
-
readonly windowSeconds: 600;
|
|
151
|
-
};
|
|
152
|
-
readonly upload: {
|
|
153
|
-
readonly limit: 120;
|
|
154
|
-
readonly windowSeconds: 600;
|
|
155
|
-
};
|
|
156
|
-
};
|
|
157
|
-
/**
|
|
158
|
-
* The client's address as the proxy reports it, used only as a rate-limit key: the first
|
|
159
|
-
* entry of the first of `trusted` that is set, trimmed, else `"unknown"`.
|
|
160
|
-
*/
|
|
161
|
-
declare function clientIp(headers: Pick<Headers, "get">, trusted?: readonly string[]): string;
|
|
162
|
-
/** The part of an address that names one client: an IPv4 address whole, an IPv6 address by its /64. */
|
|
163
|
-
declare function addressPrefix(ip: string): string;
|
|
164
|
-
/**
|
|
165
|
-
* The stored key, `<kind>:<scope>:<hex HMAC-SHA256 of the address prefix>`: what is
|
|
166
|
-
* counted (such as `submit`, `progress`, `upload`), whose count it is (a practice id),
|
|
167
|
-
* and the client, which is never stored as its address. `secret` is a server secret.
|
|
168
|
-
*/
|
|
169
|
-
declare function rateLimitKey(input: {
|
|
170
|
-
kind: string;
|
|
171
|
-
scope: string;
|
|
172
|
-
ip: string;
|
|
173
|
-
secret: string;
|
|
174
|
-
}): Promise<string>;
|
|
175
|
-
/** The app's shared counter. */
|
|
176
|
-
interface RateLimitStore {
|
|
177
|
-
/**
|
|
178
|
-
* Count one hit on `key` in its current `windowSeconds` window: true while the count is
|
|
179
|
-
* within `limit`, false past it, null when it could not count.
|
|
180
|
-
*/
|
|
181
|
-
hit(key: string, limit: number, windowSeconds: number): Promise<boolean | null>;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* True when the request may go on. Fails open: when the store can't count (it answers
|
|
185
|
-
* null or throws) the request goes on and `log` says so with `message`.
|
|
186
|
-
*/
|
|
187
|
-
declare function allow(store: RateLimitStore, check: {
|
|
188
|
-
key: string;
|
|
189
|
-
limit: number;
|
|
190
|
-
windowSeconds: number;
|
|
191
|
-
}, options?: {
|
|
192
|
-
log?: ((message: string) => void) | undefined;
|
|
193
|
-
message?: string | undefined;
|
|
194
|
-
}): Promise<boolean>;
|
|
195
|
-
|
|
196
|
-
/** What the store records when an upload begins. */
|
|
197
|
-
interface BeginUploadInput {
|
|
198
|
-
formId: string;
|
|
199
|
-
fieldId: string;
|
|
200
|
-
name: string;
|
|
201
|
-
contentType: string;
|
|
202
|
-
size: number;
|
|
203
|
-
/** A new response's attempt (`attemptHash` of its attempt id), or… */
|
|
204
|
-
attemptHash: string | null;
|
|
205
|
-
/** …the id of the sent response an edit link is changing. Exactly one of the two. */
|
|
206
|
-
editOf: string | null;
|
|
207
|
-
}
|
|
208
|
-
/** One upload as the store reads it back, with what storage holds for it. */
|
|
209
|
-
interface StoredUpload {
|
|
210
|
-
id: string;
|
|
211
|
-
fieldId: string;
|
|
212
|
-
attemptHash: string | null;
|
|
213
|
-
editOf: string | null;
|
|
214
|
-
/** The response that took it, once one did. */
|
|
215
|
-
responseId: string | null;
|
|
216
|
-
name: string;
|
|
217
|
-
contentType: string;
|
|
218
|
-
size: number;
|
|
219
|
-
/** Whether the file is in storage (the respondent's browser finished sending it). */
|
|
220
|
-
stored: boolean;
|
|
221
|
-
/** The type and size storage holds, which are trusted over what the browser said. */
|
|
222
|
-
storedType: string | null;
|
|
223
|
-
storedSize: number | null;
|
|
224
|
-
}
|
|
225
|
-
/** What uploads need from the app's database and storage; the app's service-role client, say. */
|
|
226
|
-
interface UploadStore {
|
|
227
|
-
/**
|
|
228
|
-
* Records one upload and names its object in storage; `{ tooMany: true }` once the
|
|
229
|
-
* attempt or edit has as many as the app allows (the host app: 20).
|
|
230
|
-
*/
|
|
231
|
-
beginUpload(input: BeginUploadInput): Promise<{
|
|
232
|
-
id: string;
|
|
233
|
-
objectName: string;
|
|
234
|
-
} | {
|
|
235
|
-
tooMany: true;
|
|
236
|
-
}>;
|
|
237
|
-
/** Signs an upload of that object, sent to the browser as `upload` (`SignedUpload`). */
|
|
238
|
-
signUpload(objectName: string): Promise<SignedUpload>;
|
|
239
|
-
/** This form's uploads with these ids; others are left out. */
|
|
240
|
-
uploadRefs(formId: string, ids: string[]): Promise<StoredUpload[]>;
|
|
241
|
-
}
|
|
242
|
-
/** Who may use an upload: a new response's attempt, or an edit of a sent response. */
|
|
243
|
-
type UploadOwner = {
|
|
244
|
-
attemptHash: string;
|
|
245
|
-
} | {
|
|
246
|
-
responseId: string;
|
|
247
|
-
};
|
|
248
|
-
/** The form's upload fields (`UPLOAD_FIELD_TYPES`: photo). */
|
|
249
|
-
declare function uploadFields(definition: FormDefinition): FieldBlock[];
|
|
250
|
-
/** An upload field type's limits and its words for a file it doesn't take. */
|
|
251
|
-
interface UploadRules {
|
|
252
|
-
maxFiles: number;
|
|
253
|
-
maxBytes: number;
|
|
254
|
-
mimeTypes: readonly string[];
|
|
255
|
-
typeMessage: string;
|
|
256
|
-
sizeMessage: string;
|
|
257
|
-
countMessage: string;
|
|
258
|
-
}
|
|
259
|
-
/** The rules for an upload field, or null when the field doesn't take files. */
|
|
260
|
-
declare function uploadRules(field: FieldBlock): UploadRules | null;
|
|
261
|
-
/** Said when a reference isn't a file this respondent finished uploading here. */
|
|
262
|
-
declare const NOT_UPLOADED_MESSAGE = "A photo didn't finish uploading. Remove it and add it again.";
|
|
263
|
-
interface CheckUploadsOptions {
|
|
264
|
-
/** The form's id, as the store keys its uploads. */
|
|
265
|
-
formId: string;
|
|
266
|
-
owner: UploadOwner;
|
|
267
|
-
/** Needed only when the answers name a file. */
|
|
268
|
-
store?: Pick<UploadStore, "uploadRefs"> | undefined;
|
|
269
|
-
}
|
|
270
|
-
type CheckedUploads = {
|
|
271
|
-
ok: true;
|
|
272
|
-
answers: Record<string, Json>;
|
|
273
|
-
} | {
|
|
274
|
-
ok: false;
|
|
275
|
-
errors: Record<string, string>;
|
|
276
|
-
};
|
|
277
|
-
/**
|
|
278
|
-
* Checks every file the answers name: uploaded by this attempt (or for this edit) to this
|
|
279
|
-
* form's field, in storage, of a type and size the field takes, and no more files than it
|
|
280
|
-
* takes. The stored name, type and size replace what the browser sent. Answers without
|
|
281
|
-
* files are returned as they are, without asking the store. Errors are keyed by field id;
|
|
282
|
-
* a store that throws throws (answer 503: nothing can be checked).
|
|
283
|
-
*/
|
|
284
|
-
declare function checkUploads(definition: FormDefinition, answers: Record<string, Json>, options: CheckUploadsOptions): Promise<CheckedUploads>;
|
|
285
|
-
/**
|
|
286
|
-
* The stored hash of a visit's attempt id: hex SHA-256 (Web Crypto). It equals
|
|
287
|
-
* The host app's `resumeTokenHash`, so uploads recorded before the move still match.
|
|
288
|
-
*/
|
|
289
|
-
declare function attemptHash(attemptId: string): Promise<string>;
|
|
290
|
-
|
|
291
|
-
/** The form an upload is for, as the app found it from the request. */
|
|
292
|
-
interface UploadForm {
|
|
293
|
-
/** The form's id, as the store keys uploads. */
|
|
294
|
-
id: string;
|
|
295
|
-
/** The published definition respondents answer. */
|
|
296
|
-
definition: FormDefinition;
|
|
297
|
-
/** Whether it takes responses now (not paused, closed, full…). */
|
|
298
|
-
open: boolean;
|
|
299
|
-
/** Whose rate-limit count this is: a practice, an account. */
|
|
300
|
-
scope: string;
|
|
301
|
-
/** Whether its edit links work. Default false: no uploads for an edit. */
|
|
302
|
-
allowsEdits?: boolean | undefined;
|
|
303
|
-
/** Names the form in logs, such as its slug. */
|
|
304
|
-
label?: string | undefined;
|
|
305
|
-
}
|
|
306
|
-
/** A sent response an edit link may change, as the app loads it. */
|
|
307
|
-
interface EditableResponse {
|
|
308
|
-
id: string;
|
|
309
|
-
/** The response's current nonce: only a link made with it works. */
|
|
310
|
-
editNonce: string;
|
|
311
|
-
}
|
|
312
|
-
/** The handler's words; each default is what the host app said. */
|
|
313
|
-
interface UploadMessages {
|
|
314
|
-
/** 415 / 400: not a JSON object. */
|
|
315
|
-
notJson: string;
|
|
316
|
-
/** 413: past `maxRequestBytes`. */
|
|
317
|
-
tooLong: string;
|
|
318
|
-
/** 400: no attempt id, or not a UUID. */
|
|
319
|
-
reload: string;
|
|
320
|
-
/** 404: `resolveForm` found no form. */
|
|
321
|
-
unavailable: string;
|
|
322
|
-
/** 409: the form isn't open. */
|
|
323
|
-
notOpen: string;
|
|
324
|
-
/** 409 (`code: "closed"`): an edit while the form isn't open. */
|
|
325
|
-
notTakingChanges: string;
|
|
326
|
-
/** 403 (`code: "bad_link"`): an edit link that doesn't check out, or a form without edits. */
|
|
327
|
-
badEditLink: string;
|
|
328
|
-
/** 400: the field isn't an upload field of this form. */
|
|
329
|
-
notUploadField: string;
|
|
330
|
-
/** 429: the rate limit. */
|
|
331
|
-
rateLimited: string;
|
|
332
|
-
/** 429 (`code: "too_many_photos"`): the store has as many uploads for this attempt or edit as it allows. */
|
|
333
|
-
tooMany: string;
|
|
334
|
-
/** 503 / 500: something failed on the server; nothing more is said. */
|
|
335
|
-
failed: string;
|
|
336
|
-
}
|
|
337
|
-
declare const UPLOAD_MESSAGES: UploadMessages;
|
|
338
|
-
type Secret = string | null | undefined;
|
|
339
|
-
interface UploadHandlerOptions<P = void> {
|
|
340
|
-
/**
|
|
341
|
-
* The form the request is for (from its host, path or `params`), or null (404). A throw
|
|
342
|
-
* answers 503 and logs the error's message.
|
|
343
|
-
*/
|
|
344
|
-
resolveForm(request: Request, params: P): Promise<UploadForm | null>;
|
|
345
|
-
store: UploadStore;
|
|
346
|
-
/**
|
|
347
|
-
* Uploads for an edit link (`edit` in the request). Without it, or without a usable
|
|
348
|
-
* secret, every edit link is refused (403).
|
|
349
|
-
*/
|
|
350
|
-
edit?: {
|
|
351
|
-
/** The edit-link secret, or a reader of it (`createEditSecretReader`). */
|
|
352
|
-
secret: Secret | (() => Secret);
|
|
353
|
-
/**
|
|
354
|
-
* The sent response with this id, of this form (and its scope), or null (403).
|
|
355
|
-
* A throw answers 503.
|
|
356
|
-
*/
|
|
357
|
-
response(id: string, form: UploadForm): Promise<EditableResponse | null>;
|
|
358
|
-
} | undefined;
|
|
359
|
-
/** Counted per scope and client address, as `kind: "upload"`. Without it, nothing is counted. */
|
|
360
|
-
rateLimit?: {
|
|
361
|
-
store: RateLimitStore;
|
|
362
|
-
/** The server secret the address is hashed with (`rateLimitKey`). */
|
|
363
|
-
secret: string;
|
|
364
|
-
/** Default `RATE_LIMIT_DEFAULTS.upload`: 120 per 600 seconds. */
|
|
365
|
-
limit?: number | undefined;
|
|
366
|
-
windowSeconds?: number | undefined;
|
|
367
|
-
/** Default `DEFAULT_TRUSTED_IP_HEADERS`. */
|
|
368
|
-
trustedIpHeaders?: readonly string[] | undefined;
|
|
369
|
-
} | undefined;
|
|
370
|
-
/** The stored hash of an attempt id. Default `attemptHash` (hex SHA-256). */
|
|
371
|
-
hashAttempt?: ((attemptId: string) => string | Promise<string>) | undefined;
|
|
372
|
-
/** Default `MAX_UPLOAD_REQUEST_BYTES` (4,000). */
|
|
373
|
-
maxRequestBytes?: number | undefined;
|
|
374
|
-
/** The current time (tests): edit links expire. */
|
|
375
|
-
now?: (() => Date) | undefined;
|
|
376
|
-
/** Where failures are said; `console.error` with an `[upload]` prefix by default. Never sent to the browser. */
|
|
377
|
-
log?: ((message: string) => void) | undefined;
|
|
378
|
-
messages?: Partial<UploadMessages> | undefined;
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* The upload endpoint: `(request, params) => Response`, for a POST route. `params` is
|
|
382
|
-
* whatever the route has (a form slug, say), passed to `resolveForm`. See the README's
|
|
383
|
-
* "Uploads" for the JSON and every status.
|
|
384
|
-
*/
|
|
385
|
-
declare function createUploadHandler<P = void>(options: UploadHandlerOptions<P>): (request: Request, params: P) => Promise<Response>;
|
|
386
|
-
|
|
387
|
-
interface SubmissionInput {
|
|
388
|
-
/** Answers keyed by field id. Ignored when `events` is given. */
|
|
389
|
-
answers?: Record<string, Json> | undefined;
|
|
390
|
-
/** The respondent's event log. Replayed instead of trusting `answers`. */
|
|
391
|
-
events?: FormEvent[] | undefined;
|
|
392
|
-
/** URL and host inputs sent by the client. Server inputs here are ignored. */
|
|
393
|
-
hidden?: Record<string, Json> | undefined;
|
|
394
|
-
}
|
|
395
|
-
interface SubmissionOptions {
|
|
396
|
-
/** Values for `source: "server"` inputs, resolved by the server (account id, location, user id…). */
|
|
397
|
-
context?: Record<string, Json> | undefined;
|
|
398
|
-
/** `false` checks a draft: formats only, nothing required. Default true. */
|
|
399
|
-
complete?: boolean | undefined;
|
|
400
|
-
validators?: Record<string, FieldValidator> | undefined;
|
|
401
|
-
/**
|
|
402
|
-
* Most events accepted, counted after `compactEvents`. Default `MAX_EVENTS`
|
|
403
|
-
* (10,000). At most `RAW_EVENTS_PER_EVENT` (10) times as many may be sent.
|
|
404
|
-
*/
|
|
405
|
-
maxEvents?: number | undefined;
|
|
406
|
-
/**
|
|
407
|
-
* Most JSON values in the answers, answer events and hidden inputs
|
|
408
|
-
* together (each number, text, true / false, list item, row and cell
|
|
409
|
-
* counts one; a run of answers to one field, as typing in a table sends,
|
|
410
|
-
* counts its last only). Default `MAX_SUBMISSION_VALUES` (200,000).
|
|
411
|
-
*/
|
|
412
|
-
maxValues?: number | undefined;
|
|
413
|
-
/**
|
|
414
|
-
* Most work replaying the events may take: one unit per block each time
|
|
415
|
-
* the path is walked, one per calculated field each time the answers
|
|
416
|
-
* settle, and a formula's size (its numbers, answers and operators) each
|
|
417
|
-
* time it is worked out. Default `MAX_REPLAY_WORK` (1,000,000). Only the
|
|
418
|
-
* replay counts: the submit that follows it does not.
|
|
419
|
-
*/
|
|
420
|
-
maxReplayWork?: number | undefined;
|
|
421
|
-
/**
|
|
422
|
-
* The option lists the app supplies for `optionsFrom: { source: "host" }`
|
|
423
|
-
* choice fields, loaded here on the server (never taken from the client):
|
|
424
|
-
* an answer must be one of the field's own options or of these. Pass what
|
|
425
|
-
* the form was shown with, loaded afresh, such as the current staff.
|
|
426
|
-
*/
|
|
427
|
-
hostOptions?: HostOptions | undefined;
|
|
428
|
-
}
|
|
429
|
-
interface CheckedSubmission {
|
|
430
|
-
status: "draft" | "completed";
|
|
431
|
-
/**
|
|
432
|
-
* Visible answers only, keyed by field id (kept hidden answers too, with
|
|
433
|
-
* `clearHiddenValues: false`). Calculated fields hold the value worked out
|
|
434
|
-
* here from these answers.
|
|
435
|
-
*/
|
|
436
|
-
answers: Record<string, Json>;
|
|
437
|
-
/** Untrusted URL and host inputs, coerced to their declared types. Identifying ones are left out on anonymous forms. */
|
|
438
|
-
hidden: Record<string, Json>;
|
|
439
|
-
/** Trusted server inputs. Identifying ones are left out on anonymous forms. */
|
|
440
|
-
context: Record<string, Json>;
|
|
441
|
-
ending: string | null;
|
|
442
|
-
/** Client-sent keys that were dropped, each once: unknown fields, server inputs, undeclared or withheld hidden names. */
|
|
443
|
-
ignored: string[];
|
|
444
|
-
}
|
|
445
|
-
type SubmissionResult = {
|
|
446
|
-
ok: true;
|
|
447
|
-
submission: CheckedSubmission;
|
|
448
|
-
} | {
|
|
449
|
-
ok: false;
|
|
450
|
-
errors: Record<string, string>;
|
|
451
|
-
};
|
|
452
|
-
/** Default for `SubmissionOptions.maxEvents`. */
|
|
453
|
-
declare const MAX_EVENTS = 10000;
|
|
454
|
-
/** Default for `SubmissionOptions.maxValues`. */
|
|
455
|
-
declare const MAX_SUBMISSION_VALUES = 200000;
|
|
456
|
-
/** Default for `SubmissionOptions.maxReplayWork`. */
|
|
457
|
-
declare const MAX_REPLAY_WORK = 1000000;
|
|
458
|
-
/**
|
|
459
|
-
* Most events sent, before compaction, as a multiple of `maxEvents`: a
|
|
460
|
-
* keystroke log compacts to far fewer, but every event sent is looked at.
|
|
461
|
-
*/
|
|
462
|
-
declare const RAW_EVENTS_PER_EVENT = 10;
|
|
463
|
-
/**
|
|
464
|
-
* Check a submission against the published definition. Never throws on a
|
|
465
|
-
* malformed submission: it returns `{ ok: false, errors: { "": … } }`.
|
|
466
|
-
*/
|
|
467
|
-
declare function validateSubmission(published: FormDefinition, submission: SubmissionInput, options?: SubmissionOptions): SubmissionResult;
|
|
468
|
-
/**
|
|
469
|
-
* The definition as respondents may see it. R1 forms hold nothing secret;
|
|
470
|
-
* assessment answer keys (R4) will be stripped here.
|
|
471
|
-
*/
|
|
472
|
-
declare function publicDefinition(definition: FormDefinition): FormDefinition;
|
|
473
|
-
|
|
474
|
-
export { type BeginUploadInput, type CheckUploadsOptions, type CheckedSubmission, type CheckedUploads, DEFAULT_TRUSTED_IP_HEADERS, EDIT_LINK_PARAM, EDIT_LINK_TTL_SECONDS, type EditLinkClaims, type EditLinkOptions, type EditLinkRequestOptions, type EditLinkUrlOptions, type EditSecretLog, type EditSecretReader, type EditableResponse, HostOptions, MAX_EVENTS, MAX_REPLAY_WORK, MAX_SUBMISSION_VALUES, MIN_EDIT_SECRET_BYTES, NOT_UPLOADED_MESSAGE, RATE_LIMIT_DEFAULTS, RAW_EVENTS_PER_EVENT, type RateLimitStore, SignedUpload, type StoredUpload, type SubmissionInput, type SubmissionOptions, type SubmissionResult, UPLOAD_MESSAGES, type UploadForm, type UploadHandlerOptions, type UploadMessages, type UploadOwner, type UploadRules, type UploadStore, addressPrefix, allow, attemptHash, checkUploads, clientIp, createEditSecretReader, createUploadHandler, editLinkUrl, isEditLinkRequest, isUsableEditSecret, publicDefinition, rateLimitKey, signEditToken, uploadFields, uploadRules, validateSubmission, verifyEditToken, withEditLinkHeaders, withEditToken };
|
|
1
|
+
export { A as AnswerSnapshot, H as HostOptions, f as OptionSnapshot, k as RestoredAnswers, m as answerSnapshots, n as answersFromSnapshots, ac as withHostOptions } from './validate-DpmBssFk.js';
|
|
2
|
+
import './types-C0pUfew3.js';
|
|
3
|
+
export { F as BeginUploadInput, G as CheckUploadsOptions, H as CheckedSubmission, J as CheckedUploads, K as ClientIpOptions, L as DEFAULT_TRUSTED_IP_HEADERS, N as EDIT_LINK_PARAM, O as EDIT_LINK_TTL_SECONDS, Q as EditLinkClaims, R as EditLinkOptions, T as EditLinkRequestOptions, V as EditLinkUrlOptions, W as EditSecretLog, X as EditSecretReader, Y as EditableResponse, I as IDEMPOTENCY_KEY_PATTERN, Z as MAX_EVENTS, _ as MAX_REPLAY_WORK, $ as MAX_SUBMISSION_VALUES, i as MAX_UPLOAD_REQUEST_BYTES, a0 as MIN_EDIT_SECRET_BYTES, j as MergeOptions, a1 as NOT_UPLOADED_MESSAGE, a2 as RATE_LIMIT_DEFAULTS, a3 as RAW_EVENTS_PER_EVENT, a4 as RateLimitAnswer, a5 as RateLimitStore, n as SignedUpload, a6 as StoredSubmissionRef, a7 as StoredUpload, S as SubmissionInput, a8 as SubmissionOptions, a9 as SubmissionRecord, aa as SubmissionResult, ab as SubmissionStore, c as SubmitFailureReason, o as SubmitOutcome, b as SubmitRequest, ac as SubmitResponseOptions, d as SubmitResult, ad as UPLOAD_MESSAGES, ae as UploadForm, p as UploadGrant, af as UploadHandlerOptions, ag as UploadMessages, ah as UploadOwner, q as UploadRefusal, r as UploadRequest, ai as UploadRules, aj as UploadStore, ak as addressPrefix, al as allow, am as attemptHash, an as checkUploads, ao as clientIp, ap as createEditSecretReader, aq as createUploadHandler, ar as editLinkUrl, as as isEditLinkRequest, s as isIdempotencyKey, v as isSubmitResult, at as isUsableEditSecret, w as mergeResponses, au as publicDefinition, av as rateLimitKey, aw as signEditToken, E as submitOutcome, ax as submitResponse, ay as uploadFields, az as uploadRules, aA as validateSubmission, aB as verifyEditToken, aC as withEditLinkHeaders, aD as withEditToken } from './submit-protocol-CFi-0Qd0.js';
|