@fias/create-fias-plugin 1.11.2 → 1.12.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 +56 -22
- package/templates/default/CLAUDE.md +56 -22
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
1
|
+
<!-- fias-sdk-guide-version: 2.20.0 -->
|
|
2
2
|
|
|
3
3
|
# FIAS Plugin Development Guide
|
|
4
4
|
|
|
@@ -772,30 +772,26 @@ const { referenceId } = await vault.attach(documentId, {
|
|
|
772
772
|
await vault.detach(documentId, referenceId);
|
|
773
773
|
```
|
|
774
774
|
|
|
775
|
-
**Saving into the user's
|
|
775
|
+
**Saving into the user's Documents** — `upload(bytes, { ..., destination: 'documents' })` (`userVisible: true` is the same thing). By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
|
|
776
776
|
|
|
777
777
|
```tsx
|
|
778
778
|
const saved = await vault.upload(bytes, {
|
|
779
|
-
name: 'sunset.png',
|
|
779
|
+
name: 'sunset.png', // a suggestion — the user can change it
|
|
780
780
|
mimeType: 'image/png',
|
|
781
|
-
|
|
781
|
+
destination: 'documents',
|
|
782
782
|
});
|
|
783
|
+
// saved.documentId, saved.name (what the user kept), saved.access ('none' | 'read' | 'write')
|
|
783
784
|
```
|
|
784
785
|
|
|
785
|
-
|
|
786
|
+
The host opens its **Save dialog**: the user picks a folder in their Documents, can rename the file, and settles a name clash (keep both, or replace a file of the same type). You cannot script it, batch it, or pre-approve it, and you never learn the folder. Requires `vault:user-documents:write`. Cancelling rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. `folderPath`, `replacesDocumentId`, `tags` and `sensitivity` are rejected alongside it (the user decides those). Not available in preview or the dev harness — test it in the published plugin.
|
|
786
787
|
|
|
787
|
-
**
|
|
788
|
+
**Afterwards the file is the user's.** `useVaultDocuments()` no longer lists it. What you keep is reported as `access`: with `vault:user-documents:read` you can open it again (`'read'`) through `useVaultUserDocuments()`, and with `vault:user-documents:edit` the dialog offers the user "keep editing" (`'write'`) so you can keep saving it with `useVaultUserDocuments().saveContent`. The user can change either in Arche Access.
|
|
788
789
|
|
|
789
|
-
|
|
790
|
-
try {
|
|
791
|
-
await vault.promoteToUserFiles(documentId); // the HOST asks the user; you cannot script it
|
|
792
|
-
} catch (err) {
|
|
793
|
-
if (err instanceof FiasBridgeError && err.code === 'SAVE_DECLINED') return; // a normal outcome
|
|
794
|
-
throw err;
|
|
795
|
-
}
|
|
796
|
-
```
|
|
790
|
+
**Arche Saves, automatically** — `upload(bytes, { ..., destination: 'arche-saves' })` saves into the user's `Arche Saves/<your label>/` with no dialog. Only for an arche a Fias admin has designated (that issues its label, which is yours for good) and that declares `vault:arche-saves:write` (human-reviewed). Otherwise it rejects with `ARCHE_NOT_DESIGNATED` or `PERMISSION_DENIED`. The files are the user's to keep, move or delete; you can keep reading them while they stay in Arche Saves.
|
|
797
791
|
|
|
798
|
-
|
|
792
|
+
**`saveCopy(documentId, { suggestedName? })`** — hand the user a copy of one of your own documents through the same dialog, without re-sending the bytes (needs `vault:documents:read` too). Your working file is unchanged; delete it afterwards if you no longer need it.
|
|
793
|
+
|
|
794
|
+
**A document's visibility never changes.** Your private documents stay private, and a file in the user's files stays theirs — there is no call that moves one into the other.
|
|
799
795
|
|
|
800
796
|
**Sensitivity tiers** (`'standard'` | `'confidential'` | `'proprietary'`) gate downstream read access per the Vault's standard matrix. Defaults to `'standard'`.
|
|
801
797
|
|
|
@@ -834,6 +830,7 @@ const { bytes } = await userDocs.getBytes(id); // raw bytes for PROCESSING binar
|
|
|
834
830
|
- Grants are **per-document** and **standing**: the plugin can re-read granted documents in later sessions until the user removes access in **Vault → App Access**.
|
|
835
831
|
- After revocation, `get`/`getDownloadUrl` reject with `DOCUMENT_NOT_FOUND` — handle that path gracefully (drop the document from your UI; do not retry in a loop).
|
|
836
832
|
- `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
|
|
833
|
+
- **Say what you can open:** `pick({ accept: ['application/pdf', 'image/*', '.docx'] })` takes MIME types, MIME families or extensions, up to 20. The host's Open dialog offers the matching files; the user can still reveal the rest, shown disabled. A malformed list rejects with `INVALID_PARAMS`. Omitted, every type is offered.
|
|
837
834
|
- Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
|
|
838
835
|
- `getDownloadUrl` is for DISPLAY (`<img src>`); your iframe cannot fetch the URL (CSP). To process binary content (parse a PDF, transform an image), use `getBytes(id)` — size-capped at 15 MB (`CONSENTED_DOCUMENT_TOO_LARGE` names the cap; ask the user to open the file manually instead). `getBytes` has a tighter per-minute budget than the rest of the family because it can move megabytes: read sequentially, and surface a rate-limit error rather than retrying in a loop.
|
|
839
836
|
- Revocation behaves differently per method: `getBytes` and `get` re-check the grant on every call, so they start failing immediately. A URL already handed out by `getDownloadUrl` keeps working until it expires (1 hour) — that is the documented contract, not a bug.
|
|
@@ -1050,6 +1047,32 @@ const fresh = await getUrl(assetId);
|
|
|
1050
1047
|
|
|
1051
1048
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
1052
1049
|
|
|
1050
|
+
### `useFiasContent()` — This plugin's content pack
|
|
1051
|
+
|
|
1052
|
+
**Permission:** `entities:client_invoke`
|
|
1053
|
+
**Returns:** `FiasContentApi`
|
|
1054
|
+
|
|
1055
|
+
Ship large or numerous files — lessons, articles, data, images, audio, fonts — in a `content/` directory beside `src/`. They are not bundled: `fias-dev submit` uploads them separately (only the files that changed), the review reads every one, and the published plugin reads them at runtime through this hook. The host page fetches, verifies and caches them; your plugin never touches the network.
|
|
1056
|
+
|
|
1057
|
+
```tsx
|
|
1058
|
+
import { useFiasContent, FiasContentError } from '@fias/arche-sdk';
|
|
1059
|
+
|
|
1060
|
+
const content = useFiasContent();
|
|
1061
|
+
|
|
1062
|
+
const lessons = await content.list('lessons/'); // [{ path, sizeBytes, mimeType }]
|
|
1063
|
+
const markdown = await content.getText('lessons/01-intro.md');
|
|
1064
|
+
const course = await content.getJson<Course>('course.json');
|
|
1065
|
+
const batch = await content.getMany(['a.md', 'b.md'], 'text'); // up to 100 files per call
|
|
1066
|
+
const cover = await content.getObjectUrl('img/cover.webp'); // blob: URL for <img src>
|
|
1067
|
+
const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long streaming audio
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
- Paths are relative to `content/`. Formats: `.md`, `.txt`, `.json` (text, ≤ 5 MB each); `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.avif` (static, single-frame images, ≤ 20 MB and 50 megapixels); `.ogg` (audio, ≤ 50 MB); `.woff2` (fonts, ≤ 5 MB). A pack holds at most 10,000 files, 2,000 images, 20 MB of text and 500 MB in total. Run `npx fias-dev content check` to validate without submitting.
|
|
1071
|
+
- Prefer `getObjectUrl` for images and fonts: repeat views come from the host cache. `getUrl` is for media that must stream or seek; it bypasses the cache, and a URL held past its `expiresAt` must be requested again.
|
|
1072
|
+
- Failures throw `FiasContentError` with a `code`: `CONTENT_NOT_FOUND`, `CONTENT_NOT_TEXT`, `CONTENT_TOO_LARGE`, `CONTENT_UNAVAILABLE`, `CONTENT_THROTTLED`, `CONTENT_UNAVAILABLE_IN_PREVIEW`, `PERMISSION_DENIED`, `RATE_LIMIT`. `getMany` returns the error for a failed path instead of throwing.
|
|
1073
|
+
- 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.
|
|
1074
|
+
- 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.
|
|
1075
|
+
|
|
1053
1076
|
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
1054
1077
|
|
|
1055
1078
|
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
@@ -1159,18 +1182,29 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
|
1159
1182
|
openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
|
|
1160
1183
|
```
|
|
1161
1184
|
|
|
1162
|
-
**Receiving a deep link.** There is nothing extra to opt into: `currentPath`
|
|
1163
|
-
the
|
|
1164
|
-
that wants to be a deep-link target just
|
|
1185
|
+
**Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
|
|
1186
|
+
the host's path expressed in YOUR arche's route space — the same space
|
|
1187
|
+
`navigateTo` accepts — so an arche that wants to be a deep-link target just
|
|
1188
|
+
reads it at boot. The `/a/<arche>` prefix is stripped for you, and your home
|
|
1189
|
+
screen reads `/`.
|
|
1165
1190
|
|
|
1166
1191
|
```tsx
|
|
1167
1192
|
const { currentPath } = useFiasNavigation();
|
|
1168
|
-
//
|
|
1169
|
-
const page = currentPath.
|
|
1193
|
+
// a deep link to '/a/arc_0123…/map' arrives here as '/map'
|
|
1194
|
+
const page = currentPath.replace(/^\//, '') || 'home';
|
|
1170
1195
|
```
|
|
1171
1196
|
|
|
1172
|
-
It is
|
|
1173
|
-
|
|
1197
|
+
It is `''` until the host's `init` message lands — the bridge handshake is
|
|
1198
|
+
async, so your first render usually beats it. Treat `''` as "not known yet"
|
|
1199
|
+
rather than as your home screen; that distinction is what keeps a direct entry
|
|
1200
|
+
such as a share link from being lost to the race.
|
|
1201
|
+
|
|
1202
|
+
After that it TRACKS the host: it updates when you call `navigateTo`, and when
|
|
1203
|
+
the user moves the host themselves with browser back/forward. So a router
|
|
1204
|
+
driven off `currentPath` stays in step with the address bar, and the back
|
|
1205
|
+
button works the way your users expect — you do not have to mirror the path in
|
|
1206
|
+
your own state. The dev harness echoes navigations the same way, so what you
|
|
1207
|
+
see locally is what ships.
|
|
1174
1208
|
|
|
1175
1209
|
### Opening external links
|
|
1176
1210
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
1
|
+
<!-- fias-sdk-guide-version: 2.20.0 -->
|
|
2
2
|
|
|
3
3
|
# FIAS Plugin Development Guide
|
|
4
4
|
|
|
@@ -772,30 +772,26 @@ const { referenceId } = await vault.attach(documentId, {
|
|
|
772
772
|
await vault.detach(documentId, referenceId);
|
|
773
773
|
```
|
|
774
774
|
|
|
775
|
-
**Saving into the user's
|
|
775
|
+
**Saving into the user's Documents** — `upload(bytes, { ..., destination: 'documents' })` (`userVisible: true` is the same thing). By default everything your app writes here is _arche-private_: yours to read and write, invisible in the user's Fias files, unusable by other arches. That is the right home for your app's own state (caches, session snapshots, working files). It is the wrong home for the artifact the user made and asked you to keep — an edited image, an exported document — because they will go looking for it and it will not be there.
|
|
776
776
|
|
|
777
777
|
```tsx
|
|
778
778
|
const saved = await vault.upload(bytes, {
|
|
779
|
-
name: 'sunset.png',
|
|
779
|
+
name: 'sunset.png', // a suggestion — the user can change it
|
|
780
780
|
mimeType: 'image/png',
|
|
781
|
-
|
|
781
|
+
destination: 'documents',
|
|
782
782
|
});
|
|
783
|
+
// saved.documentId, saved.name (what the user kept), saved.access ('none' | 'read' | 'write')
|
|
783
784
|
```
|
|
784
785
|
|
|
785
|
-
|
|
786
|
+
The host opens its **Save dialog**: the user picks a folder in their Documents, can rename the file, and settles a name clash (keep both, or replace a file of the same type). You cannot script it, batch it, or pre-approve it, and you never learn the folder. Requires `vault:user-documents:write`. Cancelling rejects with `SAVE_DECLINED`; that is a normal outcome, not an error to retry. `folderPath`, `replacesDocumentId`, `tags` and `sensitivity` are rejected alongside it (the user decides those). Not available in preview or the dev harness — test it in the published plugin.
|
|
786
787
|
|
|
787
|
-
**
|
|
788
|
+
**Afterwards the file is the user's.** `useVaultDocuments()` no longer lists it. What you keep is reported as `access`: with `vault:user-documents:read` you can open it again (`'read'`) through `useVaultUserDocuments()`, and with `vault:user-documents:edit` the dialog offers the user "keep editing" (`'write'`) so you can keep saving it with `useVaultUserDocuments().saveContent`. The user can change either in Arche Access.
|
|
788
789
|
|
|
789
|
-
|
|
790
|
-
try {
|
|
791
|
-
await vault.promoteToUserFiles(documentId); // the HOST asks the user; you cannot script it
|
|
792
|
-
} catch (err) {
|
|
793
|
-
if (err instanceof FiasBridgeError && err.code === 'SAVE_DECLINED') return; // a normal outcome
|
|
794
|
-
throw err;
|
|
795
|
-
}
|
|
796
|
-
```
|
|
790
|
+
**Arche Saves, automatically** — `upload(bytes, { ..., destination: 'arche-saves' })` saves into the user's `Arche Saves/<your label>/` with no dialog. Only for an arche a Fias admin has designated (that issues its label, which is yours for good) and that declares `vault:arche-saves:write` (human-reviewed). Otherwise it rejects with `ARCHE_NOT_DESIGNATED` or `PERMISSION_DENIED`. The files are the user's to keep, move or delete; you can keep reading them while they stay in Arche Saves.
|
|
797
791
|
|
|
798
|
-
|
|
792
|
+
**`saveCopy(documentId, { suggestedName? })`** — hand the user a copy of one of your own documents through the same dialog, without re-sending the bytes (needs `vault:documents:read` too). Your working file is unchanged; delete it afterwards if you no longer need it.
|
|
793
|
+
|
|
794
|
+
**A document's visibility never changes.** Your private documents stay private, and a file in the user's files stays theirs — there is no call that moves one into the other.
|
|
799
795
|
|
|
800
796
|
**Sensitivity tiers** (`'standard'` | `'confidential'` | `'proprietary'`) gate downstream read access per the Vault's standard matrix. Defaults to `'standard'`.
|
|
801
797
|
|
|
@@ -834,6 +830,7 @@ const { bytes } = await userDocs.getBytes(id); // raw bytes for PROCESSING binar
|
|
|
834
830
|
- Grants are **per-document** and **standing**: the plugin can re-read granted documents in later sessions until the user removes access in **Vault → App Access**.
|
|
835
831
|
- After revocation, `get`/`getDownloadUrl` reject with `DOCUMENT_NOT_FOUND` — handle that path gracefully (drop the document from your UI; do not retry in a loop).
|
|
836
832
|
- `proprietary`-sensitivity documents are never grantable (they don't even appear in the picker).
|
|
833
|
+
- **Say what you can open:** `pick({ accept: ['application/pdf', 'image/*', '.docx'] })` takes MIME types, MIME families or extensions, up to 20. The host's Open dialog offers the matching files; the user can still reveal the rest, shown disabled. A malformed list rejects with `INVALID_PARAMS`. Omitted, every type is offered.
|
|
837
834
|
- Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
|
|
838
835
|
- `getDownloadUrl` is for DISPLAY (`<img src>`); your iframe cannot fetch the URL (CSP). To process binary content (parse a PDF, transform an image), use `getBytes(id)` — size-capped at 15 MB (`CONSENTED_DOCUMENT_TOO_LARGE` names the cap; ask the user to open the file manually instead). `getBytes` has a tighter per-minute budget than the rest of the family because it can move megabytes: read sequentially, and surface a rate-limit error rather than retrying in a loop.
|
|
839
836
|
- Revocation behaves differently per method: `getBytes` and `get` re-check the grant on every call, so they start failing immediately. A URL already handed out by `getDownloadUrl` keeps working until it expires (1 hour) — that is the documented contract, not a bug.
|
|
@@ -1050,6 +1047,32 @@ const fresh = await getUrl(assetId);
|
|
|
1050
1047
|
|
|
1051
1048
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
1052
1049
|
|
|
1050
|
+
### `useFiasContent()` — This plugin's content pack
|
|
1051
|
+
|
|
1052
|
+
**Permission:** `entities:client_invoke`
|
|
1053
|
+
**Returns:** `FiasContentApi`
|
|
1054
|
+
|
|
1055
|
+
Ship large or numerous files — lessons, articles, data, images, audio, fonts — in a `content/` directory beside `src/`. They are not bundled: `fias-dev submit` uploads them separately (only the files that changed), the review reads every one, and the published plugin reads them at runtime through this hook. The host page fetches, verifies and caches them; your plugin never touches the network.
|
|
1056
|
+
|
|
1057
|
+
```tsx
|
|
1058
|
+
import { useFiasContent, FiasContentError } from '@fias/arche-sdk';
|
|
1059
|
+
|
|
1060
|
+
const content = useFiasContent();
|
|
1061
|
+
|
|
1062
|
+
const lessons = await content.list('lessons/'); // [{ path, sizeBytes, mimeType }]
|
|
1063
|
+
const markdown = await content.getText('lessons/01-intro.md');
|
|
1064
|
+
const course = await content.getJson<Course>('course.json');
|
|
1065
|
+
const batch = await content.getMany(['a.md', 'b.md'], 'text'); // up to 100 files per call
|
|
1066
|
+
const cover = await content.getObjectUrl('img/cover.webp'); // blob: URL for <img src>
|
|
1067
|
+
const { url } = await content.getUrl('audio/theme.ogg'); // signed URL, for long streaming audio
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
- Paths are relative to `content/`. Formats: `.md`, `.txt`, `.json` (text, ≤ 5 MB each); `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.avif` (static, single-frame images, ≤ 20 MB and 50 megapixels); `.ogg` (audio, ≤ 50 MB); `.woff2` (fonts, ≤ 5 MB). A pack holds at most 10,000 files, 2,000 images, 20 MB of text and 500 MB in total. Run `npx fias-dev content check` to validate without submitting.
|
|
1071
|
+
- Prefer `getObjectUrl` for images and fonts: repeat views come from the host cache. `getUrl` is for media that must stream or seek; it bypasses the cache, and a URL held past its `expiresAt` must be requested again.
|
|
1072
|
+
- Failures throw `FiasContentError` with a `code`: `CONTENT_NOT_FOUND`, `CONTENT_NOT_TEXT`, `CONTENT_TOO_LARGE`, `CONTENT_UNAVAILABLE`, `CONTENT_THROTTLED`, `CONTENT_UNAVAILABLE_IN_PREVIEW`, `PERMISSION_DENIED`, `RATE_LIMIT`. `getMany` returns the error for a failed path instead of throwing.
|
|
1073
|
+
- 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.
|
|
1074
|
+
- 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.
|
|
1075
|
+
|
|
1053
1076
|
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
1054
1077
|
|
|
1055
1078
|
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
@@ -1159,18 +1182,29 @@ openArche('arc_0123456789abcdef0123456789abcdef', { newTab: true });
|
|
|
1159
1182
|
openArche('arc_0123456789abcdef0123456789abcdef', { path: 'map' });
|
|
1160
1183
|
```
|
|
1161
1184
|
|
|
1162
|
-
**Receiving a deep link.** There is nothing extra to opt into: `currentPath`
|
|
1163
|
-
the
|
|
1164
|
-
that wants to be a deep-link target just
|
|
1185
|
+
**Receiving a deep link.** There is nothing extra to opt into: `currentPath` is
|
|
1186
|
+
the host's path expressed in YOUR arche's route space — the same space
|
|
1187
|
+
`navigateTo` accepts — so an arche that wants to be a deep-link target just
|
|
1188
|
+
reads it at boot. The `/a/<arche>` prefix is stripped for you, and your home
|
|
1189
|
+
screen reads `/`.
|
|
1165
1190
|
|
|
1166
1191
|
```tsx
|
|
1167
1192
|
const { currentPath } = useFiasNavigation();
|
|
1168
|
-
//
|
|
1169
|
-
const page = currentPath.
|
|
1193
|
+
// a deep link to '/a/arc_0123…/map' arrives here as '/map'
|
|
1194
|
+
const page = currentPath.replace(/^\//, '') || 'home';
|
|
1170
1195
|
```
|
|
1171
1196
|
|
|
1172
|
-
It is
|
|
1173
|
-
|
|
1197
|
+
It is `''` until the host's `init` message lands — the bridge handshake is
|
|
1198
|
+
async, so your first render usually beats it. Treat `''` as "not known yet"
|
|
1199
|
+
rather than as your home screen; that distinction is what keeps a direct entry
|
|
1200
|
+
such as a share link from being lost to the race.
|
|
1201
|
+
|
|
1202
|
+
After that it TRACKS the host: it updates when you call `navigateTo`, and when
|
|
1203
|
+
the user moves the host themselves with browser back/forward. So a router
|
|
1204
|
+
driven off `currentPath` stays in step with the address bar, and the back
|
|
1205
|
+
button works the way your users expect — you do not have to mirror the path in
|
|
1206
|
+
your own state. The dev harness echoes navigations the same way, so what you
|
|
1207
|
+
see locally is what ships.
|
|
1174
1208
|
|
|
1175
1209
|
### Opening external links
|
|
1176
1210
|
|