@fias/arche-sdk 2.27.0 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hooks.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FiasContentError = exports.PLUGIN_CONTENT_ENTITY_ID = exports.GRAMMAR_CHECK_ENTITY_ID = exports.NLLB_LANGUAGES = exports.NLLB_TRANSLATION_ENTITY_ID = void 0;
3
+ exports.FiasContentError = exports.FiasLocalStoreError = exports.PLUGIN_LOCAL_STORE_ENTITY_ID = exports.PLUGIN_CONTENT_ENTITY_ID = exports.ArcheAssetError = exports.ARCHE_ASSET_BYTES_ENTITY_ID = exports.GRAMMAR_CHECK_ENTITY_ID = exports.NLLB_LANGUAGES = exports.NLLB_TRANSLATION_ENTITY_ID = void 0;
4
4
  exports.useFiasUser = useFiasUser;
5
5
  exports.useFiasTheme = useFiasTheme;
6
6
  exports.useFiasVendoredAssets = useFiasVendoredAssets;
@@ -21,6 +21,8 @@ exports.useFiasStore = useFiasStore;
21
21
  exports.fetchVaultDocumentDownloadUrl = fetchVaultDocumentDownloadUrl;
22
22
  exports.useVaultDocuments = useVaultDocuments;
23
23
  exports.useArcheAssets = useArcheAssets;
24
+ exports.useArcheAssetBytes = useArcheAssetBytes;
25
+ exports.useLocalStore = useLocalStore;
24
26
  exports.useFiasContent = useFiasContent;
25
27
  exports.useFiasPreviewState = useFiasPreviewState;
26
28
  exports.useDataSubscription = useDataSubscription;
@@ -1457,6 +1459,16 @@ function bytesToBase64(bytes) {
1457
1459
  * state, and refresh `signedUrl` via `getUrl(assetId)` when it's about
1458
1460
  * to expire.
1459
1461
  *
1462
+ * The library holds images and short sound effects (MP3 / WAV). `list` and
1463
+ * `index` return images unless you pass `kinds` (e.g. `{ kinds: ['audio'] }`),
1464
+ * and with `kinds` the entries are typed `ArcheLibraryAsset` — narrow on
1465
+ * `contentType` before using an image's size. Keeping sounds here is
1466
+ * optional; music is not accepted. Sound effects that must play on cue need
1467
+ * decoded Web Audio buffers: load library sounds with
1468
+ * {@link useArcheAssetBytes}`().getMany` once per session, or bundle them in
1469
+ * the plugin's JS (base64) — not in `public/`, which only an `<audio>`
1470
+ * element can play.
1471
+ *
1460
1472
  * Permission: `assets:read`.
1461
1473
  *
1462
1474
  * @example
@@ -1471,12 +1483,14 @@ function useArcheAssets() {
1471
1483
  cursor: options?.cursor,
1472
1484
  limit: options?.limit,
1473
1485
  tags: options?.tags,
1486
+ kinds: options?.kinds,
1474
1487
  });
1475
1488
  }, [bridge]);
1476
1489
  const index = (0, react_1.useCallback)(async (options) => {
1477
1490
  return bridge.request('arche_assets_index', {
1478
1491
  tags: options?.tags,
1479
1492
  groupBy: options?.groupBy,
1493
+ kinds: options?.kinds,
1480
1494
  });
1481
1495
  }, [bridge]);
1482
1496
  const getUrl = (0, react_1.useCallback)(async (assetId) => {
@@ -1484,6 +1498,269 @@ function useArcheAssets() {
1484
1498
  }, [bridge]);
1485
1499
  return (0, react_1.useMemo)(() => ({ list, index, getUrl }), [list, index, getUrl]);
1486
1500
  }
