@formancy/server-core 0.4.0 → 0.5.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 +13 -2
- package/dist/index.d.mts +398 -57
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +499 -144
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -27,19 +27,30 @@ server gets argon2id and a CSPRNG.
|
|
|
27
27
|
## Use
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { publishForm, createSubmission, can } from '@formancy/server-core'
|
|
30
|
+
import { publishForm, formToFill, createSubmission, can } from '@formancy/server-core'
|
|
31
31
|
|
|
32
32
|
const published = await publishForm(deps, { path: 'contact-us', schema })
|
|
33
33
|
// publishForm is the save gate: structural validation, then the engine's own
|
|
34
34
|
// compile — a form that could loop is refused here and never persisted.
|
|
35
35
|
|
|
36
|
+
// What a respondent is handed: the current version, and the token its response is
|
|
37
|
+
// sent back with, so that a response sent twice is stored once.
|
|
38
|
+
const form = await formToFill(deps, 'contact-us')
|
|
39
|
+
|
|
36
40
|
const outcome = await createSubmission(deps, {
|
|
37
41
|
path: 'contact-us',
|
|
38
|
-
declaredSchemaHash:
|
|
42
|
+
declaredSchemaHash: form.schemaHash,
|
|
43
|
+
token: form.submissionToken,
|
|
39
44
|
data: payload,
|
|
40
45
|
})
|
|
46
|
+
// The same token again: { ok: false, kind: 'token_spent', id }, and nothing stored.
|
|
41
47
|
```
|
|
42
48
|
|
|
49
|
+
`publishForm`, `keepExamples` and `createSubmission` write their audit row inside
|
|
50
|
+
the transaction of what they record. Give them a `requestId` and the row names
|
|
51
|
+
that request, so it can be read beside a request log — `@formancy/server` passes
|
|
52
|
+
Fastify's request id. Without one, the row names none.
|
|
53
|
+
|
|
43
54
|
Authorization is one readable table (`can(actor, action)`), deliberately not
|
|
44
55
|
Postgres row-level security: RLS couples the application to per-request database
|
|
45
56
|
roles and hides authorization from the test suite.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CHALLENGE_TTL_SECONDS, Challenge, DEFAULT_MAX_NUMBER, Solution, VerifyOutcome, decodeSolution, encodeSolution, hashOf, mintChallenge, solveChallenge, verifySolution } from "@formancy/challenge";
|
|
2
|
+
import { CapabilitySource, Check, Scenario } from "@formancy/core";
|
|
2
3
|
import { Change, FormSchema } from "@formancy/spec";
|
|
3
|
-
import { CapabilitySource, Check } from "@formancy/core";
|
|
4
4
|
import { SchemaError } from "@formancy/spec/validate";
|
|
5
5
|
//#region src/options-membership.d.ts
|
|
6
6
|
/**
|
|
@@ -220,8 +220,37 @@ interface FileRecord {
|
|
|
220
220
|
createdAt: string;
|
|
221
221
|
/** The submission that claimed it, or null. One file, one submission. */
|
|
222
222
|
submissionId: string | null;
|
|
223
|
+
/**
|
|
224
|
+
* Until when one request holds the right to receive this file's bytes, or null when
|
|
225
|
+
* none does. Not a fourth state: the file is still `offered` while it is received, and
|
|
226
|
+
* a lease that has run out is no lease. See `leaseFile`.
|
|
227
|
+
*/
|
|
228
|
+
receivingUntil: string | null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* A form's examples, and the fictional sample every one of them starts from — the pair the
|
|
232
|
+
* starter templates carry as `*.scenarios.json` and `*.sample.json`
|
|
233
|
+
* ([0166](../../../docs/decisions/0166-a-deployment-keeps-a-forms-examples-and-runs-them-at-publish.md)).
|
|
234
|
+
*
|
|
235
|
+
* Beside the form and not inside a version. A published version is immutable
|
|
236
|
+
* ([0025](../../../docs/decisions/0025-immutability-in-the-database.md)), and examples change
|
|
237
|
+
* while the form does not: written into a version, every example added would mint one. One
|
|
238
|
+
* record per form, replaced whole, because the scenario panes hand back the whole list.
|
|
239
|
+
*/
|
|
240
|
+
interface ExamplesRecord {
|
|
241
|
+
formId: string;
|
|
242
|
+
scenarios: readonly Scenario[];
|
|
243
|
+
/** Where every example starts, or null when none was kept. */
|
|
244
|
+
sample: Readonly<Record<string, unknown>> | null;
|
|
245
|
+
updatedAt: string;
|
|
223
246
|
}
|
|
224
247
|
interface Storage {
|
|
248
|
+
/**
|
|
249
|
+
* The form at this path, or `undefined` for a path no form has — one holding a character this
|
|
250
|
+
* storage cannot keep included, such as a NUL in PostgreSQL's `text`. It arrives from a URL, as
|
|
251
|
+
* anybody wrote it, so it is an unknown form and never an error. `getDraft`, `getUserByEmail`
|
|
252
|
+
* and `findApiKeysByPrefix` find none the same way: what they look up arrives from a request too.
|
|
253
|
+
*/
|
|
225
254
|
getFormByPath(path: string): Promise<FormRecord | undefined>;
|
|
226
255
|
listForms(): Promise<FormRecord[]>;
|
|
227
256
|
createForm(record: FormRecord): Promise<void>;
|
|
@@ -252,6 +281,17 @@ interface Storage {
|
|
|
252
281
|
audit?: AuditEntry;
|
|
253
282
|
}): Promise<void>;
|
|
254
283
|
getVersionById(id: string): Promise<FormVersionRecord | undefined>;
|
|
284
|
+
/** A form's examples, or undefined when none were ever kept for it. */
|
|
285
|
+
getExamples(formId: string): Promise<ExamplesRecord | undefined>;
|
|
286
|
+
/**
|
|
287
|
+
* Replace a form's examples and sample, with the audit row, in ONE commit — as a publish
|
|
288
|
+
* carries its own: a change that rolled back must leave nothing saying it happened.
|
|
289
|
+
*
|
|
290
|
+
* The form must exist; the PostgreSQL storage has the database refuse one that does not.
|
|
291
|
+
* Nothing here reads the examples: whether they are examples is the use-case's question,
|
|
292
|
+
* asked before this is called, and again of what `getExamples` hands back.
|
|
293
|
+
*/
|
|
294
|
+
keepExamples(record: ExamplesRecord, audit?: AuditEntry): Promise<void>;
|
|
255
295
|
findVersionByHash(formId: string, schemaHash: string): Promise<FormVersionRecord | undefined>;
|
|
256
296
|
latestVersionNumber(formId: string): Promise<number>;
|
|
257
297
|
/**
|
|
@@ -269,8 +309,23 @@ interface Storage {
|
|
|
269
309
|
* prevents — a webhook fired for a rolled-back submission, a stored
|
|
270
310
|
* submission nothing was sent about, an accepted submission whose files the
|
|
271
311
|
* collector deletes — are the ones a self-hoster cannot debug.
|
|
312
|
+
*
|
|
313
|
+
* Returns false, and writes NOTHING — no delivery, no claim, no audit row — when a
|
|
314
|
+
* submission with `record.id` is already stored. The id is the one a response's token
|
|
315
|
+
* names, so that is the same response sent twice
|
|
316
|
+
* ([0169](../../../docs/decisions/0169-a-response-is-stored-once.md)). ATOMIC, as
|
|
317
|
+
* `spendChallenge` is and for its reason: two sends of one response both pass every check
|
|
318
|
+
* made before this, and only the storage can decide which is stored.
|
|
319
|
+
*/
|
|
320
|
+
insertSubmission(record: SubmissionRecord, deliveries?: readonly DeliveryRecord[], claimFileIds?: readonly string[], audit?: AuditEntry): Promise<boolean>;
|
|
321
|
+
/**
|
|
322
|
+
* Whether a submission with this id is stored.
|
|
323
|
+
*
|
|
324
|
+
* Asked before the replay, so a response sent again is told it was sent — rather than that
|
|
325
|
+
* the files it names are unknown, which is what the claim would say of files the first send
|
|
326
|
+
* already took. `insertSubmission` still decides: two sends at once both find nothing here.
|
|
272
327
|
*/
|
|
273
|
-
|
|
328
|
+
hasSubmission(id: string): Promise<boolean>;
|
|
274
329
|
/**
|
|
275
330
|
* Append one audit row.
|
|
276
331
|
*
|
|
@@ -305,8 +360,42 @@ interface Storage {
|
|
|
305
360
|
/** Drop spent challenges that can no longer be replayed anyway. */
|
|
306
361
|
forgetExpiredChallenges(beforeIso: string): Promise<number>;
|
|
307
362
|
insertFile(record: FileRecord): Promise<void>;
|
|
363
|
+
/**
|
|
364
|
+
* The file with this id, or `undefined` for an id no file has — one in no shape this storage's
|
|
365
|
+
* ids take included. It arrives from a URL and from a response, as anybody wrote it, so it is an
|
|
366
|
+
* unknown file and never an error.
|
|
367
|
+
*/
|
|
308
368
|
getFile(id: string): Promise<FileRecord | undefined>;
|
|
309
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Take the right to receive an `offered` file's bytes, until `untilIso`.
|
|
371
|
+
*
|
|
372
|
+
* Returns false when the file is not `offered`, or another request holds a lease that
|
|
373
|
+
* has not run out at `nowIso`. ATOMIC, as `spendChallenge` is and for its reason: two
|
|
374
|
+
* requests carrying bytes for one file both pass every check made before this, and only
|
|
375
|
+
* the storage can decide which of them receives. A lease that reads and then writes hands
|
|
376
|
+
* the file to both, and the second writes its bytes over the first's.
|
|
377
|
+
*
|
|
378
|
+
* `untilIso` is also the lease's name, handed back to `settleFile` and `releaseFile`. No
|
|
379
|
+
* two requests can hold one at once: a lease is taken only once the last has run out,
|
|
380
|
+
* so the next always runs out later.
|
|
381
|
+
*/
|
|
382
|
+
leaseFile(id: string, nowIso: string, untilIso: string): Promise<boolean>;
|
|
383
|
+
/**
|
|
384
|
+
* The bytes are kept: `stored`, and the lease cleared — only while the file is `offered`
|
|
385
|
+
* and the lease named `untilIso` still holds it. Returns false and changes nothing
|
|
386
|
+
* otherwise.
|
|
387
|
+
*
|
|
388
|
+
* It sets the state and nothing else. Its predecessor wrote back the whole record the
|
|
389
|
+
* request had read before its scan, so a request that outlasted a faster one put a
|
|
390
|
+
* claimed file back to `stored` with no submission, for the collector to delete.
|
|
391
|
+
*/
|
|
392
|
+
settleFile(id: string, untilIso: string): Promise<boolean>;
|
|
393
|
+
/**
|
|
394
|
+
* Give the lease back without keeping anything, so the retry a refusal invites is not
|
|
395
|
+
* refused as busy. Only the lease named `untilIso`: a request that outlasted its own
|
|
396
|
+
* must not free the one that took over.
|
|
397
|
+
*/
|
|
398
|
+
releaseFile(id: string, untilIso: string): Promise<boolean>;
|
|
310
399
|
/** Every file in a state other than `claimed`, created before `beforeIso`. */
|
|
311
400
|
abandonedFiles(beforeIso: string): Promise<FileRecord[]>;
|
|
312
401
|
deleteFiles(ids: readonly string[]): Promise<void>;
|
|
@@ -323,6 +412,7 @@ interface Storage {
|
|
|
323
412
|
* a table nobody looks at, which is the same as not having it.
|
|
324
413
|
*/
|
|
325
414
|
deadDeliveries(limit: number): Promise<DeliveryRecord[]>;
|
|
415
|
+
/** As `getFile`: `undefined` for an id no delivery has, whatever its shape, since it arrives from a URL. */
|
|
326
416
|
getDelivery(id: string): Promise<DeliveryRecord | undefined>;
|
|
327
417
|
/** Oldest first, only those due. */
|
|
328
418
|
claimDueDeliveries(nowIso: string, limit: number): Promise<DeliveryRecord[]>;
|
|
@@ -438,7 +528,7 @@ export declare function can(actor: Actor, action: Action): boolean;
|
|
|
438
528
|
* Closed so a new route cannot invent its own spelling: an audit log where one event is
|
|
439
529
|
* called two things is one nobody can query.
|
|
440
530
|
*/
|
|
441
|
-
export declare const AUDIT_ACTIONS: readonly ["auth.login", "auth.login.failed", "form.published", "form.access.changed", "submission.created", "submission.read", "submission.exported", "file.downloaded", "user.created", "apiKey.created", "apiKey.revoked", "delivery.replayed"];
|
|
531
|
+
export declare const AUDIT_ACTIONS: readonly ["auth.login", "auth.login.failed", "form.published", "form.access.changed", "form.examples.changed", "submission.created", "submission.read", "submission.exported", "file.downloaded", "user.created", "apiKey.created", "apiKey.revoked", "delivery.replayed", "model.asked"];
|
|
442
532
|
type AuditAction = (typeof AUDIT_ACTIONS)[number];
|
|
443
533
|
interface AuditEntry {
|
|
444
534
|
readonly id: string;
|
|
@@ -458,7 +548,9 @@ interface AuditEntry {
|
|
|
458
548
|
readonly subject?: string;
|
|
459
549
|
/**
|
|
460
550
|
* The request it happened in, so an export and the rows it returned can be
|
|
461
|
-
* tied together later
|
|
551
|
+
* tied together later — and a row to the line a server's request log wrote
|
|
552
|
+
* for that request. A use-case that writes its row inside its transaction
|
|
553
|
+
* takes the id in its input, and names it when it is given one.
|
|
462
554
|
*/
|
|
463
555
|
readonly requestId?: string;
|
|
464
556
|
/** Identifiers and counts. Never a submission's answers. */
|
|
@@ -775,13 +867,16 @@ interface ServerDeps {
|
|
|
775
867
|
/** The clock/randomness the ENGINE sees during replay. */
|
|
776
868
|
capabilities: CapabilitySource;
|
|
777
869
|
/**
|
|
778
|
-
* Signs the
|
|
870
|
+
* Signs the keys the public plane hands out: the one that proves somebody started a
|
|
871
|
+
* draft, and the token a response is sent with so that it is stored once (0169).
|
|
779
872
|
*
|
|
780
873
|
* The public plane is anonymous, so a draft has no account behind it — which
|
|
781
874
|
* is exactly why it needs a secret of its own. The host's own signing key is
|
|
782
875
|
* reused rather than a second one being configured: a draft token is a
|
|
783
876
|
* server-signed bearer token, which is what that key is already for, and an
|
|
784
877
|
* optional secret would mean drafts are unprotected whenever nobody set it.
|
|
878
|
+
* Named for the first of the two; renaming it would break every host that builds
|
|
879
|
+
* these deps, for a word.
|
|
785
880
|
*/
|
|
786
881
|
draftSecret: string;
|
|
787
882
|
/**
|
|
@@ -822,48 +917,6 @@ interface ResolvedForm {
|
|
|
822
917
|
schemaHash: string;
|
|
823
918
|
}
|
|
824
919
|
export declare function resolveForm(deps: ServerDeps, path: string): Promise<ResolvedForm | undefined>;
|
|
825
|
-
type SubmissionOutcome = {
|
|
826
|
-
ok: true;
|
|
827
|
-
id: string;
|
|
828
|
-
canonicalData: unknown;
|
|
829
|
-
} | {
|
|
830
|
-
ok: false;
|
|
831
|
-
kind: 'unknown_form';
|
|
832
|
-
} | {
|
|
833
|
-
ok: false;
|
|
834
|
-
kind: 'version_changed';
|
|
835
|
-
current?: ResolvedForm;
|
|
836
|
-
} | {
|
|
837
|
-
ok: false;
|
|
838
|
-
kind: 'invalid';
|
|
839
|
-
errors: Record<string, string[]>;
|
|
840
|
-
} |
|
|
841
|
-
/** The caller may not submit this form: not public, or not from this origin. */
|
|
842
|
-
{
|
|
843
|
-
ok: false;
|
|
844
|
-
kind: 'forbidden';
|
|
845
|
-
} |
|
|
846
|
-
/**
|
|
847
|
-
* A source that should have vouched for an answer could not.
|
|
848
|
-
*
|
|
849
|
-
* Failed closed and never accepted unchecked: a bogus value that is stored is
|
|
850
|
-
* undetectable afterwards, while a refusal is retryable and the draft still holds
|
|
851
|
-
* the answers.
|
|
852
|
-
*/
|
|
853
|
-
{
|
|
854
|
-
ok: false;
|
|
855
|
-
kind: 'source_unavailable';
|
|
856
|
-
source: string;
|
|
857
|
-
};
|
|
858
|
-
/**
|
|
859
|
-
* Accept one submission: resolve the version the client says it rendered,
|
|
860
|
-
* replay the engine over the submitted data, and store the CANONICAL result.
|
|
861
|
-
*
|
|
862
|
-
* The replay is the whole point. Computed values are recomputed and overwrite
|
|
863
|
-
* whatever arrived; visibility is evaluated server-side and hidden branches
|
|
864
|
-
* are stripped, so a client cannot smuggle data by lying about what was shown.
|
|
865
|
-
* Client validation is UX; this is truth.
|
|
866
|
-
*/
|
|
867
920
|
/**
|
|
868
921
|
* Change who may submit a form, and from where.
|
|
869
922
|
*
|
|
@@ -878,14 +931,6 @@ export declare function setFormAccess(deps: ServerDeps, input: {
|
|
|
878
931
|
}): Promise<{
|
|
879
932
|
ok: boolean;
|
|
880
933
|
}>;
|
|
881
|
-
export declare function createSubmission(deps: ServerDeps, input: {
|
|
882
|
-
path: string;
|
|
883
|
-
declaredSchemaHash: string;
|
|
884
|
-
data: unknown;
|
|
885
|
-
/** Defaults to anonymous: the caller must prove otherwise, not the reverse. */
|
|
886
|
-
actor?: 'anonymous' | 'authenticated';
|
|
887
|
-
origin?: string;
|
|
888
|
-
}): Promise<SubmissionOutcome>;
|
|
889
934
|
interface ListedSubmission {
|
|
890
935
|
id: string;
|
|
891
936
|
version: number;
|
|
@@ -913,13 +958,21 @@ export declare function exportCsv(deps: ServerDeps, path: string): Promise<strin
|
|
|
913
958
|
* The id is the server's because an id a caller supplies is an id a caller can
|
|
914
959
|
* enumerate — and enumerating them was enough to read other people's
|
|
915
960
|
* part-filled answers.
|
|
961
|
+
*
|
|
962
|
+
* A draft is one response, so it comes with the token that response is sent with, naming the
|
|
963
|
+
* draft's own id ([0169](../../../docs/decisions/0169-a-response-is-stored-once.md)). Resuming
|
|
964
|
+
* hands back the same one, so a response whose answer was lost and is sent again after a reload
|
|
965
|
+
* is refused as already sent rather than stored twice.
|
|
916
966
|
*/
|
|
917
967
|
export declare function startDraft(deps: ServerDeps, input: {
|
|
918
968
|
path: string;
|
|
919
969
|
}): Promise<{
|
|
920
970
|
draftId: string;
|
|
921
971
|
token: string;
|
|
972
|
+
submissionToken: string;
|
|
922
973
|
} | undefined>;
|
|
974
|
+
/** Save (or re-save) a partial form, bound to the CURRENT version. Undefined
|
|
975
|
+
* for an unknown form. */
|
|
923
976
|
export declare function saveDraft(deps: ServerDeps, input: {
|
|
924
977
|
path: string;
|
|
925
978
|
draftId: string;
|
|
@@ -941,6 +994,8 @@ type ResumeOutcome = {
|
|
|
941
994
|
data: unknown;
|
|
942
995
|
/** Present when the rebind lost something; absent for a silent rebind. */
|
|
943
996
|
migration?: DraftMigration;
|
|
997
|
+
/** What the response is sent with: the same on every resume, naming the draft's id. */
|
|
998
|
+
submissionToken: string;
|
|
944
999
|
} | {
|
|
945
1000
|
/** The schema changed in a way no automatic rebind survives: the draft
|
|
946
1001
|
* comes back against ITS OWN version, and the caller offers a restart. */
|
|
@@ -983,6 +1038,110 @@ interface ListedVersion {
|
|
|
983
1038
|
* view rendering fifty full schemas would be its own denial of service. */
|
|
984
1039
|
export declare function listVersions(deps: ServerDeps, path: string): Promise<ListedVersion[] | undefined>;
|
|
985
1040
|
//#endregion
|
|
1041
|
+
//#region src/submitting.d.ts
|
|
1042
|
+
/**
|
|
1043
|
+
* Submitting a form: what a respondent is handed, the replay, and what is stored.
|
|
1044
|
+
*
|
|
1045
|
+
* Its own file because `use-cases.ts` named it as the next family to leave when its size
|
|
1046
|
+
* budget refused growth, and the submission is what grew.
|
|
1047
|
+
*/
|
|
1048
|
+
type SubmissionOutcome = {
|
|
1049
|
+
ok: true;
|
|
1050
|
+
id: string;
|
|
1051
|
+
canonicalData: unknown;
|
|
1052
|
+
} | {
|
|
1053
|
+
ok: false;
|
|
1054
|
+
kind: 'unknown_form';
|
|
1055
|
+
} | {
|
|
1056
|
+
ok: false;
|
|
1057
|
+
kind: 'version_changed';
|
|
1058
|
+
current?: ResolvedForm;
|
|
1059
|
+
} | {
|
|
1060
|
+
ok: false;
|
|
1061
|
+
kind: 'invalid';
|
|
1062
|
+
errors: Record<string, string[]>;
|
|
1063
|
+
} |
|
|
1064
|
+
/** The caller may not submit this form: not public, or not from this origin. */
|
|
1065
|
+
{
|
|
1066
|
+
ok: false;
|
|
1067
|
+
kind: 'forbidden';
|
|
1068
|
+
} |
|
|
1069
|
+
/**
|
|
1070
|
+
* A source that should have vouched for an answer could not.
|
|
1071
|
+
*
|
|
1072
|
+
* Failed closed and never accepted unchecked: a bogus value that is stored is
|
|
1073
|
+
* undetectable afterwards, while a refusal is retryable and the draft still holds
|
|
1074
|
+
* the answers.
|
|
1075
|
+
*/
|
|
1076
|
+
{
|
|
1077
|
+
ok: false;
|
|
1078
|
+
kind: 'source_unavailable';
|
|
1079
|
+
source: string;
|
|
1080
|
+
} |
|
|
1081
|
+
/** An anonymous response arrived without the token its form was handed out with. */
|
|
1082
|
+
{
|
|
1083
|
+
ok: false;
|
|
1084
|
+
kind: 'token_required';
|
|
1085
|
+
} |
|
|
1086
|
+
/**
|
|
1087
|
+
* A token this server did not sign for this form: forged, another form's, or signed under
|
|
1088
|
+
* a key the deployment no longer has. Loading the form again hands out a good one.
|
|
1089
|
+
*/
|
|
1090
|
+
{
|
|
1091
|
+
ok: false;
|
|
1092
|
+
kind: 'token_invalid';
|
|
1093
|
+
} |
|
|
1094
|
+
/** The response this token names is already stored, as `id`; nothing was stored again. */
|
|
1095
|
+
{
|
|
1096
|
+
ok: false;
|
|
1097
|
+
kind: 'token_spent';
|
|
1098
|
+
id: string;
|
|
1099
|
+
};
|
|
1100
|
+
/** A published form as a respondent is handed it: its current version, and what to send it with. */
|
|
1101
|
+
interface FormToFill extends ResolvedForm {
|
|
1102
|
+
/** Names the id the response will be stored under, signed for this form (0169). */
|
|
1103
|
+
submissionToken: string;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* The form a respondent fills in, with the token their response is sent with.
|
|
1107
|
+
*
|
|
1108
|
+
* A fresh id on every call, because every reading is a response that might be sent: two
|
|
1109
|
+
* people handed one token would find the second refused as already sent. Minting writes
|
|
1110
|
+
* nothing — an HMAC over the form and the id — so handing one to everybody who reads the form
|
|
1111
|
+
* costs this server a hash and fills no table
|
|
1112
|
+
* ([0169](../../../docs/decisions/0169-a-response-is-stored-once.md)).
|
|
1113
|
+
*/
|
|
1114
|
+
export declare function formToFill(deps: ServerDeps, path: string): Promise<FormToFill | undefined>;
|
|
1115
|
+
/**
|
|
1116
|
+
* Accept one submission: resolve the version the client says it rendered,
|
|
1117
|
+
* replay the engine over the submitted data, and store the CANONICAL result.
|
|
1118
|
+
*
|
|
1119
|
+
* The replay is the whole point. Computed values are recomputed and overwrite
|
|
1120
|
+
* whatever arrived; visibility is evaluated server-side and hidden branches
|
|
1121
|
+
* are stripped, so a client cannot smuggle data by lying about what was shown.
|
|
1122
|
+
* Client validation is UX; this is truth.
|
|
1123
|
+
*
|
|
1124
|
+
* And it is stored once. A response is stored under the id its token names, and the storage
|
|
1125
|
+
* refuses that id twice, so the same response sent again — a retry after an answer that never
|
|
1126
|
+
* arrived, a replay — is told it was sent rather than stored as somebody else
|
|
1127
|
+
* ([0169](../../../docs/decisions/0169-a-response-is-stored-once.md)).
|
|
1128
|
+
*/
|
|
1129
|
+
export declare function createSubmission(deps: ServerDeps, input: {
|
|
1130
|
+
path: string;
|
|
1131
|
+
declaredSchemaHash: string;
|
|
1132
|
+
data: unknown;
|
|
1133
|
+
/** Defaults to anonymous: the caller must prove otherwise, not the reverse. */
|
|
1134
|
+
actor?: 'anonymous' | 'authenticated';
|
|
1135
|
+
origin?: string;
|
|
1136
|
+
requestId?: string;
|
|
1137
|
+
/**
|
|
1138
|
+
* The token the form was handed out with. Required of an anonymous response. A signed-in
|
|
1139
|
+
* one may leave it out and is stored under a fresh id, as before; one it sends is checked
|
|
1140
|
+
* and spent like any other, because a token that is ignored when it is wrong is decoration.
|
|
1141
|
+
*/
|
|
1142
|
+
token?: string;
|
|
1143
|
+
}): Promise<SubmissionOutcome>;
|
|
1144
|
+
//#endregion
|
|
986
1145
|
//#region src/redos.d.ts
|
|
987
1146
|
/**
|
|
988
1147
|
* Refuse a form whose `pattern` values can be made to backtrack.
|
|
@@ -1221,7 +1380,189 @@ export declare function publishForm(deps: ServerDeps, input: {
|
|
|
1221
1380
|
* asks to be told it has been overtaken.
|
|
1222
1381
|
*/
|
|
1223
1382
|
basedOnSchemaHash?: string;
|
|
1383
|
+
/** The request this publish is answering, which its audit row names (`AuditEntry.requestId`). */
|
|
1384
|
+
requestId?: string;
|
|
1224
1385
|
}): Promise<PublishOutcome>;
|
|
1225
1386
|
//#endregion
|
|
1226
|
-
|
|
1387
|
+
//#region src/examples.d.ts
|
|
1388
|
+
/**
|
|
1389
|
+
* A form's examples, kept by the deployment beside the form, and run when it is published
|
|
1390
|
+
* ([0166](../../../docs/decisions/0166-a-deployment-keeps-a-forms-examples-and-runs-them-at-publish.md)).
|
|
1391
|
+
*
|
|
1392
|
+
* An example with its answer written down is the one check that tells a condition that
|
|
1393
|
+
* compiles from the condition that was asked for
|
|
1394
|
+
* ([0110](../../../docs/decisions/0110-a-form-is-checked-against-examples.md)). The builders
|
|
1395
|
+
* run them, and the scenarios are the host's to keep
|
|
1396
|
+
* ([0111](../../../docs/decisions/0111-a-scenario-panel-names-what-stopped-holding.md)); on a
|
|
1397
|
+
* deployment the host is this server, so it keeps them, with the fictional sample they start
|
|
1398
|
+
* from, and publishing — the last moment before a version is frozen — runs them.
|
|
1399
|
+
*
|
|
1400
|
+
* **The permission is decided here, not in the route.** Reading and changing a form's
|
|
1401
|
+
* examples take what editing the form takes, `form.publish`, and a host composing its own
|
|
1402
|
+
* HTTP over this package gets that rule with the use-case rather than having to remember it.
|
|
1403
|
+
*
|
|
1404
|
+
* **What is kept is read as what is sent is read**, by `readList`, on the way in and on the
|
|
1405
|
+
* way out. A list sent to be kept with anything in it that is not an example keeps nothing; a
|
|
1406
|
+
* row already kept that is not one — edited around `keepExamples` — is left out and named
|
|
1407
|
+
* wherever the examples are read back, so the admin that draws them and the publish that runs
|
|
1408
|
+
* them are only ever handed examples.
|
|
1409
|
+
*/
|
|
1410
|
+
/** A form's examples as a builder holds them: the list, and where every one starts. */
|
|
1411
|
+
interface FormExamples {
|
|
1412
|
+
readonly scenarios: readonly Scenario[];
|
|
1413
|
+
/** Absent when none was kept: every example then starts from an empty form. */
|
|
1414
|
+
readonly sample?: Readonly<Record<string, unknown>>;
|
|
1415
|
+
/**
|
|
1416
|
+
* When they are read back: what is kept and is not an example, or not a sample, one sentence
|
|
1417
|
+
* each. Left out of the above, so nothing draws or runs it, and gone at the next save, which
|
|
1418
|
+
* keeps only what it is sent. Absent when everything kept could be read.
|
|
1419
|
+
*/
|
|
1420
|
+
readonly unreadable?: readonly string[];
|
|
1421
|
+
}
|
|
1422
|
+
type ExamplesOutcome = {
|
|
1423
|
+
readonly ok: true;
|
|
1424
|
+
readonly examples: FormExamples;
|
|
1425
|
+
} |
|
|
1426
|
+
/** Not with this actor's role: a viewer may read the form, and not what it is checked against. */
|
|
1427
|
+
{
|
|
1428
|
+
readonly ok: false;
|
|
1429
|
+
readonly kind: 'forbidden';
|
|
1430
|
+
} |
|
|
1431
|
+
/** Examples are kept beside a form, so there must be one at this path. */
|
|
1432
|
+
{
|
|
1433
|
+
readonly ok: false;
|
|
1434
|
+
readonly kind: 'unknown_form';
|
|
1435
|
+
};
|
|
1436
|
+
type KeepExamplesOutcome = ExamplesOutcome |
|
|
1437
|
+
/** Something sent is not an example, or not a sample: one sentence each, and nothing kept. */
|
|
1438
|
+
{
|
|
1439
|
+
readonly ok: false;
|
|
1440
|
+
readonly kind: 'invalid_examples';
|
|
1441
|
+
readonly problems: readonly string[];
|
|
1442
|
+
};
|
|
1443
|
+
/** The examples kept for the form at `path`; none, for a form nobody wrote any for. */
|
|
1444
|
+
export declare function readExamples(deps: ServerDeps, input: {
|
|
1445
|
+
path: string;
|
|
1446
|
+
actor: Actor;
|
|
1447
|
+
}): Promise<ExamplesOutcome>;
|
|
1448
|
+
/**
|
|
1449
|
+
* Replace the examples of the form at `path`, and the sample they start from.
|
|
1450
|
+
*
|
|
1451
|
+
* Each is read as an example the way a builder reads a model's draft — `readScenario`, one
|
|
1452
|
+
* decision for both — and a list with any item that is not one keeps nothing. Paths are not
|
|
1453
|
+
* checked against the published form: an example is written for the form being edited, which
|
|
1454
|
+
* may name a field no version has yet, and the runner says so when it runs.
|
|
1455
|
+
*/
|
|
1456
|
+
export declare function keepExamples(deps: ServerDeps,
|
|
1457
|
+
/** `requestId` is the request this change answers, which its audit row names. */
|
|
1458
|
+
input: {
|
|
1459
|
+
path: string;
|
|
1460
|
+
actor: Actor;
|
|
1461
|
+
examples: unknown;
|
|
1462
|
+
requestId?: string;
|
|
1463
|
+
}): Promise<KeepExamplesOutcome>;
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region src/model.d.ts
|
|
1466
|
+
/**
|
|
1467
|
+
* The deployment's own model, asked on a builder's behalf.
|
|
1468
|
+
*
|
|
1469
|
+
* The builders ask a model through an `AskModel` the host supplies, and a key in a browser
|
|
1470
|
+
* is a key anybody who opens the page can read. So a deployment that has a model keeps it
|
|
1471
|
+
* here, behind its own sessions, and the admin's `AskModel` is a request to this server
|
|
1472
|
+
* ([0165](../../../docs/decisions/0165-a-deployments-model-is-asked-through-its-server.md)).
|
|
1473
|
+
*
|
|
1474
|
+
* **The server writes the briefing.** The browser names the kind of request — one of the
|
|
1475
|
+
* three formancy makes — and sends the user part; the system part is the one
|
|
1476
|
+
* `@formancy/builder-core` briefs that kind with, and one in the request is never read, so
|
|
1477
|
+
* every request the operator's key pays for is asked under one of formancy's briefings.
|
|
1478
|
+
* That narrows the endpoint; it does not close it. The user part is still the person's
|
|
1479
|
+
* text, and nothing here stops it asking the model for something else under formancy's
|
|
1480
|
+
* briefing: the permission, the rate limit, the size cap and the output limit bound what
|
|
1481
|
+
* that costs, and the audit log says who asked.
|
|
1482
|
+
*/
|
|
1483
|
+
/** Everything a completion is asked: one system part and one user part, no conversation. */
|
|
1484
|
+
interface CompletionPrompt {
|
|
1485
|
+
readonly system: string;
|
|
1486
|
+
readonly user: string;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Told when whoever asked has gone, so the request is abandoned at the provider rather than
|
|
1490
|
+
* paid for to the end.
|
|
1491
|
+
*
|
|
1492
|
+
* A callback rather than an `AbortSignal`, which this package's types do not have
|
|
1493
|
+
* ([0008](../../../docs/decisions/0008-layered-packages.md)) — the shape builder-core's
|
|
1494
|
+
* `AskTurn` has for the same reason. An adapter wires it in one line:
|
|
1495
|
+
* `const c = new AbortController(); cancellation.onCancel(() => c.abort())`.
|
|
1496
|
+
*/
|
|
1497
|
+
interface Cancellation {
|
|
1498
|
+
/** Called at most once: when the asker goes, or at once if it already has. */
|
|
1499
|
+
onCancel(listener: () => void): void;
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* What one completion came to. Only the first is an answer.
|
|
1503
|
+
*
|
|
1504
|
+
* - `refused`: the provider declined in its response rather than in its text. `reason` is
|
|
1505
|
+
* for the person, and ends their run as a decline
|
|
1506
|
+
* ([0158](../../../docs/decisions/0158-a-model-may-decline.md)).
|
|
1507
|
+
* - `truncated`: the answer reached the output limit. Cut off, it is not an answer, and
|
|
1508
|
+
* is never handed over as half a document.
|
|
1509
|
+
* - `unavailable`: not reached, or it answered with an error. `status` is the provider's
|
|
1510
|
+
* HTTP status when it gave one; `cause` is for the operator's log, never the client.
|
|
1511
|
+
* - `cancelled`: the asker went away and the request was abandoned.
|
|
1512
|
+
*/
|
|
1513
|
+
type Completion = {
|
|
1514
|
+
readonly ok: true;
|
|
1515
|
+
readonly text: string;
|
|
1516
|
+
} | {
|
|
1517
|
+
readonly ok: false;
|
|
1518
|
+
readonly failure: 'refused';
|
|
1519
|
+
readonly reason: string;
|
|
1520
|
+
} | {
|
|
1521
|
+
readonly ok: false;
|
|
1522
|
+
readonly failure: 'truncated';
|
|
1523
|
+
} | {
|
|
1524
|
+
readonly ok: false;
|
|
1525
|
+
readonly failure: 'unavailable';
|
|
1526
|
+
readonly status?: number;
|
|
1527
|
+
readonly cause: string;
|
|
1528
|
+
} | {
|
|
1529
|
+
readonly ok: false;
|
|
1530
|
+
readonly failure: 'cancelled';
|
|
1531
|
+
};
|
|
1532
|
+
/**
|
|
1533
|
+
* A model, asked to complete one prompt.
|
|
1534
|
+
*
|
|
1535
|
+
* A port because what is behind it is genuinely swappable: the deployment chooses Anthropic,
|
|
1536
|
+
* OpenAI or xAI, `@formancy/server` has an adapter for each, and the tests drive the route
|
|
1537
|
+
* through doubles. **It does not throw for a provider's failure**; it says which failure.
|
|
1538
|
+
*/
|
|
1539
|
+
interface Completer {
|
|
1540
|
+
complete(prompt: CompletionPrompt, cancellation: Cancellation): Promise<Completion>;
|
|
1541
|
+
}
|
|
1542
|
+
/** A builder's request as the browser sends it, unread. */
|
|
1543
|
+
interface BuilderRequest {
|
|
1544
|
+
readonly kind?: unknown;
|
|
1545
|
+
readonly user?: unknown;
|
|
1546
|
+
}
|
|
1547
|
+
type BuilderRequestOutcome = Completion |
|
|
1548
|
+
/** Not one of the requests formancy makes, so there is no briefing to ask it under. */
|
|
1549
|
+
{
|
|
1550
|
+
readonly ok: false;
|
|
1551
|
+
readonly failure: 'unknown_kind';
|
|
1552
|
+
} |
|
|
1553
|
+
/** No text to ask about: the briefing alone, which no run sends. */
|
|
1554
|
+
{
|
|
1555
|
+
readonly ok: false;
|
|
1556
|
+
readonly failure: 'no_user';
|
|
1557
|
+
};
|
|
1558
|
+
/**
|
|
1559
|
+
* Ask the deployment's model a builder's request, under the briefing for its kind.
|
|
1560
|
+
*
|
|
1561
|
+
* Whatever else the request carries is not read — a `system` above all. Resolves on every
|
|
1562
|
+
* ending: an adapter that throws broke its contract, and is still a model that could not
|
|
1563
|
+
* be asked.
|
|
1564
|
+
*/
|
|
1565
|
+
export declare function completeBuilderRequest(completer: Completer, request: BuilderRequest, cancellation: Cancellation): Promise<BuilderRequestOutcome>;
|
|
1566
|
+
//#endregion
|
|
1567
|
+
export { type Action, type Actor, type AttemptOutcome, type AuditAction, type AuditDraft, type AuditEntry, type AuthDeps, type AuthOutcome, type BreakerState, type BuilderRequest, type BuilderRequestOutcome, CHALLENGE_TTL_SECONDS, type Cancellation, type Challenge, type ClaimOutcome, type Completer, type Completion, type CompletionPrompt, DEFAULT_MAX_NUMBER, type DeliveryHeaders, type DeliveryRecord, type DraftMigration, type DraftRecord, type ExamplesOutcome, type ExamplesRecord, type FileRecord, type FormExamples, type FormRecord, type FormToFill, type FormVersionRecord, type KeepExamplesOutcome, type ListedForm, type ListedSubmission, type ListedVersion, type MembershipOutcome, type OfferDeps, type OfferInput, type OfferOutcome, type OutboxDeps, type PublishOutcome, type ReplayOutcome, type ResolvedForm, type ResumeOutcome, type Role, type ScanVerdict, type Scanner, type ScreenOutcome, type ServerDeps, type ServerOptionsSource, type ServerOptionsSources, type Solution, type Storage, type SubmissionOutcome, type SubmissionRecord, type UnsafePattern, type VerifyOutcome, type WebhookHealth, type WebhookRecord, decodeSolution, encodeSolution, hashOf, mintChallenge, solveChallenge, verifySolution };
|
|
1227
1568
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/options-membership.ts","../src/ports.ts","../src/auth.ts","../src/audit.ts","../src/outbox.ts","../src/breaker.ts","../src/uploads.ts","../src/deps.ts","../src/use-cases.ts","../src/redos.ts","../src/testing/memory-storage.ts","../src/address.ts","../src/webhook.ts","../src/publishing.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;UAoBiB;;;;;;;;;;;;EAYf,SAAS,4BAA4B;;KAG3B,uBAAuB,SAAS,eAAe;;;;;;;;;wBAU3C,eACd,QAAQ,YACR,iBACC;EAAgB;EAAc;EAAgB;;;KAiDrC;EACN;;;;EAEA;EAAW;EAAiB,QAAQ;;;;;;;;;;EAQpC;EAAW;EAA4B;;;;;;;;wBAQvB,gBACpB,QAAQ,YACR,gBACA,SAAS,mCACR,QAAQ;;;;;;wBA8CK,cAAc,QAAQ;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/options-membership.ts","../src/ports.ts","../src/auth.ts","../src/audit.ts","../src/outbox.ts","../src/breaker.ts","../src/uploads.ts","../src/deps.ts","../src/use-cases.ts","../src/submitting.ts","../src/redos.ts","../src/testing/memory-storage.ts","../src/address.ts","../src/webhook.ts","../src/publishing.ts","../src/examples.ts","../src/model.ts"],"mappings":"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"}
|