@usefillo/core 0.1.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/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/index.d.ts +478 -0
- package/dist/index.js +976 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fillo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @usefillo/core
|
|
2
|
+
|
|
3
|
+
The framework-agnostic core of [Fillo](https://fillo.so) — forms that render **inside your own product**, with your UI, no iframe.
|
|
4
|
+
|
|
5
|
+
### 📚 Full documentation → **[fillo.so/docs](https://fillo.so/docs)**
|
|
6
|
+
|
|
7
|
+
This package holds the shared foundation: the form schema, validation, the conditional-logic engine, response prefill/piping, and a JS client with a resumable upload protocol. It has **zero framework dependencies**.
|
|
8
|
+
|
|
9
|
+
Most apps don't install this directly — you install a renderer (**[@usefillo/react](https://www.npmjs.com/package/@usefillo/react)** or **[@usefillo/dom](https://www.npmjs.com/package/@usefillo/dom)**), which re-exports everything here you need for embedding. Reach for `@usefillo/core` when you're building your own renderer or working with forms on the server.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm i @usefillo/core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createClient, defineForm, validateResponse, visibleBlocks } from "@usefillo/core";
|
|
17
|
+
|
|
18
|
+
const client = createClient({ baseUrl: "https://fillo.so" });
|
|
19
|
+
|
|
20
|
+
const form = defineForm({
|
|
21
|
+
id: "cust-feedback",
|
|
22
|
+
pages: [{ id: "p1", blocks: [
|
|
23
|
+
{ id: "email", kind: "email", label: "Work email", required: true },
|
|
24
|
+
{ id: "msg", kind: "long_text", label: "What should we know?" },
|
|
25
|
+
] }],
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Key exports: `createClient` / `FilloClient`, `defineForm`, `validateResponse`, `validateField`, `visibleBlocks`, and the schema types (`FormSchema`, `Field`, `FieldKind`, `FormTheme`, `ResponseData`, …).
|
|
30
|
+
|
|
31
|
+
## Links
|
|
32
|
+
|
|
33
|
+
- **Docs:** [fillo.so/docs](https://fillo.so/docs)
|
|
34
|
+
- **Website:** [fillo.so](https://fillo.so)
|
|
35
|
+
|
|
36
|
+
MIT licensed.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Fillo form schema — the single source of truth shared by the builder,
|
|
3
|
+
* the embed SDK, the API and the responses grid.
|
|
4
|
+
*/
|
|
5
|
+
type ConditionOp = "eq" | "neq" | "contains" | "gt" | "lt" | "answered" | "not_answered";
|
|
6
|
+
interface Condition {
|
|
7
|
+
fieldId: string;
|
|
8
|
+
op: ConditionOp;
|
|
9
|
+
value?: string | number | boolean;
|
|
10
|
+
}
|
|
11
|
+
type FieldKind = "short_text" | "long_text" | "email" | "url" | "phone" | "number" | "select" | "multi_select" | "dropdown" | "checkbox" | "rating" | "linear_scale" | "ranking" | "matrix" | "signature" | "date" | "file_upload" | "hidden" | "custom";
|
|
12
|
+
type ContentKind = "heading" | "paragraph" | "divider";
|
|
13
|
+
type BlockKind = FieldKind | ContentKind;
|
|
14
|
+
interface SelectOption {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
interface BaseBlock {
|
|
19
|
+
/** Stable id — for fields this is the key responses are stored under. */
|
|
20
|
+
id: string;
|
|
21
|
+
kind: BlockKind;
|
|
22
|
+
/** Show this block only when all conditions match (AND). Empty/undefined = always. */
|
|
23
|
+
visibleIf?: Condition[];
|
|
24
|
+
}
|
|
25
|
+
interface BaseField extends BaseBlock {
|
|
26
|
+
kind: FieldKind;
|
|
27
|
+
label: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
required?: boolean;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
}
|
|
32
|
+
interface TextField extends BaseField {
|
|
33
|
+
kind: "short_text" | "long_text" | "email" | "url" | "phone";
|
|
34
|
+
maxLength?: number;
|
|
35
|
+
}
|
|
36
|
+
interface NumberField extends BaseField {
|
|
37
|
+
kind: "number";
|
|
38
|
+
min?: number;
|
|
39
|
+
max?: number;
|
|
40
|
+
}
|
|
41
|
+
interface ChoiceField extends BaseField {
|
|
42
|
+
kind: "select" | "multi_select" | "dropdown";
|
|
43
|
+
options: SelectOption[];
|
|
44
|
+
/** Append an "Other" choice with a free-text input; the typed text is stored as the value. */
|
|
45
|
+
allowOther?: boolean;
|
|
46
|
+
/** Show options in a random order per respondent (the "Other" choice stays last). */
|
|
47
|
+
shuffleOptions?: boolean;
|
|
48
|
+
}
|
|
49
|
+
interface CheckboxField extends BaseField {
|
|
50
|
+
kind: "checkbox";
|
|
51
|
+
}
|
|
52
|
+
interface RatingField extends BaseField {
|
|
53
|
+
kind: "rating";
|
|
54
|
+
/** Number of steps, default 5. */
|
|
55
|
+
max?: number;
|
|
56
|
+
}
|
|
57
|
+
interface DateField extends BaseField {
|
|
58
|
+
kind: "date";
|
|
59
|
+
}
|
|
60
|
+
interface LinearScaleField extends BaseField {
|
|
61
|
+
kind: "linear_scale";
|
|
62
|
+
/** Default 1. */
|
|
63
|
+
min?: number;
|
|
64
|
+
/** Default 10. */
|
|
65
|
+
max?: number;
|
|
66
|
+
minLabel?: string;
|
|
67
|
+
maxLabel?: string;
|
|
68
|
+
}
|
|
69
|
+
interface RankingField extends BaseField {
|
|
70
|
+
kind: "ranking";
|
|
71
|
+
options: SelectOption[];
|
|
72
|
+
}
|
|
73
|
+
interface MatrixField extends BaseField {
|
|
74
|
+
kind: "matrix";
|
|
75
|
+
/** Questions (one answer per row). */
|
|
76
|
+
rows: SelectOption[];
|
|
77
|
+
/** Answer columns. */
|
|
78
|
+
columns: SelectOption[];
|
|
79
|
+
}
|
|
80
|
+
interface SignatureField extends BaseField {
|
|
81
|
+
kind: "signature";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Never rendered. Value arrives via a URL query parameter — for campaign
|
|
85
|
+
* tags, user ids, A/B variants. Shown in the responses grid like any field.
|
|
86
|
+
*/
|
|
87
|
+
interface HiddenField extends BaseField {
|
|
88
|
+
kind: "hidden";
|
|
89
|
+
/** Query parameter name; defaults to the field id. */
|
|
90
|
+
paramName?: string;
|
|
91
|
+
defaultValue?: string;
|
|
92
|
+
}
|
|
93
|
+
interface FileUploadField extends BaseField {
|
|
94
|
+
kind: "file_upload";
|
|
95
|
+
/** Max files, default 1. */
|
|
96
|
+
maxFiles?: number;
|
|
97
|
+
/** Per-file size cap in MB. Default 500. */
|
|
98
|
+
maxFileSizeMb?: number;
|
|
99
|
+
/** Accepted types, e.g. ["image/*", ".pdf", "video/mp4"]. Empty = anything. */
|
|
100
|
+
accept?: string[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A field type Fillo doesn't ship. You define it in code and supply the
|
|
104
|
+
* renderer via the SDK's `customComponents` map, keyed by `component`. The
|
|
105
|
+
* value is arbitrary JSON; core only enforces `required`. Your component does
|
|
106
|
+
* any richer validation and rendering — radios, sliders, address pickers,
|
|
107
|
+
* anything. See the embed docs.
|
|
108
|
+
*/
|
|
109
|
+
interface CustomField extends BaseField {
|
|
110
|
+
kind: "custom";
|
|
111
|
+
/** Key looked up in the SDK's `customComponents` map. */
|
|
112
|
+
component: string;
|
|
113
|
+
/** Arbitrary options handed to your component. */
|
|
114
|
+
config?: Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
type Field = TextField | NumberField | ChoiceField | CheckboxField | RatingField | LinearScaleField | RankingField | MatrixField | SignatureField | DateField | FileUploadField | HiddenField | CustomField;
|
|
117
|
+
interface HeadingBlock extends BaseBlock {
|
|
118
|
+
kind: "heading";
|
|
119
|
+
text: string;
|
|
120
|
+
}
|
|
121
|
+
interface ParagraphBlock extends BaseBlock {
|
|
122
|
+
kind: "paragraph";
|
|
123
|
+
text: string;
|
|
124
|
+
}
|
|
125
|
+
interface DividerBlock extends BaseBlock {
|
|
126
|
+
kind: "divider";
|
|
127
|
+
}
|
|
128
|
+
type ContentBlock = HeadingBlock | ParagraphBlock | DividerBlock;
|
|
129
|
+
type Block = Field | ContentBlock;
|
|
130
|
+
declare const CONTENT_KINDS: ContentKind[];
|
|
131
|
+
declare function isField(block: Block): block is Field;
|
|
132
|
+
interface FormPage {
|
|
133
|
+
id: string;
|
|
134
|
+
title?: string;
|
|
135
|
+
blocks: Block[];
|
|
136
|
+
}
|
|
137
|
+
interface FormSettings {
|
|
138
|
+
submitLabel?: string;
|
|
139
|
+
successTitle?: string;
|
|
140
|
+
successMessage?: string;
|
|
141
|
+
redirectUrl?: string;
|
|
142
|
+
showProgress?: boolean;
|
|
143
|
+
/** Notify this address on every submission. */
|
|
144
|
+
notifyEmail?: string;
|
|
145
|
+
/** Send respondents a receipt (to the first answered email field). */
|
|
146
|
+
sendReceipt?: boolean;
|
|
147
|
+
}
|
|
148
|
+
interface FormSchema {
|
|
149
|
+
version: 1;
|
|
150
|
+
title: string;
|
|
151
|
+
description?: string;
|
|
152
|
+
pages: FormPage[];
|
|
153
|
+
settings: FormSettings;
|
|
154
|
+
}
|
|
155
|
+
interface FormTheme {
|
|
156
|
+
/** Accent for buttons, focus rings, selection. */
|
|
157
|
+
primary?: string;
|
|
158
|
+
background?: string;
|
|
159
|
+
text?: string;
|
|
160
|
+
/** Border radius scale, e.g. "8px". */
|
|
161
|
+
radius?: string;
|
|
162
|
+
fontFamily?: string;
|
|
163
|
+
}
|
|
164
|
+
/** A completed upload, referenced from response data. */
|
|
165
|
+
interface FileValue {
|
|
166
|
+
fileId: string;
|
|
167
|
+
name: string;
|
|
168
|
+
size: number;
|
|
169
|
+
mime: string;
|
|
170
|
+
/** Download URL (may be storage-provider specific, e.g. a Drive link). */
|
|
171
|
+
url?: string;
|
|
172
|
+
}
|
|
173
|
+
/** Any JSON — what a custom field may hold. */
|
|
174
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
175
|
+
[key: string]: JsonValue;
|
|
176
|
+
};
|
|
177
|
+
type FieldValue = string | number | boolean | string[] | FileValue[]
|
|
178
|
+
/** Matrix answers: rowId → columnId. */
|
|
179
|
+
| Record<string, string>
|
|
180
|
+
/** Custom fields hold arbitrary JSON. */
|
|
181
|
+
| JsonValue | null | undefined;
|
|
182
|
+
type ResponseData = Record<string, FieldValue>;
|
|
183
|
+
type UploadStatus = "pending" | "uploading" | "complete" | "aborted";
|
|
184
|
+
/**
|
|
185
|
+
* How the browser should move the bytes.
|
|
186
|
+
* - "formwork": chunked PUTs to the Fillo API (X-Upload-Offset protocol).
|
|
187
|
+
* - "gdrive": direct to a Google Drive resumable session URL (Content-Range
|
|
188
|
+
* protocol) — bytes never touch the Fillo server.
|
|
189
|
+
* - "s3-put": a single PUT to a presigned S3/R2 URL — bytes go straight to the
|
|
190
|
+
* bucket.
|
|
191
|
+
*/
|
|
192
|
+
type UploadTransport = {
|
|
193
|
+
type: "formwork";
|
|
194
|
+
} | {
|
|
195
|
+
type: "gdrive";
|
|
196
|
+
uploadUrl: string;
|
|
197
|
+
} | {
|
|
198
|
+
type: "s3-put";
|
|
199
|
+
uploadUrl: string;
|
|
200
|
+
};
|
|
201
|
+
interface UploadSession {
|
|
202
|
+
id: string;
|
|
203
|
+
formId: string;
|
|
204
|
+
fieldId: string;
|
|
205
|
+
fileName: string;
|
|
206
|
+
size: number;
|
|
207
|
+
mime: string;
|
|
208
|
+
/** Server-chosen chunk size in bytes. */
|
|
209
|
+
chunkSize: number;
|
|
210
|
+
/** Contiguous bytes received so far — resume from here. */
|
|
211
|
+
uploadedBytes: number;
|
|
212
|
+
status: UploadStatus;
|
|
213
|
+
/** Defaults to the Fillo protocol when omitted. */
|
|
214
|
+
transport?: UploadTransport;
|
|
215
|
+
/** Set when status is "complete". */
|
|
216
|
+
file?: FileValue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** All conditions must hold (AND). No conditions = visible. */
|
|
220
|
+
declare function isBlockVisible(block: Block, data: ResponseData): boolean;
|
|
221
|
+
declare function visibleBlocks(page: FormPage, data: ResponseData): Block[];
|
|
222
|
+
/** Every field in the form (across pages), in order. */
|
|
223
|
+
declare function allFields(form: FormSchema): Field[];
|
|
224
|
+
/** Fields currently visible given the response data — the set that gets validated. */
|
|
225
|
+
declare function visibleFields(form: FormSchema, data: ResponseData): Field[];
|
|
226
|
+
|
|
227
|
+
/** Validate a single answered value for a field. Returns an error message or null. */
|
|
228
|
+
declare function validateField(field: Field, value: FieldValue): string | null;
|
|
229
|
+
interface ValidationResult {
|
|
230
|
+
ok: boolean;
|
|
231
|
+
/** fieldId -> message for every failing field. */
|
|
232
|
+
errors: Record<string, string>;
|
|
233
|
+
/** Data trimmed to the fields that are visible (hidden answers are dropped). */
|
|
234
|
+
data: ResponseData;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Validate a full submission against the schema. Only currently-visible fields
|
|
238
|
+
* are validated and kept — answers to fields hidden by logic are discarded.
|
|
239
|
+
*/
|
|
240
|
+
declare function validateResponse(form: FormSchema, data: ResponseData): ValidationResult;
|
|
241
|
+
|
|
242
|
+
interface SchemaValidationResult {
|
|
243
|
+
ok: boolean;
|
|
244
|
+
/** Present when ok — a normalized, structurally-valid schema. */
|
|
245
|
+
schema?: FormSchema;
|
|
246
|
+
/** Present when !ok — a short human-readable reason. */
|
|
247
|
+
error?: string;
|
|
248
|
+
}
|
|
249
|
+
/** Validate an untrusted object as a FormSchema. Also enforces unique field ids. */
|
|
250
|
+
declare function validateFormSchema(input: unknown): SchemaValidationResult;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Canonical plain-text rendering of an answer — the single source of truth for
|
|
254
|
+
* the responses grid, CSV export, and notification emails. Returns "" for an
|
|
255
|
+
* empty answer. Rich per-surface rendering (links, stars, images) stays in the
|
|
256
|
+
* UI; this is the text everything agrees on.
|
|
257
|
+
*
|
|
258
|
+
* The switch is exhaustive with no `default`, so adding a field kind is a
|
|
259
|
+
* compile error here until it's handled — no silently-wrong exports/emails.
|
|
260
|
+
*/
|
|
261
|
+
declare function formatAnswer(field: Field, value: FieldValue): string;
|
|
262
|
+
|
|
263
|
+
/** Display metadata for every block kind — drives the builder palette. */
|
|
264
|
+
declare const BLOCK_KIND_META: Record<BlockKind, {
|
|
265
|
+
label: string;
|
|
266
|
+
hint: string;
|
|
267
|
+
}>;
|
|
268
|
+
/** A new block of the given kind with sensible defaults, ready for the builder. */
|
|
269
|
+
declare function createBlock(kind: BlockKind): Block;
|
|
270
|
+
/** A minimal valid empty form. */
|
|
271
|
+
declare function createEmptyForm(title?: string): FormSchema;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Turning a natural-language goal into a form: an LLM is good at choosing the
|
|
275
|
+
* right *questions*, but unreliable at the structural plumbing (stable ids,
|
|
276
|
+
* exact per-kind config). So generation targets this lossy spec, and the
|
|
277
|
+
* assembler below rebuilds a structurally-sound schema from `createBlock`
|
|
278
|
+
* defaults — the model only ever supplies the human-meaningful parts.
|
|
279
|
+
*/
|
|
280
|
+
/**
|
|
281
|
+
* Field kinds the generator may use. Excludes hidden/custom/signature: the
|
|
282
|
+
* first two are code-only concerns and signature is too niche to draft well.
|
|
283
|
+
*/
|
|
284
|
+
declare const DRAFT_KINDS: readonly BlockKind[];
|
|
285
|
+
/** The LLM-friendly intermediate shape. Everything optional but `kind`. */
|
|
286
|
+
interface FieldSpec {
|
|
287
|
+
kind: string;
|
|
288
|
+
label?: string;
|
|
289
|
+
required?: boolean;
|
|
290
|
+
/** Choice / ranking options, or matrix is handled by rows+columns. */
|
|
291
|
+
options?: string[];
|
|
292
|
+
min?: number;
|
|
293
|
+
max?: number;
|
|
294
|
+
minLabel?: string;
|
|
295
|
+
maxLabel?: string;
|
|
296
|
+
rows?: string[];
|
|
297
|
+
columns?: string[];
|
|
298
|
+
placeholder?: string;
|
|
299
|
+
/** Content for heading / paragraph blocks. */
|
|
300
|
+
text?: string;
|
|
301
|
+
}
|
|
302
|
+
interface FormDraftSpec {
|
|
303
|
+
title?: string;
|
|
304
|
+
description?: string;
|
|
305
|
+
pages?: Array<{
|
|
306
|
+
title?: string;
|
|
307
|
+
fields?: FieldSpec[];
|
|
308
|
+
}>;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Build a valid {@link FormSchema} from an untrusted draft spec. Unknown kinds
|
|
312
|
+
* and empty pages are dropped; if nothing survives, a single empty page is
|
|
313
|
+
* kept so the builder always has something to render. The result should still
|
|
314
|
+
* be passed through `validateFormSchema` before persisting.
|
|
315
|
+
*/
|
|
316
|
+
declare function assembleForm(spec: FormDraftSpec): FormSchema;
|
|
317
|
+
|
|
318
|
+
interface FilloClientOptions {
|
|
319
|
+
/** Origin of the Fillo server, e.g. "https://forms.example.com". */
|
|
320
|
+
baseUrl: string;
|
|
321
|
+
/**
|
|
322
|
+
* Publishable workspace key (pk_…) — safe to ship in client code. Required
|
|
323
|
+
* only for syncing code-defined forms into the workspace.
|
|
324
|
+
*/
|
|
325
|
+
key?: string;
|
|
326
|
+
fetch?: typeof fetch;
|
|
327
|
+
}
|
|
328
|
+
interface PublishedForm {
|
|
329
|
+
id: string;
|
|
330
|
+
slug: string;
|
|
331
|
+
schema: FormSchema;
|
|
332
|
+
theme: FormTheme | null;
|
|
333
|
+
/** True when the form has stopped accepting responses (closed/limit reached). */
|
|
334
|
+
closed?: boolean;
|
|
335
|
+
}
|
|
336
|
+
interface SubmitResult {
|
|
337
|
+
ok: boolean;
|
|
338
|
+
responseId?: string;
|
|
339
|
+
/** fieldId -> message when the server rejects the submission. */
|
|
340
|
+
errors?: Record<string, string>;
|
|
341
|
+
}
|
|
342
|
+
/** Anti-spam signals collected by the renderer. */
|
|
343
|
+
interface SubmitMeta {
|
|
344
|
+
/** Honeypot value — must be empty for humans. */
|
|
345
|
+
hp?: string;
|
|
346
|
+
/** Time from first render to submit. */
|
|
347
|
+
elapsedMs?: number;
|
|
348
|
+
}
|
|
349
|
+
interface UploadProgress {
|
|
350
|
+
uploadedBytes: number;
|
|
351
|
+
totalBytes: number;
|
|
352
|
+
/** 0..1 */
|
|
353
|
+
fraction: number;
|
|
354
|
+
}
|
|
355
|
+
interface UploadFileOptions {
|
|
356
|
+
fieldId: string;
|
|
357
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
358
|
+
signal?: AbortSignal;
|
|
359
|
+
/** Resume an interrupted session instead of starting fresh. */
|
|
360
|
+
sessionId?: string;
|
|
361
|
+
}
|
|
362
|
+
declare class FilloError extends Error {
|
|
363
|
+
status?: number | undefined;
|
|
364
|
+
constructor(message: string, status?: number | undefined);
|
|
365
|
+
}
|
|
366
|
+
declare class FilloClient {
|
|
367
|
+
/** Server origin this client targets, normalized (no trailing slash). */
|
|
368
|
+
readonly baseUrl: string;
|
|
369
|
+
private fetch;
|
|
370
|
+
/** Publishable workspace key, when configured. */
|
|
371
|
+
readonly key?: string;
|
|
372
|
+
constructor(options: FilloClientOptions);
|
|
373
|
+
private url;
|
|
374
|
+
private json;
|
|
375
|
+
/** Fetch a published form definition by id or slug. */
|
|
376
|
+
getForm(idOrSlug: string): Promise<PublishedForm>;
|
|
377
|
+
/**
|
|
378
|
+
* Upsert a code-defined form into the workspace identified by the client's
|
|
379
|
+
* publishable key. Returns the canonical form id used for submissions.
|
|
380
|
+
*/
|
|
381
|
+
syncForm(handle: string, schema: FormSchema, theme?: FormTheme | null): Promise<{
|
|
382
|
+
formId: string;
|
|
383
|
+
slug: string;
|
|
384
|
+
}>;
|
|
385
|
+
/** Submit a response. Returns per-field errors instead of throwing on validation failure. */
|
|
386
|
+
submit(formId: string, data: ResponseData, meta?: SubmitMeta): Promise<SubmitResult>;
|
|
387
|
+
/**
|
|
388
|
+
* Open a respondent session for funnel analysis. Fire-and-forget: returns the
|
|
389
|
+
* session id, or null if tracking is unavailable (never blocks the form).
|
|
390
|
+
*/
|
|
391
|
+
startSession(formId: string, pageCount: number): Promise<string | null>;
|
|
392
|
+
/** Report progress on a session (furthest page reached, or completion). */
|
|
393
|
+
reportProgress(sessionId: string, data: {
|
|
394
|
+
furthestPage?: number;
|
|
395
|
+
completed?: boolean;
|
|
396
|
+
}): void;
|
|
397
|
+
/** Current state of an upload session — used to resume after interruption. */
|
|
398
|
+
getUploadSession(sessionId: string): Promise<UploadSession>;
|
|
399
|
+
/**
|
|
400
|
+
* Resumable chunked upload. Creates (or resumes) a session, streams the file
|
|
401
|
+
* chunk by chunk with progress callbacks, and finalizes into a FileValue that
|
|
402
|
+
* goes into the response data. Survives flaky connections: each chunk is
|
|
403
|
+
* retried, and on repeated failure the true offset is re-queried so no byte
|
|
404
|
+
* is sent twice or skipped.
|
|
405
|
+
*
|
|
406
|
+
* Depending on the form's storage settings the server picks a transport:
|
|
407
|
+
* Fillo's own chunk protocol, or direct-to-Google-Drive resumable
|
|
408
|
+
* sessions — in which case the bytes go straight from this browser to the
|
|
409
|
+
* form owner's Drive and never touch the Fillo server.
|
|
410
|
+
*/
|
|
411
|
+
uploadFile(formId: string, file: File | Blob, options: UploadFileOptions): Promise<FileValue>;
|
|
412
|
+
private formworkUploadLoop;
|
|
413
|
+
/**
|
|
414
|
+
* Google Drive resumable protocol: PUT chunks with Content-Range; Drive
|
|
415
|
+
* answers 308 + a Range header while incomplete, 200/201 when done. On
|
|
416
|
+
* repeated failure we re-sync through our session endpoint (the server
|
|
417
|
+
* queries Drive for the authoritative offset).
|
|
418
|
+
*/
|
|
419
|
+
private driveUploadLoop;
|
|
420
|
+
/**
|
|
421
|
+
* S3-compatible single PUT to a presigned URL — bytes go straight to the
|
|
422
|
+
* bucket. Not resumable (S3 single PUT is atomic), but retried on failure.
|
|
423
|
+
*/
|
|
424
|
+
private s3PutUpload;
|
|
425
|
+
}
|
|
426
|
+
declare function createClient(options: FilloClientOptions): FilloClient;
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* A form whose structure lives in user code. Framework renderers can show it
|
|
430
|
+
* immediately, then sync it into a Fillo workspace when a publishable key is
|
|
431
|
+
* present so responses, uploads, webhooks, and exports work normally.
|
|
432
|
+
*/
|
|
433
|
+
interface CodeForm {
|
|
434
|
+
/** Stable handle, unique in the workspace. */
|
|
435
|
+
id: string;
|
|
436
|
+
schema: FormSchema;
|
|
437
|
+
theme?: FormTheme;
|
|
438
|
+
readonly __formworkCodeForm: true;
|
|
439
|
+
}
|
|
440
|
+
declare function defineForm(def: {
|
|
441
|
+
id: string;
|
|
442
|
+
title?: string;
|
|
443
|
+
description?: string;
|
|
444
|
+
pages: FormPage[];
|
|
445
|
+
settings?: FormSchema["settings"];
|
|
446
|
+
theme?: FormTheme;
|
|
447
|
+
}): CodeForm;
|
|
448
|
+
declare function isCodeForm(form: unknown): form is CodeForm;
|
|
449
|
+
/**
|
|
450
|
+
* Sync a code-defined form into a workspace at most once per session for the
|
|
451
|
+
* same client, handle, schema, and theme.
|
|
452
|
+
*/
|
|
453
|
+
declare function syncCodeForm(client: FilloClient, form: CodeForm): Promise<{
|
|
454
|
+
formId: string;
|
|
455
|
+
slug: string;
|
|
456
|
+
}>;
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Build initial response data from URL query parameters — Tally-style
|
|
460
|
+
* prefilling. Hidden fields read their configured paramName; every other
|
|
461
|
+
* field can be prefilled by its id. Values are coerced per kind and
|
|
462
|
+
* validated against options, so a crafted URL can't inject invalid answers.
|
|
463
|
+
*/
|
|
464
|
+
declare function prefillFromParams(form: FormSchema, params: Record<string, string | undefined>): ResponseData;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Resolve `{{field_id}}` tokens in a string against the current answers —
|
|
468
|
+
* "answer piping". Choice answers resolve to their option label, not the id.
|
|
469
|
+
* Unanswered references become empty.
|
|
470
|
+
*/
|
|
471
|
+
declare function resolveText(text: string, data: ResponseData, form: FormSchema): string;
|
|
472
|
+
/** Return a copy of a block with its visible text fields piped, or the block unchanged. */
|
|
473
|
+
declare function pipeBlock(block: Block, data: ResponseData, form: FormSchema): Block;
|
|
474
|
+
|
|
475
|
+
/** Dependency-free nanoid-style id. Crypto-random where available. */
|
|
476
|
+
declare function createId(size?: number): string;
|
|
477
|
+
|
|
478
|
+
export { BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CustomField, DRAFT_KINDS, type DateField, type DividerBlock, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, FilloClient, type FilloClientOptions, FilloError, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormTheme, type HeadingBlock, type HiddenField, type JsonValue, type LinearScaleField, type MatrixField, type NumberField, type ParagraphBlock, type PublishedForm, type RankingField, type RatingField, type ResponseData, type SchemaValidationResult, type SelectOption, type SignatureField, type SubmitMeta, type SubmitResult, type TextField, type UploadFileOptions, type UploadProgress, type UploadSession, type UploadStatus, type UploadTransport, type ValidationResult, allFields, assembleForm, createBlock, createClient, createEmptyForm, createId, defineForm, formatAnswer, isBlockVisible, isCodeForm, isField, pipeBlock, prefillFromParams, resolveText, syncCodeForm, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields };
|