1501
+ /**
1502
+ * Read the bytes of assets in this arche's asset library — for Web Audio
1503
+ * (`AudioContext.decodeAudioData`), canvas pixel work, or anything else a
1504
+ * signed URL cannot do (the plugin sandbox has no network). Read each sound
1505
+ * once per session and keep the decoded buffer.
1506
+ *
1507
+ * To load a set of sounds, pass the listed entries to `getMany`: up to 100
1508
+ * are read per call, so a whole sound set takes one or two calls. One call
1509
+ * per sound would trip the bridge's runaway-loop guard (more than 50 calls in
1510
+ * 5 seconds), which blocks every read for 10 seconds — `getBytes` calls made
1511
+ * together are batched for the same reason. The host page fetches each asset,
1512
+ * so each read costs one asset load like an `<img>` does. Unavailable in the
1513
+ * builder preview.
1514
+ *
1515
+ * Sounds are ready to trigger only once their buffers are decoded: wait for
1516
+ * the load before the first cue, and resume the `AudioContext` on the first
1517
+ * user gesture.
1518
+ *
1519
+ * Permissions: `entities:client_invoke` AND `assets:read`.
1520
+ *
1521
+ * @example
1522
+ * const { list } = useArcheAssets();
1523
+ * const { getMany } = useArcheAssetBytes();
1524
+ * const { entries } = await list({ kinds: ['audio'], tags: ['sfx:ui'] });
1525
+ * const ctx = new AudioContext();
1526
+ * const loaded = await getMany(entries);
1527
+ * const buffers = new Map<string, AudioBuffer>();
1528
+ * for (const entry of entries) {
1529
+ * const result = loaded[entry.assetId];
1530
+ * if (result instanceof Error) continue; // result.code says why
1531
+ * buffers.set(entry.name ?? entry.assetId, await ctx.decodeAudioData(result.bytes));
1532
+ * }
1533
+ */
1534
+ function useArcheAssetBytes() {
1535
+ const bridge = useBridge();
1536
+ const getBytes = (0, react_1.useCallback)(async (asset) => {
1537
+ let ref;
1538
+ if (typeof asset === 'string') {
1539
+ try {
1540
+ ref = await bridge.request('arche_assets_get_url', { assetId: asset });
1541
+ }
1542
+ catch (err) {
1543
+ throw toArcheAssetError(err);
1544
+ }
1545
+ }
1546
+ else {
1547
+ ref = asset;
1548
+ }
1549
+ return enqueueAssetByteRead(bridge, ref);
1550
+ }, [bridge]);
1551
+ const getMany = (0, react_1.useCallback)(async (assets) => {
1552
+ const read = await readAssetBytesBatch(bridge, assets);
1553
+ const out = {};
1554
+ for (const [assetId, result] of read)
1555
+ out[assetId] = result;
1556
+ return out;
1557
+ }, [bridge]);
1558
+ return (0, react_1.useMemo)(() => ({ getBytes, getMany }), [getBytes, getMany]);
1559
+ }
1560
+ /**
1561
+ * Entity ID of the arche asset bytes client-side entity — the host-side
1562
+ * reader behind {@link useArcheAssetBytes}. Literal here so the SDK
1563
+ * stays bundle-clean (no `@fias/entities` runtime dep).
1564
+ *
1565
+ * SYNC NOTE: must match `ARCHE_ASSET_BYTES_ENTITY_ID` in
1566
+ * `packages/entities/src/integrations/arche-asset-bytes/config.ts`. The
1567
+ * architecture test `sdk-entity-id-constants-match.test.ts` pins this.
1568
+ */
1569
+ exports.ARCHE_ASSET_BYTES_ENTITY_ID = 'ent_intg_arche_asset_bytes_00000';
1570
+ /** A failed asset byte read (`useArcheAssetBytes`): `code` says why. */
1571
+ class ArcheAssetError extends Error {
1572
+ constructor(message, code) {
1573
+ super(message);
1574
+ this.code = code;
1575
+ this.name = 'ArcheAssetError';
1576
+ }
1577
+ }
1578
+ exports.ArcheAssetError = ArcheAssetError;
1579
+ /** A listed URL with less than this many seconds left is replaced, not reused. */
1580
+ const ASSET_URL_MIN_REMAINING_SECONDS = 30;
1581
+ /** Most URLs the host reads in one call (`ARCHE_ASSET_BYTES_MAX_URLS` in the executor). */
1582
+ const ASSET_BYTES_MAX_URLS_PER_CALL = 100;
1583
+ /**
1584
+ * Most lapsed URLs one batch replaces with `arche_assets_get_url`, one call
1585
+ * each. Covers URLs that lapse in flight while staying far below the bridge's
1586
+ * 50-calls-in-5-seconds guard; a batch of older entries is answered
1587
+ * `ASSET_URL_EXPIRED` past this, so the plugin lists again (one call).
1588
+ */
1589
+ const ASSET_URL_MAX_REFRESHES_PER_BATCH = 10;
1590
+ /** One `read` call: the outcome of each URL, in order. Throws when the call itself is refused. */
1591
+ async function readSignedUrls(bridge, signedUrls) {
1592
+ let response;
1593
+ try {
1594
+ response = await bridge.request('client_entity_invoke', { entityId: exports.ARCHE_ASSET_BYTES_ENTITY_ID, input: { verb: 'read', signedUrls } }, CLIENT_ENTITY_INVOKE_TIMEOUT_MS);
1595
+ }
1596
+ catch (err) {
1597
+ throw toArcheAssetError(err);
1598
+ }
1599
+ if ('error' in response) {
1600
+ throw new ArcheAssetError(response.error.message, response.error.code);
1601
+ }
1602
+ return response.results;
1603
+ }
1604
+ function fileError(result) {
1605
+ return result && 'error' in result
1606
+ ? new ArcheAssetError(result.error.message, result.error.code)
1607
+ : new ArcheAssetError('the asset could not be read', 'ASSET_UNAVAILABLE');
1608
+ }
1609
+ /**
1610
+ * Read `items` in calls of up to 100 URLs, recording each outcome in `out`.
1611
+ * Assets that did not fit a call's byte budget beside the others go again
1612
+ * together until a pass reads nothing more (then each is too large on its
1613
+ * own). Returns the reused URLs that lapsed in flight, for a fresh URL.
1614
+ */
1615
+ async function readInBatches(bridge, items, out) {
1616
+ const lapsed = [];
1617
+ let queue = items;
1618
+ while (queue.length > 0) {
1619
+ const overBudget = [];
1620
+ let readCount = 0;
1621
+ for (let at = 0; at < queue.length; at += ASSET_BYTES_MAX_URLS_PER_CALL) {
1622
+ const chunk = queue.slice(at, at + ASSET_BYTES_MAX_URLS_PER_CALL);
1623
+ const results = await readSignedUrls(bridge, chunk.map((item) => item.signedUrl));
1624
+ chunk.forEach((item, i) => {
1625
+ const result = results[i];
1626
+ if (result && !('error' in result)) {
1627
+ out.set(item.assetId, result);
1628
+ readCount += 1;
1629
+ }
1630
+ else if (result?.error.code === 'ASSET_URL_EXPIRED' && !item.refreshed) {
1631
+ // A reused URL can lapse before the host's fetch lands (or the
1632
+ // device clock runs fast); a fresh one recovers it.
1633
+ lapsed.push(item);
1634
+ }
1635
+ else if (result?.error.code === 'ASSET_TOO_LARGE' && chunk.length > 1) {
1636
+ overBudget.push(item);
1637
+ }
1638
+ else {
1639
+ out.set(item.assetId, fileError(result));
1640
+ }
1641
+ });
1642
+ }
1643
+ if (readCount === 0) {
1644
+ for (const item of overBudget) {
1645
+ out.set(item.assetId, new ArcheAssetError('the asset is too large to read', 'ASSET_TOO_LARGE'));
1646
+ }
1647
+ break;
1648
+ }
1649
+ queue = overBudget;
1650
+ }
1651
+ return lapsed;
1652
+ }
1653
+ /**
1654
+ * Read `refs` in as few host calls as possible: reuse each listed URL while it
1655
+ * has time left, replace a bounded number of lapsed ones, and send up to 100
1656
+ * URLs per call. Throws only when a call is refused as a whole.
1657
+ */
1658
+ async function readAssetBytesBatch(bridge, refs) {
1659
+ const out = new Map();
1660
+ let refreshesLeft = ASSET_URL_MAX_REFRESHES_PER_BATCH;
1661
+ // A fresh URL for each lapsed item, within the batch's refresh allowance.
1662
+ const refresh = async (assetIds) => {
1663
+ const fresh = [];
1664
+ for (const assetId of assetIds) {
1665
+ if (refreshesLeft <= 0) {
1666
+ out.set(assetId, new ArcheAssetError('the asset URL has expired; list the assets again for fresh URLs', 'ASSET_URL_EXPIRED'));
1667
+ continue;
1668
+ }
1669
+ refreshesLeft -= 1;
1670
+ try {
1671
+ const asset = await bridge.request('arche_assets_get_url', { assetId });
1672
+ fresh.push({ assetId, signedUrl: asset.signedUrl, refreshed: true });
1673
+ }
1674
+ catch (err) {
1675
+ out.set(assetId, toArcheAssetError(err));
1676
+ }
1677
+ }
1678
+ return fresh;
1679
+ };
1680
+ // One read per asset, whatever the caller passed twice.
1681
+ const unique = new Map();
1682
+ for (const ref of refs)
1683
+ if (!unique.has(ref.assetId))
1684
+ unique.set(ref.assetId, ref);
1685
+ const nowSeconds = Date.now() / 1000;
1686
+ const reusable = [];
1687
+ const stale = [];
1688
+ for (const ref of unique.values()) {
1689
+ if (ref.expiresAt - nowSeconds > ASSET_URL_MIN_REMAINING_SECONDS) {
1690
+ reusable.push({ assetId: ref.assetId, signedUrl: ref.signedUrl, refreshed: false });
1691
+ }
1692
+ else {
1693
+ stale.push(ref.assetId);
1694
+ }
1695
+ }
1696
+ const pending = [...reusable, ...(await refresh(stale))];
1697
+ const lapsed = await readInBatches(bridge, pending, out);
1698
+ if (lapsed.length > 0) {
1699
+ await readInBatches(bridge, await refresh(lapsed.map((item) => item.assetId)), out);
1700
+ }
1701
+ return out;
1702
+ }
1703
+ const pendingAssetByteReads = new WeakMap();
1704
+ function enqueueAssetByteRead(bridge, ref) {
1705
+ let batch = pendingAssetByteReads.get(bridge);
1706
+ if (!batch) {
1707
+ const created = { refs: [], waiters: [] };
1708
+ pendingAssetByteReads.set(bridge, created);
1709
+ batch = created;
1710
+ void Promise.resolve().then(() => flushAssetByteReads(bridge, created));
1711
+ }
1712
+ batch.refs.push(ref);
1713
+ const queued = batch;
1714
+ return new Promise((resolve, reject) => {
1715
+ queued.waiters.push({ assetId: ref.assetId, resolve, reject });
1716
+ });
1717
+ }
1718
+ async function flushAssetByteReads(bridge, batch) {
1719
+ pendingAssetByteReads.delete(bridge);
1720
+ let results;
1721
+ try {
1722
+ results = await readAssetBytesBatch(bridge, batch.refs);
1723
+ }
1724
+ catch (err) {
1725
+ const failure = toArcheAssetError(err);
1726
+ for (const waiter of batch.waiters)
1727
+ waiter.reject(failure);
1728
+ return;
1729
+ }
1730
+ const handedOut = new Set();
1731
+ for (const waiter of batch.waiters) {
1732
+ const result = results.get(waiter.assetId) ??
1733
+ new ArcheAssetError('the asset could not be read', 'ASSET_UNAVAILABLE');
1734
+ if (result instanceof ArcheAssetError) {
1735
+ waiter.reject(result);
1736
+ }
1737
+ else if (handedOut.has(waiter.assetId)) {
1738
+ // Two callers asked for one asset: each gets its own buffer, because
1739
+ // `decodeAudioData` detaches the one it is given.
1740
+ waiter.resolve({ ...result, bytes: result.bytes.slice(0) });
1741
+ }
1742
+ else {
1743
+ handedOut.add(waiter.assetId);
1744
+ waiter.resolve(result);
1745
+ }
1746
+ }
1747
+ }
1748
+ function toArcheAssetError(err) {
1749
+ if (err instanceof ArcheAssetError)
1750
+ return err;
1751
+ const message = err instanceof Error ? err.message : String(err);
1752
+ const code = err?.code;
1753
+ if (/permission denied/i.test(message)) {
1754
+ return new ArcheAssetError(`${message} — useArcheAssetBytes needs both "assets:read" and "entities:client_invoke" in fias-plugin.json`, 'PERMISSION_DENIED');
1755
+ }
1756
+ if (code === 'RATE_LIMITED' || code === 'RATE_LIMIT' || /rate limit/i.test(message)) {
1757
+ return new ArcheAssetError(message, 'RATE_LIMIT');
1758
+ }
1759
+ if (/rapid-fire loop|runaway loop/i.test(message)) {
1760
+ return new ArcheAssetError(`${message} Load many assets with useArcheAssetBytes().getMany(entries) — one call per 100.`, 'RATE_LIMIT');
1761
+ }
1762
+ return new ArcheAssetError(message, 'ASSET_UNAVAILABLE');
1763
+ }
1487
1764
  /**
1488
1765
  * Entity ID of the plugin content pack client-side entity (ADR-021) — the
1489
1766
  * host-side reader behind {@link useFiasContent}. Literal here so the SDK
@@ -1494,6 +1771,143 @@ function useArcheAssets() {
1494
1771
  * architecture test `sdk-entity-id-constants-match.test.ts` pins this.
1495
1772
  */
