@fias/arche-sdk 2.19.2 → 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.
- package/dist/entity-ops.d.ts +7 -1
- package/dist/entity-ops.d.ts.map +1 -1
- package/dist/entity-ops.js +7 -1
- package/dist/entity-ops.js.map +1 -1
- package/dist/generated/permissions.d.ts +1 -1
- package/dist/generated/permissions.d.ts.map +1 -1
- package/dist/generated/permissions.js +17 -13
- package/dist/generated/permissions.js.map +1 -1
- package/dist/hooks.d.ts +156 -6
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +192 -24
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.test.js +202 -20
- package/dist/hooks.test.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -31
- package/dist/types.d.ts +193 -72
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/default/AGENTS.md +177 -28
- package/templates/default/CLAUDE.md +177 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
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,
|
|
549
|
-
//
|
|
550
|
-
//
|
|
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
|
|
@@ -772,30 +819,26 @@ const { referenceId } = await vault.attach(documentId, {
|
|
|
772
819
|
await vault.detach(documentId, referenceId);
|
|
773
820
|
```
|
|
774
821
|
|
|
775
|
-
**Saving into the user's
|
|
822
|
+
**Saving into the user's Documents** — `upload(bytes, { ..., destination: 'documents' })` (`userVisible: true` is the same thing). By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
|
|
776
823
|
|
|
777
824
|
```tsx
|
|
778
825
|
const saved = await vault.upload(bytes, {
|
|
779
|
-
name: 'sunset.png',
|
|
826
|
+
name: 'sunset.png', // a suggestion — the user can change it
|
|
780
827
|
mimeType: 'image/png',
|
|
781
|
-
|
|
828
|
+
destination: 'documents',
|
|
782
829
|
});
|
|
830
|
+
// saved.documentId, saved.name (what the user kept), saved.access ('none' | 'read' | 'write')
|
|
783
831
|
```
|
|
784
832
|
|
|
785
|
-
|
|
833
|
+
The host opens its **Save dialog**: the user picks a folder in their Documents, can rename the file, and settles a name clash (keep both, or replace a file of the same type). You cannot script it, batch it, or pre-approve it, and you never learn the folder. Requires `vault:user-documents:write`. Cancelling rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. `folderPath`, `replacesDocumentId`, `tags` and `sensitivity` are rejected alongside it (the user decides those). Not available in preview or the dev harness — test it in the published plugin.
|
|
786
834
|
|
|
787
|
-
**
|
|
835
|
+
**Afterwards the file is the user's.** `useVaultDocuments()` no longer lists it. What you keep is reported as `access`: with `vault:user-documents:read` you can open it again (`'read'`) through `useVaultUserDocuments()`, and with `vault:user-documents:edit` the dialog offers the user "keep editing" (`'write'`) so you can keep saving it with `useVaultUserDocuments().saveContent`. The user can change either in Arche Access.
|
|
788
836
|
|
|
789
|
-
|
|
790
|
-
try {
|
|
791
|
-
await vault.promoteToUserFiles(documentId); // the HOST asks the user; you cannot script it
|
|
792
|
-
} catch (err) {
|
|
793
|
-
if (err instanceof FiasBridgeError && err.code === 'SAVE_DECLINED') return; // a normal outcome
|
|
794
|
-
throw err;
|
|
795
|
-
}
|
|
796
|
-
```
|
|
837
|
+
**Arche Saves, automatically** — `upload(bytes, { ..., destination: 'arche-saves' })` saves into the user's `Arche Saves/<your label>/` with no dialog. Only for an arche a Fias admin has designated (that issues its label, which is yours for good) and that declares `vault:arche-saves:write` (human-reviewed). Otherwise it rejects with `ARCHE_NOT_DESIGNATED` or `PERMISSION_DENIED`. The files are the user's to keep, move or delete; you can keep reading them while they stay in Arche Saves.
|
|
797
838
|
|
|
798
|
-
|
|
839
|
+
**`saveCopy(documentId, { suggestedName? })`** — hand the user a copy of one of your own documents through the same dialog, without re-sending the bytes (needs `vault:documents:read` too). Your working file is unchanged; delete it afterwards if you no longer need it.
|
|
840
|
+
|
|
841
|
+
**A document's visibility never changes.** Your private documents stay private, and a file in the user's files stays theirs — there is no call that moves one into the other.
|
|
799
842
|
|
|
800
843
|
**Sensitivity tiers** (`'standard'` | `'confidential'` | `'proprietary'`) gate downstream read access per the Vault's standard matrix. Defaults to `'standard'`.
|
|
801
844
|
|
|
@@ -834,6 +877,7 @@ const { bytes } = await userDocs.getBytes(id); // raw bytes for PROCESSING binar
|
|
|
834
877
|
- Grants are **per-document** and **standing**: the plugin can re-read granted documents in later sessions until the user removes access in **Vault → App Access**.
|
|
835
878
|
- After revocation, `get`/`getDownloadUrl` reject with `DOCUMENT_NOT_FOUND` — handle that path gracefully (drop the document from your UI; do not retry in a loop).
|
|
836
879
|
- `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
|
|
880
|
+
- **Say what you can open:** `pick({ accept: ['application/pdf', 'image/*', '.docx'] })` takes MIME types, MIME families or extensions, up to 20. The host's Open dialog offers the matching files; the user can still reveal the rest, shown disabled. A malformed list rejects with `INVALID_PARAMS`. Omitted, every type is offered.
|
|
837
881
|
- Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
|
|
838
882
|
- `getDownloadUrl` is for DISPLAY (`<img src>`); your iframe cannot fetch the URL (CSP). To process binary content (parse a PDF, transform an image), use `getBytes(id)` — size-capped at 15 MB (`CONSENTED_DOCUMENT_TOO_LARGE` names the cap; ask the user to open the file manually instead). `getBytes` has a tighter per-minute budget than the rest of the family because it can move megabytes: read sequentially, and surface a rate-limit error rather than retrying in a loop.
|
|
839
883
|
- Revocation behaves differently per method: `getBytes` and `get` re-check the grant on every call, so they start failing immediately. A URL already handed out by `getDownloadUrl` keeps working until it expires (1 hour) — that is the documented contract, not a bug.
|
|
@@ -880,7 +924,10 @@ const saved = await userDocs.saveContent({
|
|
|
880
924
|
|
|
881
925
|
### Sending a document for signature — Document Sign handoff
|
|
882
926
|
|
|
883
|
-
**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.
|
|
884
931
|
|
|
885
932
|
Hand one of the user's Vault PDFs to Document Sign with recipients pre-filled.
|
|
886
933
|
They place the signature fields and press Send there — a handoff never sends
|
|
@@ -924,7 +971,9 @@ created, which is why the outcome is there.
|
|
|
924
971
|
**Treat the result as a pointer, never as a record.** It tells you which
|
|
925
972
|
envelope to look at; it is not evidence of what happened to it. Match it to an
|
|
926
973
|
outstanding request by `requestId`, consume it once, and never re-point an
|
|
927
|
-
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.
|
|
928
977
|
|
|
929
978
|
**A handoff ENDS YOUR APP'S PROCESS. Anything you will need afterwards has to
|
|
930
979
|
be written down before you call `openArche`.** The host navigates away and
|
|
@@ -953,6 +1002,65 @@ local dev. Judging on arrival therefore rejects every genuine reply, not an
|
|
|
953
1002
|
occasional one. Hold the result until you know what was outstanding, then
|
|
954
1003
|
evaluate it.
|
|
955
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
|
+
|
|
956
1064
|
### `useArcheHandoff()` — Receive a document the user asked to open in your app
|
|
957
1065
|
|
|
958
1066
|
**Permission:** none to receive. Reading the document is a separate act (`vault:user-documents:read`, via `useVaultUserDocuments().getBytes`).
|
|
@@ -1050,6 +1158,32 @@ const fresh = await getUrl(assetId);
|
|
|
1050
1158
|
|
|
1051
1159
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
1052
1160
|
|
|
1161
|
+
### `useFiasContent()` — This plugin's content pack
|
|
1162
|
+
|
|
1163
|
+
**Permission:** `entities:client_invoke`
|
|
1164
|
+
**Returns:** `FiasContentApi`
|
|
1165
|
+
|
|
1166
|
+
Ship large or numerous files — lessons, articles, data, images, audio, fonts — in a `content/` directory beside `src/`. They are not bundled: `fias-dev submit` uploads them separately (only the files that changed), the review reads every one, and the published plugin reads them at runtime through this hook. The host page fetches, verifies and caches them; your plugin never touches the network.
|
|
1167
|
+
|
|
1168
|
+
```tsx
|
|
1169
|
+
import { useFiasContent, FiasContentError } from '@fias/arche-sdk';
|
|
1170
|
+
|
|
1171
|
+
const content = useFiasContent();
|
|
1172
|
+
|
|
1173
|
+
const lessons = await content.list('lessons/'); // [{ path, sizeBytes, mimeType }]
|
|
1174
|
+
const markdown = await content.getText('lessons/01-intro.md');
|
|
1175
|
+
const course = await content.getJson<Course>('course.json');
|
|
1176
|
+
const batch = await content.getMany(['a.md', 'b.md'], 'text'); // up to 100 files per call
|
|
1177
|
+
const cover = await content.getObjectUrl('img/cover.webp'); // blob: URL for <img src>
|
|
1178
|
+
const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long streaming audio
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
- Paths are relative to `content/`. Formats: `.md`, `.txt`, `.json` (text, ≤ 5 MB each); `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.avif` (static, single-frame images, ≤ 20 MB and 50 megapixels); `.ogg` (audio, ≤ 50 MB); `.woff2` (fonts, ≤ 5 MB). A pack holds at most 10,000 files, 2,000 images, 20 MB of text and 500 MB in total. Run `npx fias-dev content check` to validate without submitting.
|
|
1182
|
+
- Prefer `getObjectUrl` for images and fonts: repeat views come from the host cache. `getUrl` is for media that must stream or seek; it bypasses the cache, and a URL held past its `expiresAt` must be requested again.
|
|
1183
|
+
- Failures throw `FiasContentError` with a `code`: `CONTENT_NOT_FOUND`, `CONTENT_NOT_TEXT`, `CONTENT_TOO_LARGE`, `CONTENT_UNAVAILABLE`, `CONTENT_THROTTLED`, `CONTENT_UNAVAILABLE_IN_PREVIEW`, `PERMISSION_DENIED`, `RATE_LIMIT`. `getMany` returns the error for a failed path instead of throwing.
|
|
1184
|
+
- There is no published pack in the Arche Builder preview, so calls there fail with `CONTENT_UNAVAILABLE_IN_PREVIEW`; the dev harness serves your local `content/` directory.
|
|
1185
|
+
- Every publish reviews all of the content again. Reads are free for your users; the pack's storage and bandwidth are billed to you, the contributor.
|
|
1186
|
+
|
|
1053
1187
|
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
1054
1188
|
|
|
1055
1189
|
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
@@ -1159,18 +1293,29 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
|
1159
1293
|
openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
|
|
1160
1294
|
```
|
|
1161
1295
|
|
|
1162
|
-
**Receiving a deep link.** There is nothing extra to opt into: `currentPath`
|
|
1163
|
-
the
|
|
1164
|
-
that wants to be a deep-link target just
|
|
1296
|
+
**Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
|
|
1297
|
+
the host's path expressed in YOUR arche's route space — the same space
|
|
1298
|
+
`navigateTo` accepts — so an arche that wants to be a deep-link target just
|
|
1299
|
+
reads it at boot. The `/a/<arche>` prefix is stripped for you, and your home
|
|
1300
|
+
screen reads `/`.
|
|
1165
1301
|
|
|
1166
1302
|
```tsx
|
|
1167
1303
|
const { currentPath } = useFiasNavigation();
|
|
1168
|
-
//
|
|
1169
|
-
const page = currentPath.
|
|
1304
|
+
// a deep link to '/a/arc_0123…/map' arrives here as '/map'
|
|
1305
|
+
const page = currentPath.replace(/^\//, '') || 'home';
|
|
1170
1306
|
```
|
|
1171
1307
|
|
|
1172
|
-
It is
|
|
1173
|
-
|
|
1308
|
+
It is `''` until the host's `init` message lands — the bridge handshake is
|
|
1309
|
+
async, so your first render usually beats it. Treat `''` as "not known yet"
|
|
1310
|
+
rather than as your home screen; that distinction is what keeps a direct entry
|
|
1311
|
+
such as a share link from being lost to the race.
|
|
1312
|
+
|
|
1313
|
+
After that it TRACKS the host: it updates when you call `navigateTo`, and when
|
|
1314
|
+
the user moves the host themselves with browser back/forward. So a router
|
|
1315
|
+
driven off `currentPath` stays in step with the address bar, and the back
|
|
1316
|
+
button works the way your users expect — you do not have to mirror the path in
|
|
1317
|
+
your own state. The dev harness echoes navigations the same way, so what you
|
|
1318
|
+
see locally is what ships.
|
|
1174
1319
|
|
|
1175
1320
|
### Opening external links
|
|
1176
1321
|
|
|
@@ -1480,6 +1625,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
1480
1625
|
### Size and File Limits
|
|
1481
1626
|
|
|
1482
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)
|
|
1483
1629
|
- **Dependencies:** Max 20, exact semver versions only (e.g., `"4.4.7"`, not `"^4.4.7"`)
|
|
1484
1630
|
- Platform packages (`react`, `react-dom`, `@fias/arche-sdk`) are provided — do not include in `dependencies`
|
|
1485
1631
|
|
|
@@ -1489,6 +1635,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
1489
1635
|
- `storage_write`: 120/minute
|
|
1490
1636
|
- `storage_read`: 300/minute
|
|
1491
1637
|
- `storage_list`, `storage_delete`: 60/minute
|
|
1638
|
+
- Content reads (`useFiasContent()`): 120/minute — read related files together with `getMany` (up to 100 per call)
|
|
1492
1639
|
|
|
1493
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.
|
|
1494
1641
|
|
|
@@ -1578,6 +1725,8 @@ npm run submit
|
|
|
1578
1725
|
# Builds, validates, packages, uploads, submits for AI review
|
|
1579
1726
|
```
|
|
1580
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
|
+
|
|
1581
1730
|
The first time you list a plugin in the marketplace there is a one-time
|
|
1582
1731
|
listing fee; subsequent submissions of the same plugin only pay a small
|
|
1583
1732
|
per-review fee. The harness fetches the exact amount from the server and
|