@fias/arche-sdk 2.20.0 → 2.21.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.
@@ -1,4 +1,4 @@
1
- <!-- fias-sdk-guide-version: 2.20.0 -->
1
+ <!-- fias-sdk-guide-version: 2.21.0 -->
2
2
 
3
3
  # FIAS Plugin Development Guide
4
4
 
@@ -12,9 +12,34 @@ package.json # Dependencies and scripts
12
12
  src/
13
13
  index.tsx # Entry point — must render into #root with <FiasProvider> wrapper
14
14
  App.tsx # Main component
15
+ public/ # Optional — static images, fonts, sounds referenced as '/name.png'
16
+ content/ # Optional — a content pack read with useFiasContent() (lessons, data, media)
15
17
  vite.config.ts # Vite dev server config (port 3100)
16
18
  ```
17
19
 
20
+ ## Where does each kind of data go?
21
+
22
+ Decide by who writes the data and when, before you write the code: moving data to a different home after you publish means a migration.
23
+
24
+ | What it is | Where it goes | What you get |
25
+ | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
+ | Your code and small constants (config, a short word list) | The bundle (`src/`) | One file, max 5 MB uncompressed. Every user downloads all of it each time the plugin opens. |
27
+ | A few static images, fonts or sounds your UI names directly | `public/`, referenced as literal `'/name.png'` strings | Served from a CDN. Binary only (png, jpg, webp, gif, woff, woff2, ogg), top-level files only, 4 MB each, 600 files and 120 MB in total. |
28
+ | Content you write and ship with each version: lessons, articles, levels, reference data, large image or audio sets, fonts | A content pack: `content/` + `useFiasContent()` | Not bundled, so users download only what they open. Reviewed on every publish and read-only at runtime. You pay its storage and bandwidth; reads are free to users. |
29
+ | Images you want to change without republishing code (logos, maps, photos with alt text) | The asset library: `useArcheAssets()` + `fias-dev assets` | Managed from the CLI and refreshed without a code publish. |
30
+ | Records your app writes while it runs: progress, things users create, shared catalogues, leaderboards | `useFiasDataStore()` collections | Queryable, `user` or `shared` scope, write policies, optional semantic search. |
31
+ | Files only your app uses, for one user: drafts, caches, working data | `useFiasStorage()` | Free-form paths, private to the plugin and the user. Not shown in their Vault. |
32
+ | A small piece of UI state that should survive a reload (a setting, the last tab) | `usePersistentState()` | Debounced writes to `useFiasStorage()`. |
33
+ | Images one user shares with the app's other users | `useCommunityAssets()` | Moderated before anyone else sees them. |
34
+ | Documents the user should keep: files your app makes for them, or files they already have | `useVaultDocuments()` / `useVaultUserDocuments()` | In the user's My Data, where they outlive your plugin. Reading files they already had needs their consent. |
35
+
36
+ **Common mistakes:**
37
+
38
+ - **Inlining content in `src/`** — a JSON file of lessons, a level pack, base64 images. It counts against the 5 MB cap, and every user downloads all of it to use a little. Put it in `content/`.
39
+ - **Seeding content you wrote into the DataStore from the plugin.** Every user's session tries to write it, the first writer of a `shared` document owns it, and nothing reviews it. Ship it in `content/`. If it must also be searchable, keep a searchable copy in a `shared` collection, but display the pack's version.
40
+ - **Using the DataStore or `useFiasStorage()` for data that is the same for every user.** That is content.
41
+ - **Building `public/` paths at runtime**, e.g. `` `/music-${name}.ogg` ``. Only literal paths are rewritten to the CDN when you publish, so a path built in code 404s in production. Use a lookup table of literal strings, or move the files to `content/` and read them by name.
42
+
18
43
  ## SDK API Reference
19
44
 
20
45
  All hooks require the app to be wrapped in `<FiasProvider>`:
@@ -479,6 +504,25 @@ generate({
479
504
 
480
505
  Mode values: `'text-to-image'` (baseline), `'image-to-image'` (requires `referenceImage`), `'vector'` (SVG output). Style values: `'illustration'` (stylized / digital-art) or `'photoreal'`.
481
506
 
507
+ **Several reference images, and output resolution.** A model whose `maxReferenceImages` (from `useImageEntities`) is more than one takes them together in `referenceImages` — Nano Banana 2 (`ent_modeldef_nano_banana_2`) takes up to 14. There is no role per image: the model reads them in order, so the prompt says which is which. `resolution` picks one of the model's `supportedResolutions`; higher costs more (`creditsPerImageByResolution`):
508
+
509
+ ```tsx
510
+ generate({
511
+ entityId: 'ent_modeldef_nano_banana_2',
512
+ prompt:
513
+ 'The knight from the first image, standing on a cliff at dawn, painted in the style of the second and third images',
514
+ referenceImages: [
515
+ { fileId: knight.fileId },
516
+ { fileId: styleA.fileId },
517
+ { fileId: styleB.fileId },
518
+ ],
519
+ size: '21:9',
520
+ resolution: '2K', // '512px' | '1K' (default) | '2K' | '4K'
521
+ });
522
+ ```
523
+
524
+ Use `referenceImage` for one picture and `referenceImages` for several — not both. More than the model takes returns `TOO_MANY_REFERENCE_IMAGES`; a resolution it does not list returns `INVALID_RESOLUTION`. Prefer `fileId` / `archeAssetId` over `dataUrl` when sending several: each `dataUrl` counts against the bridge message size. The result's `costCents` is what the call actually cost, markup included.
525
+
482
526
  Use `npx fias-dev entities --detail <entityId>` to check supported sizes, qualities, and styles for each model.
483
527
 
484
528
  #### Full example
@@ -545,14 +589,15 @@ const { entities, isLoading } = useImageEntities({
545
589
  supportsReferenceImage: true,
546
590
  });
547
591
  // entities[i]: { entityId, displayName, provider, modes, supportedSizes,
548
- // supportsReferenceImage, isPlatformRecommended,
549
- // creditsPerImage, estimatedDurationMs, company, strengths,
550
- // providerParams, ... }
592
+ // supportsReferenceImage, maxReferenceImages,
593
+ // supportedResolutions, defaultResolution, isPlatformRecommended,
594
+ // creditsPerImage, creditsPerImageByResolution,
595
+ // estimatedDurationMs, company, strengths, providerParams, ... }
551
596
  ```