1496
1773
  exports.PLUGIN_CONTENT_ENTITY_ID = 'ent_intg_plugin_content_00000000';
1774
+ /**
1775
+ * Entity ID of the plugin local store client-side entity (ADR-031) — the
1776
+ * host-side store behind {@link useLocalStore}. Literal here so the SDK stays
1777
+ * bundle-clean (no `@fias/entities` runtime dep).
1778
+ *
1779
+ * SYNC NOTE: must match `PLUGIN_LOCAL_STORE_ENTITY_ID` in
1780
+ * `packages/entities/src/integrations/plugin-local-store/config.ts`. The
1781
+ * architecture test `sdk-entity-id-constants-match.test.ts` pins this.
1782
+ */
1783
+ exports.PLUGIN_LOCAL_STORE_ENTITY_ID = 'ent_intg_plugin_local_store_0000';
1784
+ /** A failed local-store call: `code` says why. */
1785
+ class FiasLocalStoreError extends Error {
1786
+ constructor(message, code) {
1787
+ super(message);
1788
+ this.code = code;
1789
+ this.name = 'FiasLocalStoreError';
1790
+ }
1791
+ }
1792
+ exports.FiasLocalStoreError = FiasLocalStoreError;
1793
+ function toLocalStoreError(err) {
1794
+ if (err instanceof FiasLocalStoreError)
1795
+ return err;
1796
+ const message = err instanceof Error ? err.message : String(err);
1797
+ // The bridge's centralized gates carry a code; the registry's own refusals
1798
+ // (per-entity permission, per-entity rate bucket) are plain Errors, so the
1799
+ // message is the fallback, never the first look.
1800
+ const raw = err?.code;
1801
+ const code = raw === 'PERMISSION_DENIED' || /permission denied/i.test(message)
1802
+ ? 'PERMISSION_DENIED'
1803
+ : raw === 'RATE_LIMIT' || raw === 'RATE_LIMITED' || /rate limit/i.test(message)
1804
+ ? 'RATE_LIMIT'
1805
+ : 'LOCAL_STORE_UNAVAILABLE';
1806
+ return new FiasLocalStoreError(message, code);
1807
+ }
1808
+ /**
1809
+ * Keep working data on the user's device — a recovery journal, drafts, a
1810
+ * cache — so a refresh, a crash or a closed tab loses nothing. Writes take
1811
+ * milliseconds and never touch the network: the host page keeps the data in
1812
+ * its own browser storage, in a space only this plugin can read, for the
1813
+ * signed-in user in the workspace they are working in (Personal and each org
1814
+ * workspace keep separate data; switching workspace switches the store).
1815
+ * Signing out clears it.
1816
+ *
1817
+ * This is NOT where a user's work is kept. It is best-effort device storage —
1818
+ * a browser may clear site data, and nothing here reaches another device —
1819
+ * so write the journal here and save the user's files through the Vault
1820
+ * (`useVaultUserDocuments().saveContent`, `upload`, or the Save dialog).
1821
+ *
1822
+ * Values: a string, bytes (`ArrayBuffer` / `Uint8Array`), or any JSON value;
1823
+ * `get` returns the same kind. One value ≤ 64 MB; the whole store for this
1824
+ * user ≤ 256 MB. Over budget, `put` throws `LOCAL_STORE_QUOTA_EXCEEDED`
1825
+ * rather than evicting — decide what to keep with `estimate()` and `list()`.
1826
+ * For a large snapshot (an image, a big document), put BYTES: a JSON value is
1827
+ * validated and serialized on the host page, which costs time proportional
1828
+ * to its size on every write, while bytes are only copied. Restore a journal
1829
+ * with `getMany(keys)` — one call for up to 100 entries, instead of one call
1830
+ * per key.
1831
+ *
1832
+ * Permission: `storage:local`.
1833
+ *
1834
+ * @example
1835
+ * const store = useLocalStore();
1836
+ * await store.put(`session/${docId}`, snapshotJson); // on every change
1837
+ * const saved = await store.getJson<Snapshot>(`session/${docId}`); // on load
1838
+ * await store.delete(`session/${docId}`); // on close
1839
+ */
1840
+ function useLocalStore() {
1841
+ const bridge = useBridge();
1842
+ const call = (0, react_1.useCallback)(async (input) => {
1843
+ let response;
1844
+ try {
1845
+ response = await bridge.request('client_entity_invoke', { entityId: exports.PLUGIN_LOCAL_STORE_ENTITY_ID, input }, CLIENT_ENTITY_INVOKE_TIMEOUT_MS);
1846
+ }
1847
+ catch (err) {
1848
+ throw toLocalStoreError(err);
1849
+ }
1850
+ if (response?.error) {
1851
+ throw new FiasLocalStoreError(response.error.message, response.error.code);
1852
+ }
1853
+ return response ?? {};
1854
+ }, [bridge]);
1855
+ const get = (0, react_1.useCallback)(async (key) => (await call({ verb: 'get', key })).entry ?? null, [call]);
1856
+ const getText = (0, react_1.useCallback)(async (key) => {
1857
+ const entry = await get(key);
1858
+ if (!entry)
1859
+ return null;
1860
+ if (typeof entry.value !== 'string') {
1861
+ throw new FiasLocalStoreError(`"${key}" does not hold text`, 'LOCAL_STORE_WRONG_KIND');
1862
+ }
1863
+ return entry.value;
1864
+ }, [get]);
1865
+ const getBytes = (0, react_1.useCallback)(async (key) => {
1866
+ const entry = await get(key);
1867
+ if (!entry)
1868
+ return null;
1869
+ if (!(entry.value instanceof ArrayBuffer)) {
1870
+ throw new FiasLocalStoreError(`"${key}" does not hold bytes`, 'LOCAL_STORE_WRONG_KIND');
1871
+ }
1872
+ return entry.value;
1873
+ }, [get]);
1874
+ const getJson = (0, react_1.useCallback)(async (key) => {
1875
+ const entry = await get(key);
1876
+ if (!entry)
1877
+ return null;
1878
+ if (entry.value instanceof ArrayBuffer) {
1879
+ throw new FiasLocalStoreError(`"${key}" holds bytes, not JSON`, 'LOCAL_STORE_WRONG_KIND');
1880
+ }
1881
+ // A top-level string is always stored as text, so `put(key,
1882
+ // JSON.stringify(x))` reads back here the same as `put(key, x)`.
1883
+ if (typeof entry.value === 'string') {
1884
+ try {
1885
+ return JSON.parse(entry.value);
1886
+ }
1887
+ catch {
1888
+ throw new FiasLocalStoreError(`"${key}" holds text that is not JSON`, 'LOCAL_STORE_WRONG_KIND');
1889
+ }
1890
+ }
1891
+ return entry.value;
1892
+ }, [get]);
1893
+ const getMany = (0, react_1.useCallback)(async (keys) => (await call({ verb: 'get_many', keys }))
1894
+ .entries ?? [], [call]);
1895
+ const put = (0, react_1.useCallback)(async (key, value) => ({
1896
+ sizeBytes: (await call({ verb: 'put', key, value })).sizeBytes ?? 0,
1897
+ }), [call]);
1898
+ const del = (0, react_1.useCallback)(async (key) => {
1899
+ await call({ verb: 'delete', key });
1900
+ }, [call]);
1901
+ const list = (0, react_1.useCallback)(async (prefix) => (await call({ verb: 'list', ...(prefix !== undefined ? { prefix } : {}) })).entries ?? [], [call]);
1902
+ const clear = (0, react_1.useCallback)(async () => ({
1903
+ deleted: (await call({ verb: 'clear' })).deleted ?? 0,
1904
+ }), [call]);
1905
+ const estimate = (0, react_1.useCallback)(async () => {
1906
+ const res = await call({ verb: 'estimate' });
1907
+ return { usedBytes: res.usedBytes ?? 0, budgetBytes: res.budgetBytes ?? 0 };
1908
+ }, [call]);
1909
+ return (0, react_1.useMemo)(() => ({ get, getMany, getText, getBytes, getJson, put, delete: del, list, clear, estimate }), [get, getMany, getText, getBytes, getJson, put, del, list, clear, estimate]);
1910
+ }
1497
1911
  /** A failed content read: `code` says why, `path` names the file when there is one. */
1498
1912
  class FiasContentError extends Error {
1499
1913
  constructor(message, code, path) {
@@ -2130,9 +2544,11 @@ function useCommunityAssets() {
2130
2544
  * mutate the source. And you are the authoritative type gate — if you cannot
2131
2545
  * open this file, tell the user plainly rather than opening something broken.
2132
2546
  *
2133
- * To be offered as a destination at all, your arche must be registered for
2134
- * document intake on the platform side; declaring this hook alone does not
2135
- * enrol you.
2547
+ * To be offered as a destination at all, declare what you open in
2548
+ * `fias-plugin.json` (`"fileIntake": { "kinds": ["document"], "types": [".docx"] }`;
2549
+ * `types` narrows a kind to the files you really open) and have Fias approve
2550
+ * it; declaring this hook alone does not enrol you. A kind or type added later
2551
+ * stays off until it is approved too.
2136
2552
  */
2137
2553
  function useArcheHandoff(onHandoff) {
2138
2554
  const bridge = useBridge();