@fias/create-fias-plugin 1.4.1 → 1.4.2
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 +328 -26
- package/templates/default/CLAUDE.md +328 -26
package/package.json
CHANGED
|
@@ -63,6 +63,28 @@ function MyComponent() {
|
|
|
63
63
|
- `fonts`: `{ body, heading, mono }` (font-family strings)
|
|
64
64
|
- `components`: `{ borderRadius, buttonRadius, cardRadius, inputRadius, shadowSm, shadowMd, shadowLg, borderWidth }`
|
|
65
65
|
|
|
66
|
+
### `useFiasVendoredAssets(importSpecifier)` — Self-hosted vendored-library asset URLs
|
|
67
|
+
|
|
68
|
+
**Permission:** `sandbox:vendored-libraries`
|
|
69
|
+
**Returns:** `Record<string, string> | null` (null when the host CDN is unconfigured or the library isn't vendored)
|
|
70
|
+
|
|
71
|
+
Some heavy libraries (pdf.js today; WASM runtimes later) need a Web Worker and large asset files that a sandboxed plugin can't bundle or self-host. The platform vendors a curated set of these — version-pinned, served from its trusted CDN. A plugin opts in by (1) listing the library in its `dependencies` and (2) declaring the `sandbox:vendored-libraries` permission. The platform then widens the plugin CSP for exactly that library (e.g. pdf.js → `worker-src blob:`+CDN, `script-src`/`connect-src` CDN), pins the import specifier to the self-hosted build via the host importmap (so `import * as pdfjsLib from 'pdfjs-dist'` resolves to it), and delivers the library's asset URLs here. For `pdfjs-dist` you typically only need `worker` (feed it to `@fias/pdf-core`'s loader `workerModuleUrl`) plus `cMapUrl` / `standardFontDataUrl` for CID / non-embedded fonts.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useFiasVendoredAssets } from '@fias/arche-sdk';
|
|
75
|
+
import { loadPdfJs, configurePdfAssets } from '@fias/pdf-core/loader';
|
|
76
|
+
|
|
77
|
+
function PdfViewer() {
|
|
78
|
+
const assets = useFiasVendoredAssets('pdfjs-dist');
|
|
79
|
+
if (!assets) return <div>PDF rendering unavailable</div>;
|
|
80
|
+
configurePdfAssets({ cMapUrl: assets.cMapUrl, standardFontDataUrl: assets.standardFontDataUrl });
|
|
81
|
+
const pdfjs = await loadPdfJs({ workerModuleUrl: assets.worker }); // off-thread worker
|
|
82
|
+
// ...render with pdfjs
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**`pdfjs-dist` asset keys:** `{ mainModule, worker, cMapUrl, standardFontDataUrl }` — all absolute CDN URLs.
|
|
87
|
+
|
|
66
88
|
### `useFiasFonts()` — Platform font catalog
|
|
67
89
|
|
|
68
90
|
**Permission:** none
|
|
@@ -205,10 +227,120 @@ const doc = await fias.dataStore.get('scores', 'key');
|
|
|
205
227
|
- 100 MB total storage per plugin
|
|
206
228
|
- Max 100 results per query, max 10 filters, max field path depth 5
|
|
207
229
|
|
|
208
|
-
**
|
|
230
|
+
**Atomic batch writes:** `batch(operations)` applies up to 25 put/delete ops in a **single transaction** — all-or-nothing. Use it when several writes must stay consistent (e.g. append a ledger entry AND update a running total); a sequence of separate `put`/`delete` calls can leave a half-applied state. A batch may span collections and scopes (pass `workspaceId` per op); any failure (bad key, role denial, quota) rolls the whole batch back.
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
await dataStore.batch([
|
|
234
|
+
{ op: 'put', collection: 'ledger', key: 'tx-42', data: { delta: 100 } },
|
|
235
|
+
{ op: 'put', collection: 'totals', key: 'balance', data: { value: newBalance } },
|
|
236
|
+
]);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Rate limits (per minute):** `put` 120, `get` 300, `query` 60, `delete` 60, `batch` 30, `createCollection` 10
|
|
209
240
|
|
|
210
241
|
**Error handling:** Data store calls can reject on infrastructure errors or limit violations. Always use `try/catch`. Common error codes: `COLLECTION_NOT_FOUND`, `DOCUMENT_TOO_LARGE`, `STORAGE_LIMIT_REACHED`, `RATE_LIMIT`.
|
|
211
242
|
|
|
243
|
+
**Semantic search (`data:search`):** Make a collection searchable to do vector search over a text field. Declare it at creation with `searchable: { field }`, then call `search()`. Embeddings are managed by the platform — you never handle vectors or pick a model. Documents are embedded asynchronously, so a freshly-written doc becomes searchable a moment later (it's always immediately visible to `query`). The query embedding charges the caller's credits. Requires the `data:search` permission (distinct from `data:store`).
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// Create a searchable collection (embedding the `text` field) and publish docs.
|
|
247
|
+
await dataStore.createCollection('recipes', { userScope: 'shared', searchable: { field: 'text' } });
|
|
248
|
+
await dataStore.put('recipes', recipeId, { text: `${title} ${description}`, title, cuisine });
|
|
249
|
+
|
|
250
|
+
// Semantic search (charges the caller's credits for the query embedding).
|
|
251
|
+
const matches = await dataStore.search('recipes', 'quick vegetarian weeknight dinner', {
|
|
252
|
+
topK: 10,
|
|
253
|
+
filters: [{ field: 'cuisine', op: 'eq', value: 'italian' }], // vector + JSONB facet, ANDed
|
|
254
|
+
});
|
|
255
|
+
// matches: Array<{ key, data, similarity }>, best first
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Search limits:** `topK` ≤ 50, query ≤ 1024 chars, 50,000 embedded documents per arche; `search` is rate-limited to 20/min (each call embeds the query). Documents in non-searchable collections — and not-yet-embedded docs — are invisible to `search` but always returned by `query`.
|
|
259
|
+
|
|
260
|
+
### `useDataSubscription()` — Realtime collection change events
|
|
261
|
+
|
|
262
|
+
**Permission:** `data:store` (same as reading — live updates grant nothing polling couldn't)
|
|
263
|
+
**Returns:** `DataSubscriptionState` — `{ status: 'subscribing' | 'active' | 'ended', endedReason? }`
|
|
264
|
+
|
|
265
|
+
Get notified when a datastore collection changes, instead of manually refreshing. **Notify-then-refetch:** the platform pushes a minimal "this collection changed" signal and your `onInvalidate` callback re-runs whatever query your component renders from. Perfect for shared-scope collections where other users' writes should appear live (leaderboards, live scores, registrations).
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { useFiasDataStore, useDataSubscription } from '@fias/arche-sdk';
|
|
269
|
+
|
|
270
|
+
function Leaderboard() {
|
|
271
|
+
const dataStore = useFiasDataStore();
|
|
272
|
+
const [rows, setRows] = useState<ScoreRow[]>([]);
|
|
273
|
+
|
|
274
|
+
const refresh = useCallback(async () => {
|
|
275
|
+
const res = await dataStore.query<ScoreRow>('scores', {
|
|
276
|
+
orderBy: { field: 'score', direction: 'desc' },
|
|
277
|
+
limit: 20,
|
|
278
|
+
});
|
|
279
|
+
setRows(res.documents.map((d) => d.data));
|
|
280
|
+
}, [dataStore]);
|
|
281
|
+
|
|
282
|
+
// Refetches automatically whenever ANY user writes to `scores`.
|
|
283
|
+
const { status } = useDataSubscription('scores', refresh);
|
|
284
|
+
// status: 'subscribing' → 'active'; 'ended' + endedReason on teardown
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Semantics you can rely on:**
|
|
289
|
+
|
|
290
|
+
- The initial refetch fires after the subscription is live, so a write racing your mount is never missed.
|
|
291
|
+
- `onInvalidate` runs single-flight with one trailing call — event bursts coalesce, never parallel refetches.
|
|
292
|
+
- Delivery is best-effort (events can drop or duplicate) — always treat it as "time to refetch", never as data.
|
|
293
|
+
- Scope-aware: `shared` collections notify on any user's writes; `user`-scoped only on yours; `workspace`-scoped need `{ workspaceId }` and an active membership.
|
|
294
|
+
- When the subscription ends (`status: 'ended'`), `endedReason` tells you why: `collection_deleted` and `authorization_revoked` are terminal; `limit`, `transport_error`, and `lease_expired` may be worth resubscribing (remount or toggle the `enabled` option).
|
|
295
|
+
|
|
296
|
+
**Options:** `useDataSubscription(collection, onInvalidate, { workspaceId?, enabled? })`.
|
|
297
|
+
|
|
298
|
+
**Rate limit:** subscribe handshakes 60/min (renewals are automatic and budgeted within this). Up to 32 live subscriptions per tab.
|
|
299
|
+
|
|
300
|
+
### `useFiasWorkspaces()` — Team/org workspaces with roles
|
|
301
|
+
|
|
302
|
+
**Permission:** `data:workspace`
|
|
303
|
+
**Returns:** `FiasWorkspacesApi`
|
|
304
|
+
|
|
305
|
+
Model **multi-tenant data with roles**: a _workspace_ is a tenant (team/org space) inside your arche, with role-on-edge membership. A collection created with `userScope: 'workspace'` holds documents owned by a workspace and visible to its members, gated by role. Roles are `owner` > `admin` > `member` > `viewer`:
|
|
306
|
+
|
|
307
|
+
- **owner** — manage the workspace + members, read/write docs
|
|
308
|
+
- **admin** — manage non-owner members, read/write docs
|
|
309
|
+
- **member** — read/write docs
|
|
310
|
+
- **viewer** — read docs only
|
|
311
|
+
|
|
312
|
+
The creating user becomes the founding owner, and a workspace always keeps at least one owner (`MUST_KEEP_ONE_OWNER`). To read/write a workspace's documents, use `useFiasDataStore()` with the `{ workspaceId }` option (that still needs `data:store`, plus the server-side role gate).
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { useFiasWorkspaces, useFiasDataStore } from '@fias/arche-sdk';
|
|
316
|
+
|
|
317
|
+
function Team() {
|
|
318
|
+
const workspaces = useFiasWorkspaces();
|
|
319
|
+
const dataStore = useFiasDataStore();
|
|
320
|
+
|
|
321
|
+
async function setup() {
|
|
322
|
+
const ws = await workspaces.create('Acme Corp'); // caller becomes owner
|
|
323
|
+
await workspaces.addMember(ws.workspaceId, 'alice', 'member'); // members are addressed by username
|
|
324
|
+
|
|
325
|
+
// Workspace-scoped collection + document (role-gated server-side).
|
|
326
|
+
await dataStore.createCollection('ledger', { userScope: 'workspace' });
|
|
327
|
+
await dataStore.put('ledger', 'tx-1', { amount: 100 }, { workspaceId: ws.workspaceId });
|
|
328
|
+
|
|
329
|
+
// Read the workspace's ledger back: data store query with the workspace scope.
|
|
330
|
+
const securities = await dataStore.query(
|
|
331
|
+
'ledger',
|
|
332
|
+
{ orderBy: { field: 'shares', direction: 'desc' } },
|
|
333
|
+
{ workspaceId: ws.workspaceId },
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const mine = await workspaces.list(); // workspaces I belong to
|
|
337
|
+
const members = await workspaces.listMembers(ws.workspaceId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Methods:** `create`, `list`, `get`, `archive` (owner only), `listMembers`, `addMember`, `updateMember`, `removeMember`. `addMember(workspaceId, username, role, expiresAt?)` accepts an optional ISO-8601 `expiresAt` to grant **time-bounded** access (e.g. a contractor seat) — owners cannot expire; an expired member is treated as no longer a member. **Rate limits (per minute):** `create`/`archive` 20, member ops 30, `list`/`get`/`listMembers` 120. **Error codes:** `NOT_A_MEMBER`, `FORBIDDEN`, `ROLE_CEILING`, `MUST_KEEP_ONE_OWNER`, `WORKSPACE_NOT_FOUND`. Read workspace documents with `useFiasDataStore().get`/`query`/`search` passing `{ workspaceId }` (every active member can read; make the collection `searchable` to use `search`). **Per-collection role floors:** raise a workspace collection's minimum role at creation — `createCollection('board_minutes', { userScope: 'workspace', writeMinRole: 'admin', readMinRole: 'member' })` makes that collection admin-write / member-read, above the default (write ≥ member, read ≥ viewer).
|
|
343
|
+
|
|
212
344
|
### `useEntityInvocation()` — Invoke AI models
|
|
213
345
|
|
|
214
346
|
**Permission:** `entities:invoke`
|
|
@@ -412,6 +544,73 @@ const transparentPng = await removeBackground(jpegBlob);
|
|
|
412
544
|
|
|
413
545
|
**Limits:** input ≤ 25 MiB, PNG/JPEG only, rate-limited to 10/min per arche per user. Throws on size violation rather than returning a result.
|
|
414
546
|
|
|
547
|
+
### Image-editing capability hooks (auto-generated)
|
|
548
|
+
|
|
549
|
+
**Permission:** `entities:image_edit`
|
|
550
|
+
**Returns (per hook):** `{ invoke, isLoading, result, error }`
|
|
551
|
+
|
|
552
|
+
Mask-aware AI image-editing surfaces, generated from the platform's surface registry (same typed-hook pattern as audio). The source image and mask are passed **by handle** — `{ fileId }` (a Vault Document this arche owns), `{ archeAssetId }` (a published asset), or `{ dataUrl }` (inline base64; keep it small — use a handle for large source images). The edited result is saved to the user's Vault and returned as a presigned `imageUrl` you load onto your canvas.
|
|
553
|
+
|
|
554
|
+
```tsx
|
|
555
|
+
import { useSmartInpaint } from '@fias/arche-sdk';
|
|
556
|
+
|
|
557
|
+
const { invoke } = useSmartInpaint();
|
|
558
|
+
const result = await invoke({
|
|
559
|
+
image: { fileId: sourceDocId }, // or { dataUrl } / { archeAssetId }
|
|
560
|
+
mask: { dataUrl: maskPngDataUrl }, // alpha=255 = region to replace
|
|
561
|
+
prompt: 'a field of sunflowers',
|
|
562
|
+
engine: 'stability', // 'stability' | 'recraft' | 'openai'
|
|
563
|
+
});
|
|
564
|
+
// result: { imageUrl, imageRef, fileId, mimeType, costCredits }
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Long-running edits complete over WebSocket transparently — `await invoke(...)` resolves with the final result just like any other hook. Browse the full surface list with `npx fias-dev entities`.
|
|
568
|
+
|
|
569
|
+
Currently shipped image-edit hooks: `useSmartInpaint` (mask replace), `useSmartErase` (mask remove + fill), `useSmartOutpaint` (extend canvas), `useSmartSearchReplace` (prompt-targeted replace, no mask), `useSmartSearchRecolor` (prompt-targeted recolor), `useSmartVectorize` (raster → SVG), `useSmartRemoveBackground` (server-side bg removal), `useSmartImageToImage` (guided regenerate), `useSmartUpscale` (resolution increase), `useSmartControl` (sketch/structure/style control), `useSmartReplaceBackground` (swap + relight background). Mask-based hooks take an `image` + `mask`; search/recolor take an `image` + a text selector; outpaint takes an `image` + edge pixels; vectorize/remove-background/upscale take an `image` (+ mode/engine); control and replace-background accept additional reference images by handle.
|
|
570
|
+
|
|
571
|
+
### `useClientEntity()` — Invoke client-side (on-device) entities
|
|
572
|
+
|
|
573
|
+
**Permission:** `entities:client_invoke`
|
|
574
|
+
**Returns:** `ClientEntityApi`
|
|
575
|
+
|
|
576
|
+
The generic surface for any client-side entity — capabilities the host runs entirely in the browser (dictionary/WASM engines). Input never leaves the device and there is no per-call cost. The on-device counterpart to `useEntityInvocation()`. Target the entity by ID and supply its documented input/output types:
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
import {
|
|
580
|
+
useClientEntity,
|
|
581
|
+
GRAMMAR_CHECK_ENTITY_ID,
|
|
582
|
+
NLLB_TRANSLATION_ENTITY_ID,
|
|
583
|
+
NLLB_LANGUAGES,
|
|
584
|
+
} from '@fias/arche-sdk';
|
|
585
|
+
import type {
|
|
586
|
+
GrammarCheckInput,
|
|
587
|
+
GrammarCheckResult,
|
|
588
|
+
TranslateParams,
|
|
589
|
+
TranslationResult,
|
|
590
|
+
} from '@fias/arche-sdk';
|
|
591
|
+
|
|
592
|
+
const { invoke, isLoading, error } = useClientEntity();
|
|
593
|
+
|
|
594
|
+
// Grammar & spell check (non-AI nspell + retext engine):
|
|
595
|
+
const { issues } = await invoke<GrammarCheckInput, GrammarCheckResult>(GRAMMAR_CHECK_ENTITY_ID, {
|
|
596
|
+
text: 'teh quick brown fox',
|
|
597
|
+
categories: ['spelling', 'grammar', 'style', 'readability'], // optional; default = all
|
|
598
|
+
customDictionary: ['Fias'], // optional; words never flagged as misspelled
|
|
599
|
+
});
|
|
600
|
+
// issues: Array<{ start, end, message, category, ruleId, suggestions[] }>
|
|
601
|
+
|
|
602
|
+
// On-device translation (Meta NLLB-200 via transformers.js, free; the source
|
|
603
|
+
// language is auto-detected when omitted, target is a FLORES-200 code — pick
|
|
604
|
+
// from the exported NLLB_LANGUAGES label/code list):
|
|
605
|
+
const { translation, detectedSourceLang } = await invoke<TranslateParams, TranslationResult>(
|
|
606
|
+
NLLB_TRANSLATION_ENTITY_ID,
|
|
607
|
+
{ text: 'Hello', targetLang: 'spa_Latn' }, // pass sourceLang to skip detection
|
|
608
|
+
);
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**Grammar limits:** text ≤ 100,000 characters, rate-limited to 30/min per arche per user.
|
|
612
|
+
**Translation limits:** text ≤ 2,000 characters, rate-limited to 20/min per arche per user. The first call downloads the model (~600 MB, then browser-cached) and needs a reasonably capable device (~1–2 GB of browser memory); on constrained/mobile devices the load can fail — catch the error and offer `useEntityInvocation` (the AI engine) as a fallback.
|
|
613
|
+
|
|
415
614
|
### Audio capability hooks (auto-generated)
|
|
416
615
|
|
|
417
616
|
**Permission:** `entities:audio_generate`
|
|
@@ -441,6 +640,23 @@ const { invoke } = useSurface<MyParams, MyResult>('my.surface-key');
|
|
|
441
640
|
|
|
442
641
|
Default per-request timeout is 180 s (covers TTS for long text and Lyria multi-clip generation).
|
|
443
642
|
|
|
643
|
+
### `useIntegrationWebSearch()` — Live web search
|
|
644
|
+
|
|
645
|
+
**Permission:** `entities:web_search`
|
|
646
|
+
**Returns:** `{ invoke, isLoading, result, error }`
|
|
647
|
+
|
|
648
|
+
Search the live web and get a concise, cited summary. Backed by the swappable
|
|
649
|
+
`web-search` entity (the search provider can change without your code changing). The signed-in user is charged the standard 20% markup over the raw
|
|
650
|
+
search cost. Rate-limited to 10/min per arche per user.
|
|
651
|
+
|
|
652
|
+
```tsx
|
|
653
|
+
import { useIntegrationWebSearch } from '@fias/arche-sdk';
|
|
654
|
+
|
|
655
|
+
const { invoke, isLoading, result } = useIntegrationWebSearch();
|
|
656
|
+
await invoke({ query: 'latest EU AI Act enforcement dates' });
|
|
657
|
+
// result: { summary, sources: [{ url, title }], searchCount, costCredits }
|
|
658
|
+
```
|
|
659
|
+
|
|
444
660
|
### `useVaultDocuments()` — User-owned Vault documents
|
|
445
661
|
|
|
446
662
|
**Permissions:** `vault:documents:read` (list / read / getDownloadUrl / search), `vault:documents:write` (write / update / delete / attach / detach / upload)
|
|
@@ -499,6 +715,41 @@ await vault.detach(documentId, referenceId);
|
|
|
499
715
|
|
|
500
716
|
**`fetchVaultDocumentDownloadUrl(documentId)`** — non-React function that shares the hook's client-side URL cache. Use from PDF exporters, image preloaders, or other non-component code paths.
|
|
501
717
|
|
|
718
|
+
### `useVaultUserDocuments()` — User-granted access to the user's own documents
|
|
719
|
+
|
|
720
|
+
**Permission:** `vault:user-documents:read`
|
|
721
|
+
|
|
722
|
+
The only SDK surface that reaches data your plugin did NOT create: documents the user already owns in their Vault, shared with your plugin through an explicit, per-document consent flow. `useVaultDocuments` (above) is for documents your plugin creates; this hook is for documents the _user_ brings.
|
|
723
|
+
|
|
724
|
+
```tsx
|
|
725
|
+
import { useVaultUserDocuments } from '@fias/arche-sdk';
|
|
726
|
+
|
|
727
|
+
const userDocs = useVaultUserDocuments();
|
|
728
|
+
|
|
729
|
+
// 1. Ask the user to share documents. The HOST renders its own Vault picker
|
|
730
|
+
// + consent sheet — your plugin never sees the user's vault listing, only
|
|
731
|
+
// what they hand-pick and explicitly approve. Call from a button click.
|
|
732
|
+
const picked = await userDocs.pick({ maxDocuments: 5 });
|
|
733
|
+
if ('canceled' in picked) {
|
|
734
|
+
// A normal outcome — the user changed their mind. Never treat as an error.
|
|
735
|
+
} else {
|
|
736
|
+
// picked.documents: [{ documentId, name, mimeType, sizeBytes }]
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// 2. Later (any session): read what was granted.
|
|
740
|
+
const granted = await userDocs.list(); // all granted docs
|
|
741
|
+
const { document, content } = await userDocs.get(id, { includeContent: true }); // text docs
|
|
742
|
+
const { url } = await userDocs.getDownloadUrl(id); // binary docs (images, PDFs)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**The consent model (what to tell your users):**
|
|
746
|
+
|
|
747
|
+
- 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**.
|
|
748
|
+
- 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).
|
|
749
|
+
- `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
|
|
750
|
+
- 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.
|
|
751
|
+
- Not available in the builder preview or the dev harness — `pick()` resolves `{ canceled: true }` there. Test the flow in the published plugin with your own documents.
|
|
752
|
+
|
|
502
753
|
### `useArcheAssets()` — Contributor-published asset library
|
|
503
754
|
|
|
504
755
|
**Permission:** `assets:read`
|
|
@@ -579,10 +830,40 @@ function UnlockButton() {
|
|
|
579
830
|
```tsx
|
|
580
831
|
import { useFiasNavigation } from '@fias/arche-sdk';
|
|
581
832
|
|
|
582
|
-
const { navigateTo, currentPath } = useFiasNavigation();
|
|
583
|
-
navigateTo('/settings');
|
|
833
|
+
const { navigateTo, openArche, currentPath } = useFiasNavigation();
|
|
834
|
+
navigateTo('/settings'); // within THIS arche's route space
|
|
835
|
+
|
|
836
|
+
// Open ANOTHER arche's page. Requires the `navigation:open_arche` permission.
|
|
837
|
+
// Pass a stable arche id (`arc_…` / `arche_…`), not a slug. `newTab` is
|
|
838
|
+
// best-effort — the browser may block the popup, in which case the host
|
|
839
|
+
// navigates in the same tab instead.
|
|
840
|
+
openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
584
841
|
```
|
|
585
842
|
|
|
843
|
+
### Opening external links
|
|
844
|
+
|
|
845
|
+
To send the user to an external website (a YouTube video, docs, your homepage),
|
|
846
|
+
open it in a **new tab** with the standard web API — no SDK hook or permission
|
|
847
|
+
is needed:
|
|
848
|
+
|
|
849
|
+
```tsx
|
|
850
|
+
<button
|
|
851
|
+
onClick={() => window.open('https://youtube.com/watch?v=…', '_blank', 'noopener,noreferrer')}
|
|
852
|
+
>
|
|
853
|
+
Watch the video
|
|
854
|
+
</button>
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Notes:
|
|
858
|
+
|
|
859
|
+
- The plugin iframe grants `allow-popups`, so `window.open(url, '_blank')`
|
|
860
|
+
works. Opening in the **same** tab (or an `<a>` without `target="_blank"`)
|
|
861
|
+
does **not** — that would try to navigate the sandbox itself.
|
|
862
|
+
- There is **no** "leaving Fias" confirmation dialog — the tab opens directly.
|
|
863
|
+
Only send users to URLs you trust, and always pass `'noopener,noreferrer'`.
|
|
864
|
+
- Only `http(s)` URLs make sense here; `javascript:`/`data:` URLs are blocked
|
|
865
|
+
by the sandbox.
|
|
866
|
+
|
|
586
867
|
### `useStepNavigation()` — Multi-step workflows
|
|
587
868
|
|
|
588
869
|
**Permission:** None for in-memory use; `storage:sandbox` when `persistKey` is set (it reads/writes `__state/<persistKey>` via the storage bridge).
|
|
@@ -616,6 +897,26 @@ const [count, setCount] = usePersistentState<number>('counter', 0);
|
|
|
616
897
|
|
|
617
898
|
**Writes are debounced (SDK ≥ 1.8.0).** The in-memory value updates synchronously, but the underlying `storage_write` is coalesced (250 ms trailing-edge debounce, 1 s max-wait, flushed on unmount). Safe to call from `requestAnimationFrame` and other high-frequency handlers. For state that updates every frame (game positions, drag coordinates), still prefer plain `useState` — persisting transient state is wasteful and not useful on reload.
|
|
618
899
|
|
|
900
|
+
### `useFiasPreviewState()` — Survive builder-preview rebuilds
|
|
901
|
+
|
|
902
|
+
**Permission:** none.
|
|
903
|
+
|
|
904
|
+
In the arche builder, editing code (or a background cross-device sync) rebuilds the preview, which reloads the iframe and wipes the plugin's in-memory state — losing, for example, a report the user just generated. Opt that state into host-side preservation: just before the reload the host snapshots `getState()`, and after the reload it hands the value back to `onRestore`.
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
import { useFiasPreviewState } from '@fias/arche-sdk';
|
|
908
|
+
|
|
909
|
+
const [report, setReport] = useState<Report | null>(null);
|
|
910
|
+
// `key` namespaces this slice; `getState` must return JSON-safe state.
|
|
911
|
+
useFiasPreviewState(
|
|
912
|
+
'report',
|
|
913
|
+
() => report,
|
|
914
|
+
(saved) => setReport(saved as Report),
|
|
915
|
+
);
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
**Inert outside the builder preview** (the host never requests a capture), so it's safe to leave in production plugin code. Use it only for expensive-to-recreate transient state — not as a substitute for `usePersistentState` (which persists across full reloads and sessions).
|
|
919
|
+
|
|
619
920
|
### `fias` — Imperative utilities
|
|
620
921
|
|
|
621
922
|
Use the `fias` namespace when you need bridge operations from outside a React component (event handlers attached imperatively, utility modules, non-React entry points).
|
|
@@ -667,26 +968,26 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
667
968
|
|
|
668
969
|
**Fields:**
|
|
669
970
|
|
|
670
|
-
| Field | Req | Description
|
|
671
|
-
| --------------------- | ---- |
|
|
672
|
-
| `name` | Yes | Plugin identifier (lowercase, hyphens)
|
|
673
|
-
| `version` | Yes | Semver (e.g., `"1.0.0"`)
|
|
674
|
-
| `description` | Yes | Short marketplace description
|
|
675
|
-
| `expandedDescription` | No | Long-form marketplace copy
|
|
676
|
-
| `main` | Yes | Entry point source file
|
|
677
|
-
| `archeType` | Yes | `"tool"` or `"site"`
|
|
678
|
-
| `categorySlug` | No | Marketplace category for discovery
|
|
679
|
-
| `icon` | No |
|
|
680
|
-
| `tags` | No | Discovery tags
|
|
681
|
-
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products`
|
|
682
|
-
| `permissions` | Yes | Array of permission scopes (see below)
|
|
683
|
-
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below)
|
|
684
|
-
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below)
|
|
685
|
-
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"`
|
|
686
|
-
| `isListed` | No | Default `true`. Visible in marketplace store
|
|
687
|
-
| `sdk` | Yes | SDK version range
|
|
688
|
-
| `dependencies` | No | npm packages with **exact** versions (max 20)
|
|
689
|
-
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit
|
|
971
|
+
| Field | Req | Description |
|
|
972
|
+
| --------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
973
|
+
| `name` | Yes | Plugin identifier (lowercase, hyphens) |
|
|
974
|
+
| `version` | Yes | Semver (e.g., `"1.0.0"`) |
|
|
975
|
+
| `description` | Yes | Short marketplace description |
|
|
976
|
+
| `expandedDescription` | No | Long-form marketplace copy |
|
|
977
|
+
| `main` | Yes | Entry point source file |
|
|
978
|
+
| `archeType` | Yes | `"tool"` or `"site"` |
|
|
979
|
+
| `categorySlug` | No | Marketplace category for discovery |
|
|
980
|
+
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on local-dev submissions only; on staging/prod set the icon via the admin console. |
|
|
981
|
+
| `tags` | No | Discovery tags |
|
|
982
|
+
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products` |
|
|
983
|
+
| `permissions` | Yes | Array of permission scopes (see below) |
|
|
984
|
+
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below) |
|
|
985
|
+
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below) |
|
|
986
|
+
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"` |
|
|
987
|
+
| `isListed` | No | Default `true`. Visible in marketplace store |
|
|
988
|
+
| `sdk` | Yes | SDK version range |
|
|
989
|
+
| `dependencies` | No | npm packages with **exact** versions (max 20) |
|
|
990
|
+
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit |
|
|
690
991
|
|
|
691
992
|
**Pricing detail:**
|
|
692
993
|
|
|
@@ -701,7 +1002,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
701
1002
|
|
|
702
1003
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
703
1004
|
|
|
704
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `entities:invoke`, `entities:image_generate`, `entities:image_remove_background`, `entities:audio_generate`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `assets:read`
|
|
1005
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:image_remove_background`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `assets:read`, `navigation:open_arche`, `sandbox:vendored-libraries`
|
|
705
1006
|
|
|
706
1007
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
707
1008
|
|
|
@@ -737,7 +1038,7 @@ Add a `products` array to declare what users can buy inside the arche. Each entr
|
|
|
737
1038
|
|
|
738
1039
|
**Public arches (`isPublic: true`):**
|
|
739
1040
|
|
|
740
|
-
Setting `isPublic: true` lets anonymous visitors (no Fias account) view the arche at `/a/<archeId>`. The UI renders without the platform sidebar. **Bridge operations still require auth** — `useFiasDataStore` writes, `useFiasStore.purchase()`, `useFiasStorage` reads, vault operations, etc. all require the visitor to be signed in. Public mode is for read-only landing pages, marketing pages, and
|
|
1041
|
+
Setting `isPublic: true` lets anonymous visitors (no Fias account) view the arche at `/a/<archeId>`. The UI renders without the platform sidebar. **Bridge operations still require auth** — `useFiasDataStore` writes, `useFiasStore.purchase()`, `useFiasStorage` reads, vault operations, etc. all require the visitor to be signed in. Public mode is for read-only landing pages, marketing pages, and read-only catalog viewers — not for collecting registrations or payments from anonymous users.
|
|
741
1042
|
|
|
742
1043
|
## Plugin Constraints
|
|
743
1044
|
|
|
@@ -751,8 +1052,9 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
751
1052
|
|
|
752
1053
|
### Sandboxing
|
|
753
1054
|
|
|
754
|
-
- Plugins run in an iframe with `sandbox="allow-scripts allow-forms allow-
|
|
1055
|
+
- Plugins run in an iframe with `sandbox="allow-scripts allow-forms allow-downloads allow-popups allow-popups-to-escape-sandbox"` (note: **no** `allow-same-origin` — the plugin has an opaque origin)
|
|
755
1056
|
- **No `fetch()` or `XMLHttpRequest`** — all network access is blocked
|
|
1057
|
+
- **You _can_ open external links in a new tab** (e.g. `window.open('https://…', '_blank')`) — see "Opening external links" above
|
|
756
1058
|
- **No access** to parent DOM, cookies, or localStorage
|
|
757
1059
|
- **No external scripts or stylesheets** — everything must be bundled
|
|
758
1060
|
- All platform communication goes through the bridge (SDK hooks)
|
|
@@ -63,6 +63,28 @@ function MyComponent() {
|
|
|
63
63
|
- `fonts`: `{ body, heading, mono }` (font-family strings)
|
|
64
64
|
- `components`: `{ borderRadius, buttonRadius, cardRadius, inputRadius, shadowSm, shadowMd, shadowLg, borderWidth }`
|
|
65
65
|
|
|
66
|
+
### `useFiasVendoredAssets(importSpecifier)` — Self-hosted vendored-library asset URLs
|
|
67
|
+
|
|
68
|
+
**Permission:** `sandbox:vendored-libraries`
|
|
69
|
+
**Returns:** `Record<string, string> | null` (null when the host CDN is unconfigured or the library isn't vendored)
|
|
70
|
+
|
|
71
|
+
Some heavy libraries (pdf.js today; WASM runtimes later) need a Web Worker and large asset files that a sandboxed plugin can't bundle or self-host. The platform vendors a curated set of these — version-pinned, served from its trusted CDN. A plugin opts in by (1) listing the library in its `dependencies` and (2) declaring the `sandbox:vendored-libraries` permission. The platform then widens the plugin CSP for exactly that library (e.g. pdf.js → `worker-src blob:`+CDN, `script-src`/`connect-src` CDN), pins the import specifier to the self-hosted build via the host importmap (so `import * as pdfjsLib from 'pdfjs-dist'` resolves to it), and delivers the library's asset URLs here. For `pdfjs-dist` you typically only need `worker` (feed it to `@fias/pdf-core`'s loader `workerModuleUrl`) plus `cMapUrl` / `standardFontDataUrl` for CID / non-embedded fonts.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useFiasVendoredAssets } from '@fias/arche-sdk';
|
|
75
|
+
import { loadPdfJs, configurePdfAssets } from '@fias/pdf-core/loader';
|
|
76
|
+
|
|
77
|
+
function PdfViewer() {
|
|
78
|
+
const assets = useFiasVendoredAssets('pdfjs-dist');
|
|
79
|
+
if (!assets) return <div>PDF rendering unavailable</div>;
|
|
80
|
+
configurePdfAssets({ cMapUrl: assets.cMapUrl, standardFontDataUrl: assets.standardFontDataUrl });
|
|
81
|
+
const pdfjs = await loadPdfJs({ workerModuleUrl: assets.worker }); // off-thread worker
|
|
82
|
+
// ...render with pdfjs
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**`pdfjs-dist` asset keys:** `{ mainModule, worker, cMapUrl, standardFontDataUrl }` — all absolute CDN URLs.
|
|
87
|
+
|
|
66
88
|
### `useFiasFonts()` — Platform font catalog
|
|
67
89
|
|
|
68
90
|
**Permission:** none
|
|
@@ -205,10 +227,120 @@ const doc = await fias.dataStore.get('scores', 'key');
|
|
|
205
227
|
- 100 MB total storage per plugin
|
|
206
228
|
- Max 100 results per query, max 10 filters, max field path depth 5
|
|
207
229
|
|
|
208
|
-
**
|
|
230
|
+
**Atomic batch writes:** `batch(operations)` applies up to 25 put/delete ops in a **single transaction** — all-or-nothing. Use it when several writes must stay consistent (e.g. append a ledger entry AND update a running total); a sequence of separate `put`/`delete` calls can leave a half-applied state. A batch may span collections and scopes (pass `workspaceId` per op); any failure (bad key, role denial, quota) rolls the whole batch back.
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
await dataStore.batch([
|
|
234
|
+
{ op: 'put', collection: 'ledger', key: 'tx-42', data: { delta: 100 } },
|
|
235
|
+
{ op: 'put', collection: 'totals', key: 'balance', data: { value: newBalance } },
|
|
236
|
+
]);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Rate limits (per minute):** `put` 120, `get` 300, `query` 60, `delete` 60, `batch` 30, `createCollection` 10
|
|
209
240
|
|
|
210
241
|
**Error handling:** Data store calls can reject on infrastructure errors or limit violations. Always use `try/catch`. Common error codes: `COLLECTION_NOT_FOUND`, `DOCUMENT_TOO_LARGE`, `STORAGE_LIMIT_REACHED`, `RATE_LIMIT`.
|
|
211
242
|
|
|
243
|
+
**Semantic search (`data:search`):** Make a collection searchable to do vector search over a text field. Declare it at creation with `searchable: { field }`, then call `search()`. Embeddings are managed by the platform — you never handle vectors or pick a model. Documents are embedded asynchronously, so a freshly-written doc becomes searchable a moment later (it's always immediately visible to `query`). The query embedding charges the caller's credits. Requires the `data:search` permission (distinct from `data:store`).
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// Create a searchable collection (embedding the `text` field) and publish docs.
|
|
247
|
+
await dataStore.createCollection('recipes', { userScope: 'shared', searchable: { field: 'text' } });
|
|
248
|
+
await dataStore.put('recipes', recipeId, { text: `${title} ${description}`, title, cuisine });
|
|
249
|
+
|
|
250
|
+
// Semantic search (charges the caller's credits for the query embedding).
|
|
251
|
+
const matches = await dataStore.search('recipes', 'quick vegetarian weeknight dinner', {
|
|
252
|
+
topK: 10,
|
|
253
|
+
filters: [{ field: 'cuisine', op: 'eq', value: 'italian' }], // vector + JSONB facet, ANDed
|
|
254
|
+
});
|
|
255
|
+
// matches: Array<{ key, data, similarity }>, best first
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Search limits:** `topK` ≤ 50, query ≤ 1024 chars, 50,000 embedded documents per arche; `search` is rate-limited to 20/min (each call embeds the query). Documents in non-searchable collections — and not-yet-embedded docs — are invisible to `search` but always returned by `query`.
|
|
259
|
+
|
|
260
|
+
### `useDataSubscription()` — Realtime collection change events
|
|
261
|
+
|
|
262
|
+
**Permission:** `data:store` (same as reading — live updates grant nothing polling couldn't)
|
|
263
|
+
**Returns:** `DataSubscriptionState` — `{ status: 'subscribing' | 'active' | 'ended', endedReason? }`
|
|
264
|
+
|
|
265
|
+
Get notified when a datastore collection changes, instead of manually refreshing. **Notify-then-refetch:** the platform pushes a minimal "this collection changed" signal and your `onInvalidate` callback re-runs whatever query your component renders from. Perfect for shared-scope collections where other users' writes should appear live (leaderboards, live scores, registrations).
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { useFiasDataStore, useDataSubscription } from '@fias/arche-sdk';
|
|
269
|
+
|
|
270
|
+
function Leaderboard() {
|
|
271
|
+
const dataStore = useFiasDataStore();
|
|
272
|
+
const [rows, setRows] = useState<ScoreRow[]>([]);
|
|
273
|
+
|
|
274
|
+
const refresh = useCallback(async () => {
|
|
275
|
+
const res = await dataStore.query<ScoreRow>('scores', {
|
|
276
|
+
orderBy: { field: 'score', direction: 'desc' },
|
|
277
|
+
limit: 20,
|
|
278
|
+
});
|
|
279
|
+
setRows(res.documents.map((d) => d.data));
|
|
280
|
+
}, [dataStore]);
|
|
281
|
+
|
|
282
|
+
// Refetches automatically whenever ANY user writes to `scores`.
|
|
283
|
+
const { status } = useDataSubscription('scores', refresh);
|
|
284
|
+
// status: 'subscribing' → 'active'; 'ended' + endedReason on teardown
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Semantics you can rely on:**
|
|
289
|
+
|
|
290
|
+
- The initial refetch fires after the subscription is live, so a write racing your mount is never missed.
|
|
291
|
+
- `onInvalidate` runs single-flight with one trailing call — event bursts coalesce, never parallel refetches.
|
|
292
|
+
- Delivery is best-effort (events can drop or duplicate) — always treat it as "time to refetch", never as data.
|
|
293
|
+
- Scope-aware: `shared` collections notify on any user's writes; `user`-scoped only on yours; `workspace`-scoped need `{ workspaceId }` and an active membership.
|
|
294
|
+
- When the subscription ends (`status: 'ended'`), `endedReason` tells you why: `collection_deleted` and `authorization_revoked` are terminal; `limit`, `transport_error`, and `lease_expired` may be worth resubscribing (remount or toggle the `enabled` option).
|
|
295
|
+
|
|
296
|
+
**Options:** `useDataSubscription(collection, onInvalidate, { workspaceId?, enabled? })`.
|
|
297
|
+
|
|
298
|
+
**Rate limit:** subscribe handshakes 60/min (renewals are automatic and budgeted within this). Up to 32 live subscriptions per tab.
|
|
299
|
+
|
|
300
|
+
### `useFiasWorkspaces()` — Team/org workspaces with roles
|
|
301
|
+
|
|
302
|
+
**Permission:** `data:workspace`
|
|
303
|
+
**Returns:** `FiasWorkspacesApi`
|
|
304
|
+
|
|
305
|
+
Model **multi-tenant data with roles**: a _workspace_ is a tenant (team/org space) inside your arche, with role-on-edge membership. A collection created with `userScope: 'workspace'` holds documents owned by a workspace and visible to its members, gated by role. Roles are `owner` > `admin` > `member` > `viewer`:
|
|
306
|
+
|
|
307
|
+
- **owner** — manage the workspace + members, read/write docs
|
|
308
|
+
- **admin** — manage non-owner members, read/write docs
|
|
309
|
+
- **member** — read/write docs
|
|
310
|
+
- **viewer** — read docs only
|
|
311
|
+
|
|
312
|
+
The creating user becomes the founding owner, and a workspace always keeps at least one owner (`MUST_KEEP_ONE_OWNER`). To read/write a workspace's documents, use `useFiasDataStore()` with the `{ workspaceId }` option (that still needs `data:store`, plus the server-side role gate).
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { useFiasWorkspaces, useFiasDataStore } from '@fias/arche-sdk';
|
|
316
|
+
|
|
317
|
+
function Team() {
|
|
318
|
+
const workspaces = useFiasWorkspaces();
|
|
319
|
+
const dataStore = useFiasDataStore();
|
|
320
|
+
|
|
321
|
+
async function setup() {
|
|
322
|
+
const ws = await workspaces.create('Acme Corp'); // caller becomes owner
|
|
323
|
+
await workspaces.addMember(ws.workspaceId, 'alice', 'member'); // members are addressed by username
|
|
324
|
+
|
|
325
|
+
// Workspace-scoped collection + document (role-gated server-side).
|
|
326
|
+
await dataStore.createCollection('ledger', { userScope: 'workspace' });
|
|
327
|
+
await dataStore.put('ledger', 'tx-1', { amount: 100 }, { workspaceId: ws.workspaceId });
|
|
328
|
+
|
|
329
|
+
// Read the workspace's ledger back: data store query with the workspace scope.
|
|
330
|
+
const securities = await dataStore.query(
|
|
331
|
+
'ledger',
|
|
332
|
+
{ orderBy: { field: 'shares', direction: 'desc' } },
|
|
333
|
+
{ workspaceId: ws.workspaceId },
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const mine = await workspaces.list(); // workspaces I belong to
|
|
337
|
+
const members = await workspaces.listMembers(ws.workspaceId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Methods:** `create`, `list`, `get`, `archive` (owner only), `listMembers`, `addMember`, `updateMember`, `removeMember`. `addMember(workspaceId, username, role, expiresAt?)` accepts an optional ISO-8601 `expiresAt` to grant **time-bounded** access (e.g. a contractor seat) — owners cannot expire; an expired member is treated as no longer a member. **Rate limits (per minute):** `create`/`archive` 20, member ops 30, `list`/`get`/`listMembers` 120. **Error codes:** `NOT_A_MEMBER`, `FORBIDDEN`, `ROLE_CEILING`, `MUST_KEEP_ONE_OWNER`, `WORKSPACE_NOT_FOUND`. Read workspace documents with `useFiasDataStore().get`/`query`/`search` passing `{ workspaceId }` (every active member can read; make the collection `searchable` to use `search`). **Per-collection role floors:** raise a workspace collection's minimum role at creation — `createCollection('board_minutes', { userScope: 'workspace', writeMinRole: 'admin', readMinRole: 'member' })` makes that collection admin-write / member-read, above the default (write ≥ member, read ≥ viewer).
|
|
343
|
+
|
|
212
344
|
### `useEntityInvocation()` — Invoke AI models
|
|
213
345
|
|
|
214
346
|
**Permission:** `entities:invoke`
|
|
@@ -412,6 +544,73 @@ const transparentPng = await removeBackground(jpegBlob);
|
|
|
412
544
|
|
|
413
545
|
**Limits:** input ≤ 25 MiB, PNG/JPEG only, rate-limited to 10/min per arche per user. Throws on size violation rather than returning a result.
|
|
414
546
|
|
|
547
|
+
### Image-editing capability hooks (auto-generated)
|
|
548
|
+
|
|
549
|
+
**Permission:** `entities:image_edit`
|
|
550
|
+
**Returns (per hook):** `{ invoke, isLoading, result, error }`
|
|
551
|
+
|
|
552
|
+
Mask-aware AI image-editing surfaces, generated from the platform's surface registry (same typed-hook pattern as audio). The source image and mask are passed **by handle** — `{ fileId }` (a Vault Document this arche owns), `{ archeAssetId }` (a published asset), or `{ dataUrl }` (inline base64; keep it small — use a handle for large source images). The edited result is saved to the user's Vault and returned as a presigned `imageUrl` you load onto your canvas.
|
|
553
|
+
|
|
554
|
+
```tsx
|
|
555
|
+
import { useSmartInpaint } from '@fias/arche-sdk';
|
|
556
|
+
|
|
557
|
+
const { invoke } = useSmartInpaint();
|
|
558
|
+
const result = await invoke({
|
|
559
|
+
image: { fileId: sourceDocId }, // or { dataUrl } / { archeAssetId }
|
|
560
|
+
mask: { dataUrl: maskPngDataUrl }, // alpha=255 = region to replace
|
|
561
|
+
prompt: 'a field of sunflowers',
|
|
562
|
+
engine: 'stability', // 'stability' | 'recraft' | 'openai'
|
|
563
|
+
});
|
|
564
|
+
// result: { imageUrl, imageRef, fileId, mimeType, costCredits }
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Long-running edits complete over WebSocket transparently — `await invoke(...)` resolves with the final result just like any other hook. Browse the full surface list with `npx fias-dev entities`.
|
|
568
|
+
|
|
569
|
+
Currently shipped image-edit hooks: `useSmartInpaint` (mask replace), `useSmartErase` (mask remove + fill), `useSmartOutpaint` (extend canvas), `useSmartSearchReplace` (prompt-targeted replace, no mask), `useSmartSearchRecolor` (prompt-targeted recolor), `useSmartVectorize` (raster → SVG), `useSmartRemoveBackground` (server-side bg removal), `useSmartImageToImage` (guided regenerate), `useSmartUpscale` (resolution increase), `useSmartControl` (sketch/structure/style control), `useSmartReplaceBackground` (swap + relight background). Mask-based hooks take an `image` + `mask`; search/recolor take an `image` + a text selector; outpaint takes an `image` + edge pixels; vectorize/remove-background/upscale take an `image` (+ mode/engine); control and replace-background accept additional reference images by handle.
|
|
570
|
+
|
|
571
|
+
### `useClientEntity()` — Invoke client-side (on-device) entities
|
|
572
|
+
|
|
573
|
+
**Permission:** `entities:client_invoke`
|
|
574
|
+
**Returns:** `ClientEntityApi`
|
|
575
|
+
|
|
576
|
+
The generic surface for any client-side entity — capabilities the host runs entirely in the browser (dictionary/WASM engines). Input never leaves the device and there is no per-call cost. The on-device counterpart to `useEntityInvocation()`. Target the entity by ID and supply its documented input/output types:
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
import {
|
|
580
|
+
useClientEntity,
|
|
581
|
+
GRAMMAR_CHECK_ENTITY_ID,
|
|
582
|
+
NLLB_TRANSLATION_ENTITY_ID,
|
|
583
|
+
NLLB_LANGUAGES,
|
|
584
|
+
} from '@fias/arche-sdk';
|
|
585
|
+
import type {
|
|
586
|
+
GrammarCheckInput,
|
|
587
|
+
GrammarCheckResult,
|
|
588
|
+
TranslateParams,
|
|
589
|
+
TranslationResult,
|
|
590
|
+
} from '@fias/arche-sdk';
|
|
591
|
+
|
|
592
|
+
const { invoke, isLoading, error } = useClientEntity();
|
|
593
|
+
|
|
594
|
+
// Grammar & spell check (non-AI nspell + retext engine):
|
|
595
|
+
const { issues } = await invoke<GrammarCheckInput, GrammarCheckResult>(GRAMMAR_CHECK_ENTITY_ID, {
|
|
596
|
+
text: 'teh quick brown fox',
|
|
597
|
+
categories: ['spelling', 'grammar', 'style', 'readability'], // optional; default = all
|
|
598
|
+
customDictionary: ['Fias'], // optional; words never flagged as misspelled
|
|
599
|
+
});
|
|
600
|
+
// issues: Array<{ start, end, message, category, ruleId, suggestions[] }>
|
|
601
|
+
|
|
602
|
+
// On-device translation (Meta NLLB-200 via transformers.js, free; the source
|
|
603
|
+
// language is auto-detected when omitted, target is a FLORES-200 code — pick
|
|
604
|
+
// from the exported NLLB_LANGUAGES label/code list):
|
|
605
|
+
const { translation, detectedSourceLang } = await invoke<TranslateParams, TranslationResult>(
|
|
606
|
+
NLLB_TRANSLATION_ENTITY_ID,
|
|
607
|
+
{ text: 'Hello', targetLang: 'spa_Latn' }, // pass sourceLang to skip detection
|
|
608
|
+
);
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**Grammar limits:** text ≤ 100,000 characters, rate-limited to 30/min per arche per user.
|
|
612
|
+
**Translation limits:** text ≤ 2,000 characters, rate-limited to 20/min per arche per user. The first call downloads the model (~600 MB, then browser-cached) and needs a reasonably capable device (~1–2 GB of browser memory); on constrained/mobile devices the load can fail — catch the error and offer `useEntityInvocation` (the AI engine) as a fallback.
|
|
613
|
+
|
|
415
614
|
### Audio capability hooks (auto-generated)
|
|
416
615
|
|
|
417
616
|
**Permission:** `entities:audio_generate`
|
|
@@ -441,6 +640,23 @@ const { invoke } = useSurface<MyParams, MyResult>('my.surface-key');
|
|
|
441
640
|
|
|
442
641
|
Default per-request timeout is 180 s (covers TTS for long text and Lyria multi-clip generation).
|
|
443
642
|
|
|
643
|
+
### `useIntegrationWebSearch()` — Live web search
|
|
644
|
+
|
|
645
|
+
**Permission:** `entities:web_search`
|
|
646
|
+
**Returns:** `{ invoke, isLoading, result, error }`
|
|
647
|
+
|
|
648
|
+
Search the live web and get a concise, cited summary. Backed by the swappable
|
|
649
|
+
`web-search` entity (the search provider can change without your code changing). The signed-in user is charged the standard 20% markup over the raw
|
|
650
|
+
search cost. Rate-limited to 10/min per arche per user.
|
|
651
|
+
|
|
652
|
+
```tsx
|
|
653
|
+
import { useIntegrationWebSearch } from '@fias/arche-sdk';
|
|
654
|
+
|
|
655
|
+
const { invoke, isLoading, result } = useIntegrationWebSearch();
|
|
656
|
+
await invoke({ query: 'latest EU AI Act enforcement dates' });
|
|
657
|
+
// result: { summary, sources: [{ url, title }], searchCount, costCredits }
|
|
658
|
+
```
|
|
659
|
+
|
|
444
660
|
### `useVaultDocuments()` — User-owned Vault documents
|
|
445
661
|
|
|
446
662
|
**Permissions:** `vault:documents:read` (list / read / getDownloadUrl / search), `vault:documents:write` (write / update / delete / attach / detach / upload)
|
|
@@ -499,6 +715,41 @@ await vault.detach(documentId, referenceId);
|
|
|
499
715
|
|
|
500
716
|
**`fetchVaultDocumentDownloadUrl(documentId)`** — non-React function that shares the hook's client-side URL cache. Use from PDF exporters, image preloaders, or other non-component code paths.
|
|
501
717
|
|
|
718
|
+
### `useVaultUserDocuments()` — User-granted access to the user's own documents
|
|
719
|
+
|
|
720
|
+
**Permission:** `vault:user-documents:read`
|
|
721
|
+
|
|
722
|
+
The only SDK surface that reaches data your plugin did NOT create: documents the user already owns in their Vault, shared with your plugin through an explicit, per-document consent flow. `useVaultDocuments` (above) is for documents your plugin creates; this hook is for documents the _user_ brings.
|
|
723
|
+
|
|
724
|
+
```tsx
|
|
725
|
+
import { useVaultUserDocuments } from '@fias/arche-sdk';
|
|
726
|
+
|
|
727
|
+
const userDocs = useVaultUserDocuments();
|
|
728
|
+
|
|
729
|
+
// 1. Ask the user to share documents. The HOST renders its own Vault picker
|
|
730
|
+
// + consent sheet — your plugin never sees the user's vault listing, only
|
|
731
|
+
// what they hand-pick and explicitly approve. Call from a button click.
|
|
732
|
+
const picked = await userDocs.pick({ maxDocuments: 5 });
|
|
733
|
+
if ('canceled' in picked) {
|
|
734
|
+
// A normal outcome — the user changed their mind. Never treat as an error.
|
|
735
|
+
} else {
|
|
736
|
+
// picked.documents: [{ documentId, name, mimeType, sizeBytes }]
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// 2. Later (any session): read what was granted.
|
|
740
|
+
const granted = await userDocs.list(); // all granted docs
|
|
741
|
+
const { document, content } = await userDocs.get(id, { includeContent: true }); // text docs
|
|
742
|
+
const { url } = await userDocs.getDownloadUrl(id); // binary docs (images, PDFs)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**The consent model (what to tell your users):**
|
|
746
|
+
|
|
747
|
+
- 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**.
|
|
748
|
+
- 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).
|
|
749
|
+
- `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
|
|
750
|
+
- 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.
|
|
751
|
+
- Not available in the builder preview or the dev harness — `pick()` resolves `{ canceled: true }` there. Test the flow in the published plugin with your own documents.
|
|
752
|
+
|
|
502
753
|
### `useArcheAssets()` — Contributor-published asset library
|
|
503
754
|
|
|
504
755
|
**Permission:** `assets:read`
|
|
@@ -579,10 +830,40 @@ function UnlockButton() {
|
|
|
579
830
|
```tsx
|
|
580
831
|
import { useFiasNavigation } from '@fias/arche-sdk';
|
|
581
832
|
|
|
582
|
-
const { navigateTo, currentPath } = useFiasNavigation();
|
|
583
|
-
navigateTo('/settings');
|
|
833
|
+
const { navigateTo, openArche, currentPath } = useFiasNavigation();
|
|
834
|
+
navigateTo('/settings'); // within THIS arche's route space
|
|
835
|
+
|
|
836
|
+
// Open ANOTHER arche's page. Requires the `navigation:open_arche` permission.
|
|
837
|
+
// Pass a stable arche id (`arc_…` / `arche_…`), not a slug. `newTab` is
|
|
838
|
+
// best-effort — the browser may block the popup, in which case the host
|
|
839
|
+
// navigates in the same tab instead.
|
|
840
|
+
openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
584
841
|
```
|
|
585
842
|
|
|
843
|
+
### Opening external links
|
|
844
|
+
|
|
845
|
+
To send the user to an external website (a YouTube video, docs, your homepage),
|
|
846
|
+
open it in a **new tab** with the standard web API — no SDK hook or permission
|
|
847
|
+
is needed:
|
|
848
|
+
|
|
849
|
+
```tsx
|
|
850
|
+
<button
|
|
851
|
+
onClick={() => window.open('https://youtube.com/watch?v=…', '_blank', 'noopener,noreferrer')}
|
|
852
|
+
>
|
|
853
|
+
Watch the video
|
|
854
|
+
</button>
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Notes:
|
|
858
|
+
|
|
859
|
+
- The plugin iframe grants `allow-popups`, so `window.open(url, '_blank')`
|
|
860
|
+
works. Opening in the **same** tab (or an `<a>` without `target="_blank"`)
|
|
861
|
+
does **not** — that would try to navigate the sandbox itself.
|
|
862
|
+
- There is **no** "leaving Fias" confirmation dialog — the tab opens directly.
|
|
863
|
+
Only send users to URLs you trust, and always pass `'noopener,noreferrer'`.
|
|
864
|
+
- Only `http(s)` URLs make sense here; `javascript:`/`data:` URLs are blocked
|
|
865
|
+
by the sandbox.
|
|
866
|
+
|
|
586
867
|
### `useStepNavigation()` — Multi-step workflows
|
|
587
868
|
|
|
588
869
|
**Permission:** None for in-memory use; `storage:sandbox` when `persistKey` is set (it reads/writes `__state/<persistKey>` via the storage bridge).
|
|
@@ -616,6 +897,26 @@ const [count, setCount] = usePersistentState<number>('counter', 0);
|
|
|
616
897
|
|
|
617
898
|
**Writes are debounced (SDK ≥ 1.8.0).** The in-memory value updates synchronously, but the underlying `storage_write` is coalesced (250 ms trailing-edge debounce, 1 s max-wait, flushed on unmount). Safe to call from `requestAnimationFrame` and other high-frequency handlers. For state that updates every frame (game positions, drag coordinates), still prefer plain `useState` — persisting transient state is wasteful and not useful on reload.
|
|
618
899
|
|
|
900
|
+
### `useFiasPreviewState()` — Survive builder-preview rebuilds
|
|
901
|
+
|
|
902
|
+
**Permission:** none.
|
|
903
|
+
|
|
904
|
+
In the arche builder, editing code (or a background cross-device sync) rebuilds the preview, which reloads the iframe and wipes the plugin's in-memory state — losing, for example, a report the user just generated. Opt that state into host-side preservation: just before the reload the host snapshots `getState()`, and after the reload it hands the value back to `onRestore`.
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
import { useFiasPreviewState } from '@fias/arche-sdk';
|
|
908
|
+
|
|
909
|
+
const [report, setReport] = useState<Report | null>(null);
|
|
910
|
+
// `key` namespaces this slice; `getState` must return JSON-safe state.
|
|
911
|
+
useFiasPreviewState(
|
|
912
|
+
'report',
|
|
913
|
+
() => report,
|
|
914
|
+
(saved) => setReport(saved as Report),
|
|
915
|
+
);
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
**Inert outside the builder preview** (the host never requests a capture), so it's safe to leave in production plugin code. Use it only for expensive-to-recreate transient state — not as a substitute for `usePersistentState` (which persists across full reloads and sessions).
|
|
919
|
+
|
|
619
920
|
### `fias` — Imperative utilities
|
|
620
921
|
|
|
621
922
|
Use the `fias` namespace when you need bridge operations from outside a React component (event handlers attached imperatively, utility modules, non-React entry points).
|
|
@@ -667,26 +968,26 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
667
968
|
|
|
668
969
|
**Fields:**
|
|
669
970
|
|
|
670
|
-
| Field | Req | Description
|
|
671
|
-
| --------------------- | ---- |
|
|
672
|
-
| `name` | Yes | Plugin identifier (lowercase, hyphens)
|
|
673
|
-
| `version` | Yes | Semver (e.g., `"1.0.0"`)
|
|
674
|
-
| `description` | Yes | Short marketplace description
|
|
675
|
-
| `expandedDescription` | No | Long-form marketplace copy
|
|
676
|
-
| `main` | Yes | Entry point source file
|
|
677
|
-
| `archeType` | Yes | `"tool"` or `"site"`
|
|
678
|
-
| `categorySlug` | No | Marketplace category for discovery
|
|
679
|
-
| `icon` | No |
|
|
680
|
-
| `tags` | No | Discovery tags
|
|
681
|
-
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products`
|
|
682
|
-
| `permissions` | Yes | Array of permission scopes (see below)
|
|
683
|
-
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below)
|
|
684
|
-
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below)
|
|
685
|
-
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"`
|
|
686
|
-
| `isListed` | No | Default `true`. Visible in marketplace store
|
|
687
|
-
| `sdk` | Yes | SDK version range
|
|
688
|
-
| `dependencies` | No | npm packages with **exact** versions (max 20)
|
|
689
|
-
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit
|
|
971
|
+
| Field | Req | Description |
|
|
972
|
+
| --------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
973
|
+
| `name` | Yes | Plugin identifier (lowercase, hyphens) |
|
|
974
|
+
| `version` | Yes | Semver (e.g., `"1.0.0"`) |
|
|
975
|
+
| `description` | Yes | Short marketplace description |
|
|
976
|
+
| `expandedDescription` | No | Long-form marketplace copy |
|
|
977
|
+
| `main` | Yes | Entry point source file |
|
|
978
|
+
| `archeType` | Yes | `"tool"` or `"site"` |
|
|
979
|
+
| `categorySlug` | No | Marketplace category for discovery |
|
|
980
|
+
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on local-dev submissions only; on staging/prod set the icon via the admin console. |
|
|
981
|
+
| `tags` | No | Discovery tags |
|
|
982
|
+
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products` |
|
|
983
|
+
| `permissions` | Yes | Array of permission scopes (see below) |
|
|
984
|
+
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below) |
|
|
985
|
+
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below) |
|
|
986
|
+
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"` |
|
|
987
|
+
| `isListed` | No | Default `true`. Visible in marketplace store |
|
|
988
|
+
| `sdk` | Yes | SDK version range |
|
|
989
|
+
| `dependencies` | No | npm packages with **exact** versions (max 20) |
|
|
990
|
+
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit |
|
|
690
991
|
|
|
691
992
|
**Pricing detail:**
|
|
692
993
|
|
|
@@ -701,7 +1002,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
701
1002
|
|
|
702
1003
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
703
1004
|
|
|
704
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `entities:invoke`, `entities:image_generate`, `entities:image_remove_background`, `entities:audio_generate`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `assets:read`
|
|
1005
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:image_remove_background`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `assets:read`, `navigation:open_arche`, `sandbox:vendored-libraries`
|
|
705
1006
|
|
|
706
1007
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
707
1008
|
|
|
@@ -737,7 +1038,7 @@ Add a `products` array to declare what users can buy inside the arche. Each entr
|
|
|
737
1038
|
|
|
738
1039
|
**Public arches (`isPublic: true`):**
|
|
739
1040
|
|
|
740
|
-
Setting `isPublic: true` lets anonymous visitors (no Fias account) view the arche at `/a/<archeId>`. The UI renders without the platform sidebar. **Bridge operations still require auth** — `useFiasDataStore` writes, `useFiasStore.purchase()`, `useFiasStorage` reads, vault operations, etc. all require the visitor to be signed in. Public mode is for read-only landing pages, marketing pages, and
|
|
1041
|
+
Setting `isPublic: true` lets anonymous visitors (no Fias account) view the arche at `/a/<archeId>`. The UI renders without the platform sidebar. **Bridge operations still require auth** — `useFiasDataStore` writes, `useFiasStore.purchase()`, `useFiasStorage` reads, vault operations, etc. all require the visitor to be signed in. Public mode is for read-only landing pages, marketing pages, and read-only catalog viewers — not for collecting registrations or payments from anonymous users.
|
|
741
1042
|
|
|
742
1043
|
## Plugin Constraints
|
|
743
1044
|
|
|
@@ -751,8 +1052,9 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
751
1052
|
|
|
752
1053
|
### Sandboxing
|
|
753
1054
|
|
|
754
|
-
- Plugins run in an iframe with `sandbox="allow-scripts allow-forms allow-
|
|
1055
|
+
- Plugins run in an iframe with `sandbox="allow-scripts allow-forms allow-downloads allow-popups allow-popups-to-escape-sandbox"` (note: **no** `allow-same-origin` — the plugin has an opaque origin)
|
|
755
1056
|
- **No `fetch()` or `XMLHttpRequest`** — all network access is blocked
|
|
1057
|
+
- **You _can_ open external links in a new tab** (e.g. `window.open('https://…', '_blank')`) — see "Opening external links" above
|
|
756
1058
|
- **No access** to parent DOM, cookies, or localStorage
|
|
757
1059
|
- **No external scripts or stylesheets** — everything must be bundled
|
|
758
1060
|
- All platform communication goes through the bridge (SDK hooks)
|