552
597
 
553
598
  Each entry is scrubbed — only fields a UI legitimately needs (no provider model strings, no endpoints, no execution config).
554
599
 
555
- `creditsPerImage` is what one image costs the user in credits, markup already included — it matches what the ledger deducts, so it is the figure to show. `estimatedDurationMs` sizes a progress indicator; `company` and `strengths` are model-info copy. All optional: absent means the model has nothing to declare, not that the data is missing.
600
+ `creditsPerImage` is what one image costs the user in credits, markup already included — it matches what the ledger deducts, so it is the figure to show. For a model priced by resolution it is the price at `defaultResolution`, and `creditsPerImageByResolution` has each resolution's; Nano Banana 2 also bills reference images and thinking as tokens on top (a fraction of a credit to a few credits). `estimatedDurationMs` sizes a progress indicator; `company` and `strengths` are model-info copy. All optional: absent means the model has nothing to declare, not that the data is missing.
556
601
 
557
602
  **Tunable engine parameters.** `providerParams` describes what a model exposes for tuning — enough to render a settings panel without hardcoding a thing:
558
603
 
@@ -564,6 +609,8 @@ const seed = entity.providerParams?.find((p) => p.key === 'seed');
564
609
  await generate({ entityId, prompt, providerParams: { seed: 42 } });
