@fias/create-fias-plugin 1.19.0 → 1.20.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/package.json +1 -1
- package/templates/default/AGENTS.md +142 -35
- package/templates/default/CLAUDE.md +142 -35
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
1
|
+
<!-- fias-sdk-guide-version: 2.28.0 -->
|
|
2
2
|
|
|
3
3
|
# FIAS Plugin Development Guide
|
|
4
4
|
|
|
@@ -21,24 +21,27 @@ vite.config.ts # Vite dev server config (port 3100)
|
|
|
21
21
|
|
|
22
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
23
|
|
|
24
|
-
| What it is | Where it goes
|
|
25
|
-
| ------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
-
| Your code and small constants (config, a short word list) | The bundle (`src/`)
|
|
27
|
-
| A few static images, fonts or
|
|
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()`
|
|
29
|
-
| Images you want to change without republishing code (logos, maps, photos with alt text) | The asset library: `useArcheAssets()` + `fias-dev assets`
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
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 music tracks 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
|
+
| Sound effects that must play on cue (hits, jumps, UI clicks) | The bundle: base64 in `src/`, decoded once at startup with `decodeAudioData` | Ready the moment the plugin opens; nothing to download per session. Keep the set small (a few hundred KB). Not `public/`: a `public/` sound can only play through an `<audio>` element, which lags its cue (badly on iOS). |
|
|
31
|
+
| The same sound effects shared by several arches (optional) | A workspace asset library: `useArcheAssetBytes().getMany()` | One copy for every linked arche; a new version reaches them with a pin refresh, no code republish. Downloaded each session — wait for it before the first cue. MP3 or WAV, ≤ 1 MB and 30 s each. |
|
|
32
|
+
| 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. |
|
|
33
|
+
| 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. |
|
|
34
|
+
| A small piece of UI state that should survive a reload (a setting, the last tab) | `usePersistentState()` | Debounced writes to `useFiasStorage()`. |
|
|
35
|
+
| Images one user shares with the app's other users | `useCommunityAssets()` | Moderated before anyone else sees them. |
|
|
36
|
+
| 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
37
|
|
|
36
38
|
**Common mistakes:**
|
|
37
39
|
|
|
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/`.
|
|
40
|
+
- **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/`. (A small set of sound effects is the exception — see the table.)
|
|
39
41
|
- **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
42
|
- **Using the DataStore or `useFiasStorage()` for data that is the same for every user.** That is content.
|
|
41
43
|
- **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.
|
|
44
|
+
- **Putting anything confidential in `public/`.** Files in `public/` are always public: anyone with a file's link can fetch it, even when your arche is restricted to chosen people. Put confidential material in a content pack (`content/`) — its access follows who may use the arche.
|
|
42
45
|
|
|
43
46
|
## SDK API Reference
|
|
44
47
|
|
|
@@ -283,6 +286,41 @@ Why this and not `createCollection` at startup: the runtime op is capped at **10
|
|
|
283
286
|
|
|
284
287
|
Re-publishing is safe: existing collections are left alone, a stricter role floor is applied, a looser one is refused, and a collection you stop declaring is kept (never deleted — it may hold user data). Keep using `createCollection` for collections whose names you only know at runtime.
|
|
285
288
|
|
|
289
|
+
### Integrity rules (`constraints`)
|
|
290
|
+
|
|
291
|
+
A collection can carry rules the platform checks on **every** write, so a buggy or tampered client cannot break them. Declare them as `constraints` on the collection in `fias-plugin.json` (or in `createCollection`'s options):
|
|
292
|
+
|
|
293
|
+
```jsonc
|
|
294
|
+
"collections": [
|
|
295
|
+
{ "name": "stakeholders", "scope": "workspace" },
|
|
296
|
+
{ "name": "transactions", "scope": "workspace", "constraints": { "appendOnly": true } },
|
|
297
|
+
{
|
|
298
|
+
"name": "securities",
|
|
299
|
+
"scope": "workspace",
|
|
300
|
+
"constraints": {
|
|
301
|
+
"nonNegative": ["quantityOutstanding"],
|
|
302
|
+
"fieldBounds": [{ "field": "quantityIssued", "op": "lte", "valueField": "quantityAuthorized" }],
|
|
303
|
+
"immutableFields": ["issuedAt"],
|
|
304
|
+
"references": [{ "field": "stakeholderId", "collection": "stakeholders" }],
|
|
305
|
+
"ledgerSums": [{
|
|
306
|
+
"field": "quantityOutstanding",
|
|
307
|
+
"ledger": "transactions",
|
|
308
|
+
"terms": [{ "key": "securityId", "amount": "quantityDelta" }]
|
|
309
|
+
}]
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- **`appendOnly`**: the collection is a ledger. Documents can be added, never overwritten or deleted.
|
|
316
|
+
- **`nonNegative`**: these numeric fields must be ≥ 0 when present.
|
|
317
|
+
- **`fieldBounds`**: `field <op> bound`, where `op` is `eq` / `ne` / `lt` / `lte` / `gt` / `gte` and the bound is a constant `value` or a sibling field `valueField` (exactly one).
|
|
318
|
+
- **`immutableFields`**: set once; an overwrite must carry the same value.
|
|
319
|
+
- **`references`**: `field` holds the key of a document that must exist in `collection`, in the same scope (same workspace, same user, or shared). An absent or null field is allowed. The writer must be able to read the target collection. Deleting a document that something still references is refused; there is no cascade.
|
|
320
|
+
- **`ledgerSums`**: a counter `field` may change only by exactly the sum of the rows appended to `ledger` in the **same write**. A ledger row counts toward the counter whose key its term's `key` field holds, adding its `amount` (subtracted with `negate: true`, filtered with `when: { field, in: [...] }`). A counter that moves without rows, and rows whose counter did not move by their sum, are both refused. The ledger must be `appendOnly` and have the counter's scope; an absent counter counts as 0. At most 4 terms per rule.
|
|
321
|
+
|
|
322
|
+
Rules that relate two documents (`references`, `ledgerSums`) are checked at the **end** of the write, so write the ledger row and the counter, or the target and the document pointing at it, together in one `batch` in any order. A violation rejects the whole write with `CONSTRAINT_VIOLATION`. Rules are **tighten-only**: you can add rules (at most 10 per kind), never remove or change one.
|
|
323
|
+
|
|
286
324
|
### Letting agents use your data (`agentAccess`)
|
|
287
325
|
|
|
288
326
|
**Permission:** `data:agents` (requires `data:store`)
|
|
@@ -1302,6 +1340,7 @@ useArcheHandoff((payload) => {
|
|
|
1302
1340
|
- **You are the type gate.** If you cannot open this file, say so plainly in your UI. Half-opening it is worse than refusing.
|
|
1303
1341
|
- Fires **once per handoff**, and one that arrives before your hook mounts is buffered and delivered on registration — so the first handoff of a session is never lost.
|
|
1304
1342
|
- Nothing hands documents to a preview, so the handler only fires in the published plugin.
|
|
1343
|
+
- **Being offered at all takes two steps.** Declare what you open in `fias-plugin.json` — `"fileIntake": { "kinds": ["document"], "types": [".docx"] }` — and ask Fias to approve it. `kinds` are `image`, `pdf`, `document`, `spreadsheet`, `presentation`, `audio`, `video`, `archive`, `code`, `other`; `types` (optional, lowercase extensions or MIME types) narrows them — list them, or your app is offered for every file of the kind (`document` includes `.txt`, `.md` and `.html`). Your own format goes under `other` with its extension in `types` (`"kinds": ["document", "other"], "types": [".docx", ".myformat"]`). The build fails if you declare `fileIntake` but never call `useArcheHandoff`. Once approved, your app appears under "Open in…" in My Data (and is the double-click default for documents, spreadsheets and presentations), and Fias AI can open files in it — when the user didn't pick your app themselves, Fias asks them first. A kind or type you add later stays off until it is approved too.
|
|
1305
1344
|
|
|
1306
1345
|
### `useFiasAIActions()` — Let the platform's Fias AI assistant operate your plugin
|
|
1307
1346
|
|
|
@@ -1355,7 +1394,7 @@ Rules that matter:
|
|
|
1355
1394
|
**Permission:** `assets:read`
|
|
1356
1395
|
**Returns:** `ArcheAssetsApi`
|
|
1357
1396
|
|
|
1358
|
-
A frozen set of images the arche's contributor uploaded and pinned to the published version
|
|
1397
|
+
A frozen set of images — and short sound effects — the arche's contributor uploaded and pinned to the published version: logos, brand images, hole maps, reference photos, UI sounds. Each entry has an immutable `assetId` and a short-lived `signedUrl` (~5 min) suitable for `<img src>` / `<audio src>`. `list` and `index` return images only unless you pass `kinds: ['audio']` (or `['image', 'audio']`); with `kinds` the entries are typed `ArcheLibraryAsset`, so narrow on `contentType` before using an image's size (a sound's `width`/`height` are `null`). Keeping sounds here is optional (see the table at the top for where sound effects belong), and music is not accepted.
|
|
1359
1398
|
|
|
1360
1399
|
```tsx
|
|
1361
1400
|
import { useArcheAssets } from '@fias/arche-sdk';
|
|
@@ -1372,6 +1411,37 @@ const fresh = await getUrl(assetId);
|
|
|
1372
1411
|
|
|
1373
1412
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
1374
1413
|
|
|
1414
|
+
### `useArcheAssetBytes()` — Asset bytes for Web Audio and canvas
|
|
1415
|
+
|
|
1416
|
+
**Permissions:** `entities:client_invoke` and `assets:read`
|
|
1417
|
+
**Returns:** `ArcheAssetBytesApi`
|
|
1418
|
+
|
|
1419
|
+
For sounds (or images) kept in the asset library. The plugin sandbox cannot fetch, so a signed URL is only good for `<img>`/`<audio>`. When you need the bytes — `AudioContext.decodeAudioData` for overlapping, precisely timed sound effects, or canvas pixel work — the host reads them for you. Load a set of sounds with **one `getMany` call** and wait for it before the first cue:
|
|
1420
|
+
|
|
1421
|
+
```tsx
|
|
1422
|
+
import { useArcheAssets, useArcheAssetBytes } from '@fias/arche-sdk';
|
|
1423
|
+
|
|
1424
|
+
const { list } = useArcheAssets();
|
|
1425
|
+
const { getMany } = useArcheAssetBytes();
|
|
1426
|
+
|
|
1427
|
+
const ctx = new AudioContext(); // resume() it on the first user gesture
|
|
1428
|
+
const { entries } = await list({ kinds: ['audio'], tags: ['sfx:ui'] });
|
|
1429
|
+
const loaded = await getMany(entries); // { [assetId]: { contentType, bytes, sizeBytes } | ArcheAssetError }
|
|
1430
|
+
const buffers = new Map<string, AudioBuffer>();
|
|
1431
|
+
for (const sound of entries) {
|
|
1432
|
+
const result = loaded[sound.assetId];
|
|
1433
|
+
if (result instanceof Error) continue; // result.code says why
|
|
1434
|
+
buffers.set(sound.name ?? sound.assetId, await ctx.decodeAudioData(result.bytes));
|
|
1435
|
+
}
|
|
1436
|
+
// From here a sound plays on cue: const src = ctx.createBufferSource(); src.buffer = …; src.connect(ctx.destination); src.start();
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
- **Never one call per sound.** The host blocks any capability called more than 50 times in 5 seconds, for 10 seconds — the 51st sound and every one after it would stay silent. `getMany` reads up to 100 per call. `getBytes(entry)` reads one; `getBytes` calls made together (a `Promise.all`) are batched the same way, but an `await` loop over many sounds is not.
|
|
1440
|
+
- **Pass entries straight from `list()`.** Their signed URLs (5 minutes) are reused, so each read is one round trip; a lapsed URL is replaced for a few entries, past that `ASSET_URL_EXPIRED` asks you to list again.
|
|
1441
|
+
- **Read once per session and keep the decoded buffers.** Every read is one billed asset load, and signed URLs change each session, so nothing carries over from the last one.
|
|
1442
|
+
- `getMany` resolves to each asset's bytes or its `ArcheAssetError` (`code`: `ASSET_URL_EXPIRED`, `ASSET_UNAVAILABLE`, `ASSET_TOO_LARGE`, …) and rejects only when the read is refused as a whole (`PERMISSION_DENIED`, `RATE_LIMIT`, `ASSETS_UNAVAILABLE_IN_PREVIEW`). Not available in the builder preview.
|
|
1443
|
+
- Library sounds are MP3 or WAV, which `decodeAudioData` accepts in every browser, iOS Safari included.
|
|
1444
|
+
|
|
1375
1445
|
### `useFiasContent()` — This plugin's content pack
|
|
1376
1446
|
|
|
1377
1447
|
**Permission:** `entities:client_invoke`
|
|
@@ -1398,6 +1468,33 @@ const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long
|
|
|
1398
1468
|
- 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.
|
|
1399
1469
|
- 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.
|
|
1400
1470
|
|
|
1471
|
+
### `useLocalStore()` — Working data kept on the user's device
|
|
1472
|
+
|
|
1473
|
+
**Permission:** `storage:local`
|
|
1474
|
+
**Returns:** `FiasLocalStoreApi`
|
|
1475
|
+
|
|
1476
|
+
A recovery journal, drafts, a cache: anything you would write on every change so a refresh, a crash or a closed tab loses nothing. Writes take milliseconds and never touch the network — the host page keeps the data in its own browser storage, in a space only your plugin can read, for the signed-in user in the workspace they are working in. Personal and each org workspace keep separate data: when the user switches workspace, your plugin sees that workspace's store, so nothing kept while working in one workspace leaks into another. It is cleared when the user signs out, or when someone else signs in on the device; an expired session keeps it, so the same user picks up where they left off.
|
|
1477
|
+
|
|
1478
|
+
```tsx
|
|
1479
|
+
import { useLocalStore, FiasLocalStoreError } from '@fias/arche-sdk';
|
|
1480
|
+
|
|
1481
|
+
const store = useLocalStore();
|
|
1482
|
+
|
|
1483
|
+
await store.put(`session/${docId}`, snapshot); // JSON, a string, or bytes — on every change
|
|
1484
|
+
const saved = await store.getJson<Snapshot>(`session/${docId}`); // on load; null if absent
|
|
1485
|
+
const open = await store.list('session/'); // [{ key, sizeBytes, updatedAt }], sorted by key
|
|
1486
|
+
const restored = await store.getMany(open.map((e) => e.key)); // ≤ 100 entries in one call
|
|
1487
|
+
await store.delete(`session/${docId}`); // on close
|
|
1488
|
+
const { usedBytes, budgetBytes } = await store.estimate();
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
- This is **not** where the user's work is kept. It is best-effort device storage: a browser may clear site data, and nothing here reaches another device. Journal here; save the user's files through the Vault (`useVaultUserDocuments().saveContent`, `upload`, or the Save dialog).
|
|
1492
|
+
- Values come back as the kind they were put: a string is text, an `ArrayBuffer` / `Uint8Array` is bytes (read back as an `ArrayBuffer`), anything else is plain JSON (no `Date`, `Map`, `Blob` or other typed arrays — those are refused). `getText`, `getBytes` and `getJson` check the kind and throw `LOCAL_STORE_WRONG_KIND` on a mismatch; `getJson` also parses a text entry, so `put(key, JSON.stringify(x))` reads back like `put(key, x)`. `get` returns the raw entry; `getMany(keys)` returns up to 100 raw entries in one call, in the order asked, `null` for a missing key — restore a journal that way rather than with a `get` per key, which the bridge's runaway-loop guard (50 calls in 5 seconds) would stop.
|
|
1493
|
+
- Put a large snapshot (an image, a big document) as **bytes**. A JSON value is validated and serialized on the host page on every write, which takes time in proportion to its size; bytes are only copied.
|
|
1494
|
+
- Limits: a key ≤ 256 characters; one value ≤ 64 MB, and one `getMany` returns ≤ 64 MB in all (`LOCAL_STORE_VALUE_TOO_LARGE` — read fewer keys at a time); your whole store for this user in one workspace ≤ 256 MB (or a quarter of the browser's quota, if smaller) and ≤ 10,000 entries. Each entry is charged its value, its key and a small fixed overhead. Over a limit, `put` throws `LOCAL_STORE_QUOTA_EXCEEDED` instead of evicting anything — decide what to keep with `estimate()` and `list()`.
|
|
1495
|
+
- Failures throw `FiasLocalStoreError` with a `code`: `LOCAL_STORE_UNAVAILABLE` (the browser refused storage — degrade to "nothing persisted"), `LOCAL_STORE_KEY_INVALID`, `LOCAL_STORE_VALUE_TOO_LARGE`, `LOCAL_STORE_QUOTA_EXCEEDED`, `LOCAL_STORE_WRONG_KIND`, `INVALID_INPUT`, `PERMISSION_DENIED`, `RATE_LIMIT`.
|
|
1496
|
+
- Works in the dev harness (mock and live modes) and in the Arche Builder preview, each under its own namespace.
|
|
1497
|
+
|
|
1401
1498
|
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
1402
1499
|
|
|
1403
1500
|
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
@@ -1535,8 +1632,17 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
|
1535
1632
|
// `arche_…` arche is rejected, as is a malformed one (you get an error, never
|
|
1536
1633
|
// a silent landing on the home screen).
|
|
1537
1634
|
openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
|
|
1635
|
+
|
|
1636
|
+
// Open a chat channel in the Chat arche.
|
|
1637
|
+
openArche('arche_chat', { payload: { kind: 'open_channel', channelId } });
|
|
1538
1638
|
```
|
|
1539
1639
|
|
|
1640
|
+
**Opening a chat channel.** The `open_channel` payload is a pointer, never
|
|
1641
|
+
access: Chat re-reads the channel under the user's own membership, and a
|
|
1642
|
+
non-member lands on the Chat home with a notice instead. Make the user a member
|
|
1643
|
+
first (LitCraft: `join_guild_chat` on the LitCraft Player entity). Code that
|
|
1644
|
+
switches exhaustively on `ArcheHandoffPayloadInput`'s `kind` needs the new case.
|
|
1645
|
+
|
|
1540
1646
|
**Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
|
|
1541
1647
|
the host's path expressed in YOUR arche's route space — the same space
|
|
1542
1648
|
`navigateTo` accepts — so an arche that wants to be a deep-link target just
|
|
@@ -1790,26 +1896,26 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
1790
1896
|
|
|
1791
1897
|
**Fields:**
|
|
1792
1898
|
|
|
1793
|
-
| Field | Req | Description
|
|
1794
|
-
| --------------------- | ---- |
|
|
1795
|
-
| `name` | Yes | Plugin identifier (lowercase, hyphens)
|
|
1796
|
-
| `version` | Yes | Semver (e.g., `"1.0.0"`)
|
|
1797
|
-
| `description` | Yes | Short marketplace description
|
|
1798
|
-
| `expandedDescription` | No | Long-form marketplace copy
|
|
1799
|
-
| `main` | Yes | Entry point source file
|
|
1800
|
-
| `archeType` | Yes | `"tool"` or `"site"`
|
|
1801
|
-
| `categorySlug` | No | Marketplace category for discovery
|
|
1802
|
-
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on local
|
|
1803
|
-
| `tags` | No | Discovery tags
|
|
1804
|
-
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products`
|
|
1805
|
-
| `permissions` | Yes | Array of permission scopes (see below)
|
|
1806
|
-
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below)
|
|
1807
|
-
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below)
|
|
1808
|
-
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"`
|
|
1809
|
-
| `isListed` | No | Default `true`. Visible in marketplace store
|
|
1810
|
-
| `sdk` | Yes | SDK version range
|
|
1811
|
-
| `dependencies` | No | npm packages with **exact** versions (max 20)
|
|
1812
|
-
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit
|
|
1899
|
+
| Field | Req | Description |
|
|
1900
|
+
| --------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1901
|
+
| `name` | Yes | Plugin identifier (lowercase, hyphens) |
|
|
1902
|
+
| `version` | Yes | Semver (e.g., `"1.0.0"`) |
|
|
1903
|
+
| `description` | Yes | Short marketplace description |
|
|
1904
|
+
| `expandedDescription` | No | Long-form marketplace copy |
|
|
1905
|
+
| `main` | Yes | Entry point source file |
|
|
1906
|
+
| `archeType` | Yes | `"tool"` or `"site"` |
|
|
1907
|
+
| `categorySlug` | No | Marketplace category for discovery |
|
|
1908
|
+
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on every publish (local, staging and prod) and rehosted as your marketplace/store icon. PNG, JPEG, WebP or SVG, ≤5 MB; raster images must also be ≤1024×1024 px (SVG has no pixel limit). An invalid icon rejects the submission: a first-publish listing fee is refunded, but the AI review that already ran is still billed. To change it, replace the file (or the path) and republish; removing the field keeps the current icon. |
|
|
1909
|
+
| `tags` | No | Discovery tags |
|
|
1910
|
+
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products` |
|
|
1911
|
+
| `permissions` | Yes | Array of permission scopes (see below) |
|
|
1912
|
+
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below) |
|
|
1913
|
+
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below) |
|
|
1914
|
+
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"` |
|
|
1915
|
+
| `isListed` | No | Default `true`. Visible in marketplace store |
|
|
1916
|
+
| `sdk` | Yes | SDK version range |
|
|
1917
|
+
| `dependencies` | No | npm packages with **exact** versions (max 20) |
|
|
1918
|
+
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit |
|
|
1813
1919
|
|
|
1814
1920
|
**Pricing detail:**
|
|
1815
1921
|
|
|
@@ -1824,7 +1930,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
1824
1930
|
|
|
1825
1931
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
1826
1932
|
|
|
1827
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `assets:read`, `assets:community:read`, `assets:community:publish`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`
|
|
1933
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `storage:local`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `assets:read`, `assets:community:read`, `assets:community:publish`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`
|
|
1828
1934
|
|
|
1829
1935
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID (or a `{ capability }` selector, or an entry from `useTextModels()`) and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
1830
1936
|
|
|
@@ -1896,6 +2002,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
1896
2002
|
- `storage_read`: 300/minute
|
|
1897
2003
|
- `storage_list`, `storage_delete`: 60/minute
|
|
1898
2004
|
- Content reads (`useFiasContent()`): 120/minute — read related files together with `getMany` (up to 100 per call)
|
|
2005
|
+
- Asset byte reads (`useArcheAssetBytes()`): 120/minute — load a set of sounds with one `getMany` (up to 100 per call)
|
|
1899
2006
|
|
|
1900
2007
|
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.
|
|
1901
2008
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
1
|
+
<!-- fias-sdk-guide-version: 2.28.0 -->
|
|
2
2
|
|
|
3
3
|
# FIAS Plugin Development Guide
|
|
4
4
|
|
|
@@ -21,24 +21,27 @@ vite.config.ts # Vite dev server config (port 3100)
|
|
|
21
21
|
|
|
22
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
23
|
|
|
24
|
-
| What it is | Where it goes
|
|
25
|
-
| ------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
-
| Your code and small constants (config, a short word list) | The bundle (`src/`)
|
|
27
|
-
| A few static images, fonts or
|
|
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()`
|
|
29
|
-
| Images you want to change without republishing code (logos, maps, photos with alt text) | The asset library: `useArcheAssets()` + `fias-dev assets`
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
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 music tracks 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
|
+
| Sound effects that must play on cue (hits, jumps, UI clicks) | The bundle: base64 in `src/`, decoded once at startup with `decodeAudioData` | Ready the moment the plugin opens; nothing to download per session. Keep the set small (a few hundred KB). Not `public/`: a `public/` sound can only play through an `<audio>` element, which lags its cue (badly on iOS). |
|
|
31
|
+
| The same sound effects shared by several arches (optional) | A workspace asset library: `useArcheAssetBytes().getMany()` | One copy for every linked arche; a new version reaches them with a pin refresh, no code republish. Downloaded each session — wait for it before the first cue. MP3 or WAV, ≤ 1 MB and 30 s each. |
|
|
32
|
+
| 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. |
|
|
33
|
+
| 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. |
|
|
34
|
+
| A small piece of UI state that should survive a reload (a setting, the last tab) | `usePersistentState()` | Debounced writes to `useFiasStorage()`. |
|
|
35
|
+
| Images one user shares with the app's other users | `useCommunityAssets()` | Moderated before anyone else sees them. |
|
|
36
|
+
| 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
37
|
|
|
36
38
|
**Common mistakes:**
|
|
37
39
|
|
|
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/`.
|
|
40
|
+
- **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/`. (A small set of sound effects is the exception — see the table.)
|
|
39
41
|
- **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
42
|
- **Using the DataStore or `useFiasStorage()` for data that is the same for every user.** That is content.
|
|
41
43
|
- **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.
|
|
44
|
+
- **Putting anything confidential in `public/`.** Files in `public/` are always public: anyone with a file's link can fetch it, even when your arche is restricted to chosen people. Put confidential material in a content pack (`content/`) — its access follows who may use the arche.
|
|
42
45
|
|
|
43
46
|
## SDK API Reference
|
|
44
47
|
|
|
@@ -283,6 +286,41 @@ Why this and not `createCollection` at startup: the runtime op is capped at **10
|
|
|
283
286
|
|
|
284
287
|
Re-publishing is safe: existing collections are left alone, a stricter role floor is applied, a looser one is refused, and a collection you stop declaring is kept (never deleted — it may hold user data). Keep using `createCollection` for collections whose names you only know at runtime.
|
|
285
288
|
|
|
289
|
+
### Integrity rules (`constraints`)
|
|
290
|
+
|
|
291
|
+
A collection can carry rules the platform checks on **every** write, so a buggy or tampered client cannot break them. Declare them as `constraints` on the collection in `fias-plugin.json` (or in `createCollection`'s options):
|
|
292
|
+
|
|
293
|
+
```jsonc
|
|
294
|
+
"collections": [
|
|
295
|
+
{ "name": "stakeholders", "scope": "workspace" },
|
|
296
|
+
{ "name": "transactions", "scope": "workspace", "constraints": { "appendOnly": true } },
|
|
297
|
+
{
|
|
298
|
+
"name": "securities",
|
|
299
|
+
"scope": "workspace",
|
|
300
|
+
"constraints": {
|
|
301
|
+
"nonNegative": ["quantityOutstanding"],
|
|
302
|
+
"fieldBounds": [{ "field": "quantityIssued", "op": "lte", "valueField": "quantityAuthorized" }],
|
|
303
|
+
"immutableFields": ["issuedAt"],
|
|
304
|
+
"references": [{ "field": "stakeholderId", "collection": "stakeholders" }],
|
|
305
|
+
"ledgerSums": [{
|
|
306
|
+
"field": "quantityOutstanding",
|
|
307
|
+
"ledger": "transactions",
|
|
308
|
+
"terms": [{ "key": "securityId", "amount": "quantityDelta" }]
|
|
309
|
+
}]
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- **`appendOnly`**: the collection is a ledger. Documents can be added, never overwritten or deleted.
|
|
316
|
+
- **`nonNegative`**: these numeric fields must be ≥ 0 when present.
|
|
317
|
+
- **`fieldBounds`**: `field <op> bound`, where `op` is `eq` / `ne` / `lt` / `lte` / `gt` / `gte` and the bound is a constant `value` or a sibling field `valueField` (exactly one).
|
|
318
|
+
- **`immutableFields`**: set once; an overwrite must carry the same value.
|
|
319
|
+
- **`references`**: `field` holds the key of a document that must exist in `collection`, in the same scope (same workspace, same user, or shared). An absent or null field is allowed. The writer must be able to read the target collection. Deleting a document that something still references is refused; there is no cascade.
|
|
320
|
+
- **`ledgerSums`**: a counter `field` may change only by exactly the sum of the rows appended to `ledger` in the **same write**. A ledger row counts toward the counter whose key its term's `key` field holds, adding its `amount` (subtracted with `negate: true`, filtered with `when: { field, in: [...] }`). A counter that moves without rows, and rows whose counter did not move by their sum, are both refused. The ledger must be `appendOnly` and have the counter's scope; an absent counter counts as 0. At most 4 terms per rule.
|
|
321
|
+
|
|
322
|
+
Rules that relate two documents (`references`, `ledgerSums`) are checked at the **end** of the write, so write the ledger row and the counter, or the target and the document pointing at it, together in one `batch` in any order. A violation rejects the whole write with `CONSTRAINT_VIOLATION`. Rules are **tighten-only**: you can add rules (at most 10 per kind), never remove or change one.
|
|
323
|
+
|
|
286
324
|
### Letting agents use your data (`agentAccess`)
|
|
287
325
|
|
|
288
326
|
**Permission:** `data:agents` (requires `data:store`)
|
|
@@ -1302,6 +1340,7 @@ useArcheHandoff((payload) => {
|
|
|
1302
1340
|
- **You are the type gate.** If you cannot open this file, say so plainly in your UI. Half-opening it is worse than refusing.
|
|
1303
1341
|
- Fires **once per handoff**, and one that arrives before your hook mounts is buffered and delivered on registration — so the first handoff of a session is never lost.
|
|
1304
1342
|
- Nothing hands documents to a preview, so the handler only fires in the published plugin.
|
|
1343
|
+
- **Being offered at all takes two steps.** Declare what you open in `fias-plugin.json` — `"fileIntake": { "kinds": ["document"], "types": [".docx"] }` — and ask Fias to approve it. `kinds` are `image`, `pdf`, `document`, `spreadsheet`, `presentation`, `audio`, `video`, `archive`, `code`, `other`; `types` (optional, lowercase extensions or MIME types) narrows them — list them, or your app is offered for every file of the kind (`document` includes `.txt`, `.md` and `.html`). Your own format goes under `other` with its extension in `types` (`"kinds": ["document", "other"], "types": [".docx", ".myformat"]`). The build fails if you declare `fileIntake` but never call `useArcheHandoff`. Once approved, your app appears under "Open in…" in My Data (and is the double-click default for documents, spreadsheets and presentations), and Fias AI can open files in it — when the user didn't pick your app themselves, Fias asks them first. A kind or type you add later stays off until it is approved too.
|
|
1305
1344
|
|
|
1306
1345
|
### `useFiasAIActions()` — Let the platform's Fias AI assistant operate your plugin
|
|
1307
1346
|
|
|
@@ -1355,7 +1394,7 @@ Rules that matter:
|
|
|
1355
1394
|
**Permission:** `assets:read`
|
|
1356
1395
|
**Returns:** `ArcheAssetsApi`
|
|
1357
1396
|
|
|
1358
|
-
A frozen set of images the arche's contributor uploaded and pinned to the published version
|
|
1397
|
+
A frozen set of images — and short sound effects — the arche's contributor uploaded and pinned to the published version: logos, brand images, hole maps, reference photos, UI sounds. Each entry has an immutable `assetId` and a short-lived `signedUrl` (~5 min) suitable for `<img src>` / `<audio src>`. `list` and `index` return images only unless you pass `kinds: ['audio']` (or `['image', 'audio']`); with `kinds` the entries are typed `ArcheLibraryAsset`, so narrow on `contentType` before using an image's size (a sound's `width`/`height` are `null`). Keeping sounds here is optional (see the table at the top for where sound effects belong), and music is not accepted.
|
|
1359
1398
|
|
|
1360
1399
|
```tsx
|
|
1361
1400
|
import { useArcheAssets } from '@fias/arche-sdk';
|
|
@@ -1372,6 +1411,37 @@ const fresh = await getUrl(assetId);
|
|
|
1372
1411
|
|
|
1373
1412
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
1374
1413
|
|
|
1414
|
+
### `useArcheAssetBytes()` — Asset bytes for Web Audio and canvas
|
|
1415
|
+
|
|
1416
|
+
**Permissions:** `entities:client_invoke` and `assets:read`
|
|
1417
|
+
**Returns:** `ArcheAssetBytesApi`
|
|
1418
|
+
|
|
1419
|
+
For sounds (or images) kept in the asset library. The plugin sandbox cannot fetch, so a signed URL is only good for `<img>`/`<audio>`. When you need the bytes — `AudioContext.decodeAudioData` for overlapping, precisely timed sound effects, or canvas pixel work — the host reads them for you. Load a set of sounds with **one `getMany` call** and wait for it before the first cue:
|
|
1420
|
+
|
|
1421
|
+
```tsx
|
|
1422
|
+
import { useArcheAssets, useArcheAssetBytes } from '@fias/arche-sdk';
|
|
1423
|
+
|
|
1424
|
+
const { list } = useArcheAssets();
|
|
1425
|
+
const { getMany } = useArcheAssetBytes();
|
|
1426
|
+
|
|
1427
|
+
const ctx = new AudioContext(); // resume() it on the first user gesture
|
|
1428
|
+
const { entries } = await list({ kinds: ['audio'], tags: ['sfx:ui'] });
|
|
1429
|
+
const loaded = await getMany(entries); // { [assetId]: { contentType, bytes, sizeBytes } | ArcheAssetError }
|
|
1430
|
+
const buffers = new Map<string, AudioBuffer>();
|
|
1431
|
+
for (const sound of entries) {
|
|
1432
|
+
const result = loaded[sound.assetId];
|
|
1433
|
+
if (result instanceof Error) continue; // result.code says why
|
|
1434
|
+
buffers.set(sound.name ?? sound.assetId, await ctx.decodeAudioData(result.bytes));
|
|
1435
|
+
}
|
|
1436
|
+
// From here a sound plays on cue: const src = ctx.createBufferSource(); src.buffer = …; src.connect(ctx.destination); src.start();
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
- **Never one call per sound.** The host blocks any capability called more than 50 times in 5 seconds, for 10 seconds — the 51st sound and every one after it would stay silent. `getMany` reads up to 100 per call. `getBytes(entry)` reads one; `getBytes` calls made together (a `Promise.all`) are batched the same way, but an `await` loop over many sounds is not.
|
|
1440
|
+
- **Pass entries straight from `list()`.** Their signed URLs (5 minutes) are reused, so each read is one round trip; a lapsed URL is replaced for a few entries, past that `ASSET_URL_EXPIRED` asks you to list again.
|
|
1441
|
+
- **Read once per session and keep the decoded buffers.** Every read is one billed asset load, and signed URLs change each session, so nothing carries over from the last one.
|
|
1442
|
+
- `getMany` resolves to each asset's bytes or its `ArcheAssetError` (`code`: `ASSET_URL_EXPIRED`, `ASSET_UNAVAILABLE`, `ASSET_TOO_LARGE`, …) and rejects only when the read is refused as a whole (`PERMISSION_DENIED`, `RATE_LIMIT`, `ASSETS_UNAVAILABLE_IN_PREVIEW`). Not available in the builder preview.
|
|
1443
|
+
- Library sounds are MP3 or WAV, which `decodeAudioData` accepts in every browser, iOS Safari included.
|
|
1444
|
+
|
|
1375
1445
|
### `useFiasContent()` — This plugin's content pack
|
|
1376
1446
|
|
|
1377
1447
|
**Permission:** `entities:client_invoke`
|
|
@@ -1398,6 +1468,33 @@ const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long
|
|
|
1398
1468
|
- 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.
|
|
1399
1469
|
- 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.
|
|
1400
1470
|
|
|
1471
|
+
### `useLocalStore()` — Working data kept on the user's device
|
|
1472
|
+
|
|
1473
|
+
**Permission:** `storage:local`
|
|
1474
|
+
**Returns:** `FiasLocalStoreApi`
|
|
1475
|
+
|
|
1476
|
+
A recovery journal, drafts, a cache: anything you would write on every change so a refresh, a crash or a closed tab loses nothing. Writes take milliseconds and never touch the network — the host page keeps the data in its own browser storage, in a space only your plugin can read, for the signed-in user in the workspace they are working in. Personal and each org workspace keep separate data: when the user switches workspace, your plugin sees that workspace's store, so nothing kept while working in one workspace leaks into another. It is cleared when the user signs out, or when someone else signs in on the device; an expired session keeps it, so the same user picks up where they left off.
|
|
1477
|
+
|
|
1478
|
+
```tsx
|
|
1479
|
+
import { useLocalStore, FiasLocalStoreError } from '@fias/arche-sdk';
|
|
1480
|
+
|
|
1481
|
+
const store = useLocalStore();
|
|
1482
|
+
|
|
1483
|
+
await store.put(`session/${docId}`, snapshot); // JSON, a string, or bytes — on every change
|
|
1484
|
+
const saved = await store.getJson<Snapshot>(`session/${docId}`); // on load; null if absent
|
|
1485
|
+
const open = await store.list('session/'); // [{ key, sizeBytes, updatedAt }], sorted by key
|
|
1486
|
+
const restored = await store.getMany(open.map((e) => e.key)); // ≤ 100 entries in one call
|
|
1487
|
+
await store.delete(`session/${docId}`); // on close
|
|
1488
|
+
const { usedBytes, budgetBytes } = await store.estimate();
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
- This is **not** where the user's work is kept. It is best-effort device storage: a browser may clear site data, and nothing here reaches another device. Journal here; save the user's files through the Vault (`useVaultUserDocuments().saveContent`, `upload`, or the Save dialog).
|
|
1492
|
+
- Values come back as the kind they were put: a string is text, an `ArrayBuffer` / `Uint8Array` is bytes (read back as an `ArrayBuffer`), anything else is plain JSON (no `Date`, `Map`, `Blob` or other typed arrays — those are refused). `getText`, `getBytes` and `getJson` check the kind and throw `LOCAL_STORE_WRONG_KIND` on a mismatch; `getJson` also parses a text entry, so `put(key, JSON.stringify(x))` reads back like `put(key, x)`. `get` returns the raw entry; `getMany(keys)` returns up to 100 raw entries in one call, in the order asked, `null` for a missing key — restore a journal that way rather than with a `get` per key, which the bridge's runaway-loop guard (50 calls in 5 seconds) would stop.
|
|
1493
|
+
- Put a large snapshot (an image, a big document) as **bytes**. A JSON value is validated and serialized on the host page on every write, which takes time in proportion to its size; bytes are only copied.
|
|
1494
|
+
- Limits: a key ≤ 256 characters; one value ≤ 64 MB, and one `getMany` returns ≤ 64 MB in all (`LOCAL_STORE_VALUE_TOO_LARGE` — read fewer keys at a time); your whole store for this user in one workspace ≤ 256 MB (or a quarter of the browser's quota, if smaller) and ≤ 10,000 entries. Each entry is charged its value, its key and a small fixed overhead. Over a limit, `put` throws `LOCAL_STORE_QUOTA_EXCEEDED` instead of evicting anything — decide what to keep with `estimate()` and `list()`.
|
|
1495
|
+
- Failures throw `FiasLocalStoreError` with a `code`: `LOCAL_STORE_UNAVAILABLE` (the browser refused storage — degrade to "nothing persisted"), `LOCAL_STORE_KEY_INVALID`, `LOCAL_STORE_VALUE_TOO_LARGE`, `LOCAL_STORE_QUOTA_EXCEEDED`, `LOCAL_STORE_WRONG_KIND`, `INVALID_INPUT`, `PERMISSION_DENIED`, `RATE_LIMIT`.
|
|
1496
|
+
- Works in the dev harness (mock and live modes) and in the Arche Builder preview, each under its own namespace.
|
|
1497
|
+
|
|
1401
1498
|
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
1402
1499
|
|
|
1403
1500
|
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
@@ -1535,8 +1632,17 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
|
1535
1632
|
// `arche_…` arche is rejected, as is a malformed one (you get an error, never
|
|
1536
1633
|
// a silent landing on the home screen).
|
|
1537
1634
|
openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
|
|
1635
|
+
|
|
1636
|
+
// Open a chat channel in the Chat arche.
|
|
1637
|
+
openArche('arche_chat', { payload: { kind: 'open_channel', channelId } });
|
|
1538
1638
|
```
|
|
1539
1639
|
|
|
1640
|
+
**Opening a chat channel.** The `open_channel` payload is a pointer, never
|
|
1641
|
+
access: Chat re-reads the channel under the user's own membership, and a
|
|
1642
|
+
non-member lands on the Chat home with a notice instead. Make the user a member
|
|
1643
|
+
first (LitCraft: `join_guild_chat` on the LitCraft Player entity). Code that
|
|
1644
|
+
switches exhaustively on `ArcheHandoffPayloadInput`'s `kind` needs the new case.
|
|
1645
|
+
|
|
1540
1646
|
**Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
|
|
1541
1647
|
the host's path expressed in YOUR arche's route space — the same space
|
|
1542
1648
|
`navigateTo` accepts — so an arche that wants to be a deep-link target just
|
|
@@ -1790,26 +1896,26 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
1790
1896
|
|
|
1791
1897
|
**Fields:**
|
|
1792
1898
|
|
|
1793
|
-
| Field | Req | Description
|
|
1794
|
-
| --------------------- | ---- |
|
|
1795
|
-
| `name` | Yes | Plugin identifier (lowercase, hyphens)
|
|
1796
|
-
| `version` | Yes | Semver (e.g., `"1.0.0"`)
|
|
1797
|
-
| `description` | Yes | Short marketplace description
|
|
1798
|
-
| `expandedDescription` | No | Long-form marketplace copy
|
|
1799
|
-
| `main` | Yes | Entry point source file
|
|
1800
|
-
| `archeType` | Yes | `"tool"` or `"site"`
|
|
1801
|
-
| `categorySlug` | No | Marketplace category for discovery
|
|
1802
|
-
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on local
|
|
1803
|
-
| `tags` | No | Discovery tags
|
|
1804
|
-
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products`
|
|
1805
|
-
| `permissions` | Yes | Array of permission scopes (see below)
|
|
1806
|
-
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below)
|
|
1807
|
-
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below)
|
|
1808
|
-
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"`
|
|
1809
|
-
| `isListed` | No | Default `true`. Visible in marketplace store
|
|
1810
|
-
| `sdk` | Yes | SDK version range
|
|
1811
|
-
| `dependencies` | No | npm packages with **exact** versions (max 20)
|
|
1812
|
-
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit
|
|
1899
|
+
| Field | Req | Description |
|
|
1900
|
+
| --------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1901
|
+
| `name` | Yes | Plugin identifier (lowercase, hyphens) |
|
|
1902
|
+
| `version` | Yes | Semver (e.g., `"1.0.0"`) |
|
|
1903
|
+
| `description` | Yes | Short marketplace description |
|
|
1904
|
+
| `expandedDescription` | No | Long-form marketplace copy |
|
|
1905
|
+
| `main` | Yes | Entry point source file |
|
|
1906
|
+
| `archeType` | Yes | `"tool"` or `"site"` |
|
|
1907
|
+
| `categorySlug` | No | Marketplace category for discovery |
|
|
1908
|
+
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on every publish (local, staging and prod) and rehosted as your marketplace/store icon. PNG, JPEG, WebP or SVG, ≤5 MB; raster images must also be ≤1024×1024 px (SVG has no pixel limit). An invalid icon rejects the submission: a first-publish listing fee is refunded, but the AI review that already ran is still billed. To change it, replace the file (or the path) and republish; removing the field keeps the current icon. |
|
|
1909
|
+
| `tags` | No | Discovery tags |
|
|
1910
|
+
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products` |
|
|
1911
|
+
| `permissions` | Yes | Array of permission scopes (see below) |
|
|
1912
|
+
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below) |
|
|
1913
|
+
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below) |
|
|
1914
|
+
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"` |
|
|
1915
|
+
| `isListed` | No | Default `true`. Visible in marketplace store |
|
|
1916
|
+
| `sdk` | Yes | SDK version range |
|
|
1917
|
+
| `dependencies` | No | npm packages with **exact** versions (max 20) |
|
|
1918
|
+
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit |
|
|
1813
1919
|
|
|
1814
1920
|
**Pricing detail:**
|
|
1815
1921
|
|
|
@@ -1824,7 +1930,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
1824
1930
|
|
|
1825
1931
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
1826
1932
|
|
|
1827
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `assets:read`, `assets:community:read`, `assets:community:publish`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`
|
|
1933
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `storage:local`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `assets:read`, `assets:community:read`, `assets:community:publish`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`
|
|
1828
1934
|
|
|
1829
1935
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID (or a `{ capability }` selector, or an entry from `useTextModels()`) and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
1830
1936
|
|
|
@@ -1896,6 +2002,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
1896
2002
|
- `storage_read`: 300/minute
|
|
1897
2003
|
- `storage_list`, `storage_delete`: 60/minute
|
|
1898
2004
|
- Content reads (`useFiasContent()`): 120/minute — read related files together with `getMany` (up to 100 per call)
|
|
2005
|
+
- Asset byte reads (`useArcheAssetBytes()`): 120/minute — load a set of sounds with one `getMany` (up to 100 per call)
|
|
1899
2006
|
|
|
1900
2007
|
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.
|
|
1901
2008
|
|