565
610
  ```
566
611
 
612
+ Nano Banana 2 exposes `seed`, `temperature` (0–2) and `thinking_level` (`'minimal'` default, or `'high'` — the model plans the picture first: better at layouts and directions like "seen from above", slower, and its thinking tokens are billed).
613
+
567
614
  **The `category` trap.** A param carrying `category: 'quality'` or `'style'` does NOT belong in `providerParams` — send it on the matching top-level field. Providers read those two from the top level only, and an unrecognized `providerParams` key is dropped without an error, so the image generates at the model's default **and you are billed for it anyway**:
568
615
 
569
616
  ```tsx
@@ -877,7 +924,10 @@ const saved = await userDocs.saveContent({
877
924
 
878
925
  ### Sending a document for signature — Document Sign handoff
879
926
 
880
- **Permission:** `navigation:open_arche`.
927
+ **Permission:** `navigation:open_arche` to hand the document over. Reading the
928
+ envelope's status afterwards is a separate act (`docsign:envelopes:read`) —
929
+ your app only ever sees envelopes it started this way, and never their
930
+ document bytes.
881
931
 
882
932
  Hand one of the user's Vault PDFs to Document Sign with recipients pre-filled.
883
933
  They place the signature fields and press Send there — a handoff never sends
@@ -921,7 +971,9 @@ created, which is why the outcome is there.
921
971
  **Treat the result as a pointer, never as a record.** It tells you which
922
972
  envelope to look at; it is not evidence of what happened to it. Match it to an
923
973
  outstanding request by `requestId`, consume it once, and never re-point an
924
- existing association because a `correlationId` matched.
974
+ existing association because a `correlationId` matched. Anything you would act
975
+ on — who signed, what is outstanding — comes from reading the envelope under
976
+ `docsign:envelopes:read`, not from the payload.
925
977
 
926
978
  **A handoff ENDS YOUR APP'S PROCESS. Anything you will need afterwards has to
927
979
  be written down before you call `openArche`.** The host navigates away and
@@ -950,6 +1002,65 @@ local dev. Judging on arrival therefore rejects every genuine reply, not an
950
1002
  occasional one. Hold the result until you know what was outstanding, then
951
1003
  evaluate it.
952
1004
 
1005
+ ### `useDocumentSign()` — Follow an envelope you handed over
1006
+
1007
+ **Permission:** `docsign:envelopes:read`
1008
+
1009
+ The other half of the handoff above. Once the user has pressed Send in
1010
+ Document Sign, this is how your app learns what happened — status, who the
1011
+ recipients are, where each of them has got to, and the audit timeline.
1012
+
1013
+ ```tsx
1014
+ import { useDocumentSign } from '@fias/arche-sdk';
1015
+
1016
+ const sign = useDocumentSign();
1017
+
1018
+ const envelope = await sign.getEnvelope(approval.envelopeId);
1019
+ if (envelope.status === 'completed') markApproved(approval.id);
1020
+
1021
+ // The audit timeline, oldest first.
1022
+ const { events } = await sign.listEvents(approval.envelopeId);
1023
+ ```
1024
+
1025
+ **The contract:**
1026
+
1027
+ - **Only envelopes YOUR app started**, via a `sign_document` handoff. Not one
1028
+ the user created in Document Sign directly, not another app's. Two
1029
+ independent gates, both the user's: they grant access by pressing Send on an
1030
+ envelope you handed over (named to them on the send screen, revocable in
1031
+ Vault → App Access), and the platform separately requires the envelope to
1032
+ record your app as its origin. Either one missing means no read.
1033
+ - **This is where truth comes from.** The `sign_document_result` handoff is a
1034
+ pointer; it rides navigation state, which is forgeable. Write "signed" into
1035
+ an approval record only on the strength of a read.
1036
+ - **No list verb.** Keep the `envelopeId` on your own record and read by id —
1037
+ the same reason the result has to be matched to an outstanding `requestId`
1038
+ and consumed once.
1039
+ - **Drafts are not readable.** Send is the moment the user grants access, so a
1040
+ `saved_draft` result gives you an id to keep, and `getEnvelope` on it rejects
1041
+ until the envelope is actually sent. Do not poll a draft.
1042
+ - **One error for every refusal.** `ENVELOPE_NOT_FOUND_OR_NOT_GRANTED` covers
1043
+ an unknown id, an envelope you did not start, one on a workspace this user
1044
+ cannot act for, a draft, and one whose access the user has removed. Deliberately
1045
+ indistinguishable — a specific "it exists but you cannot see it" would let
1046
+ anyone probe for envelope ids. Treat it as "we can no longer follow this",
1047
+ not as a bug, and stop asking.
1048
+ - **You never see the document.** No bytes, no download URL — and no recipient
1049
+ email addresses either. Name, role, status and timestamps only. If your app
1050
+ supplied the recipients, it already has their addresses.
1051
+ - **The timeline is a projection, not the stored rows.** `type`, `actorKind`,
1052
+ `recipientId`, `createdAt`. Signers' IP addresses and user agents, the event
1053
+ payloads, and the hash-chain columns stay on the platform — they belong to
1054
+ the signing certificate, not to your app.
1055
+ - **There are no webhooks.** Nothing calls you when a recipient signs. Read
1056
+ when your UI needs to know — on mount, on a refresh the user asked for — not
1057
+ on a timer.
1058
+ - Terminal envelopes (`voided`, `expired`, `declined`) stay readable. A
1059
+ terminal outcome is part of the trail you came for; your UI should render it
1060
+ rather than leaving the record stuck on "in progress".
1061
+ - Not available in the builder preview: a preview tenant has no arche row, so
1062
+ it can never be an envelope's origin.
1063
+
953
1064
  ### `useArcheHandoff()` — Receive a document the user asked to open in your app
954
1065
 
955
1066
  **Permission:** none to receive. Reading the document is a separate act (`vault:user-documents:read`, via `useVaultUserDocuments().getBytes`).
@@ -1514,6 +1625,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
1514
1625
  ### Size and File Limits
1515
1626
 
1516
1627
  - **Bundle size:** Max 5 MB, measured **uncompressed** — the raw bytes of the final single HTML file with your JS and CSS inlined. Files in `public/` don't count toward it (they have their own caps: 4 MB per file, 120 MB and 600 files total)
1628
+ - **Content packs:** files in `content/` don't count toward the bundle either. If content (lessons, data, media) is what is pushing you toward the cap, move it there — see "Where does each kind of data go?" above. A pack holds up to 10,000 files and 500 MB (20 MB of text, 2,000 images)
1517
1629
  - **Dependencies:** Max 20, exact semver versions only (e.g., `"4.4.7"`, not `"^4.4.7"`)
1518
1630
  - Platform packages (`react`, `react-dom`, `@fias/arche-sdk`) are provided — do not include in `dependencies`
1519
1631
 
@@ -1523,6 +1635,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
1523
1635
  - `storage_write`: 120/minute
1524
1636
  - `storage_read`: 300/minute
1525
1637
  - `storage_list`, `storage_delete`: 60/minute
1638
+ - Content reads (`useFiasContent()`): 120/minute — read related files together with `getMany` (up to 100 per call)
1526
1639
 
1527
1640
  A separate **runaway-loop detector** fires if any method is called >50 times in 5 s and blocks that method for 10 s. Errors include the target, e.g. `Runaway loop detected: "storage_write" (path: __state/gameState) …` — the path tells you where to debounce at the source.
1528
1641
 
@@ -1612,6 +1725,8 @@ npm run submit
1612
1725
  # Builds, validates, packages, uploads, submits for AI review
1613
1726
  ```
1614
1727
 
1728
+ If the plugin has a `content/` folder, submit uploads it too — only the files that changed — and the review reads the whole pack on every publish. `npx fias-dev content check` validates it without submitting.
1729
+
1615
1730
  The first time you list a plugin in the marketplace there is a one-time
1616
1731
  listing fee; subsequent submissions of the same plugin only pay a small
1617
1732
  per-review fee. The harness fetches the exact amount from the server and