@layers/amba-web 4.0.8 → 4.0.9

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/index.js CHANGED
@@ -2,28 +2,46 @@
2
2
  /**
3
3
  * Browser localStorage adapter for SDK identity persistence.
4
4
  *
5
- * The SDK runtime currently re-seeds an `anonymous_id` on every page load.
6
- * This adapter is the documented home for storage-prefix handling so
7
- * customers can override the localStorage key prefix used by the
8
- * (eventual) identity cache.
5
+ * Browsers can deny or throw on localStorage access (privacy mode, sandboxed
6
+ * iframes, quota exhaustion). Persistence is best-effort: storage failures
7
+ * must never turn a successful auth/network operation into an SDK failure.
9
8
  */
10
9
  var LocalStoragePersistence = class {
11
10
  constructor(prefix) {
12
11
  this.prefix = prefix;
13
12
  }
14
- /** Read everything we'd want to hydrate at init. No-op until wired through. */
15
- hydrate() {}
13
+ get storage() {
14
+ try {
15
+ return globalThis.localStorage ?? null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ get available() {
21
+ return this.storage !== null;
22
+ }
16
23
  read(key) {
17
- if (typeof localStorage === "undefined") return null;
18
- return localStorage.getItem(`${this.prefix}:${key}`);
24
+ const storage = this.storage;
25
+ if (!storage) return null;
26
+ try {
27
+ return storage.getItem(`${this.prefix}:${key}`);
28
+ } catch {
29
+ return null;
30
+ }
19
31
  }
20
32
  write(key, value) {
21
- if (typeof localStorage === "undefined") return;
22
- localStorage.setItem(`${this.prefix}:${key}`, value);
33
+ const storage = this.storage;
34
+ if (!storage) return;
35
+ try {
36
+ storage.setItem(`${this.prefix}:${key}`, value);
37
+ } catch {}
23
38
  }
24
39
  delete(key) {
25
- if (typeof localStorage === "undefined") return;
26
- localStorage.removeItem(`${this.prefix}:${key}`);
40
+ const storage = this.storage;
41
+ if (!storage) return;
42
+ try {
43
+ storage.removeItem(`${this.prefix}:${key}`);
44
+ } catch {}
27
45
  }
28
46
  };
29
47
 
@@ -1340,11 +1358,10 @@ const DEFAULT_API_URL = "https://api.amba.dev";
1340
1358
  /**
1341
1359
  * Default host that serves deployed functions. `Amba.functions.invoke(name)`
1342
1360
  * targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a
1343
- * `functionsBaseUrl` override is configured. The `<projectId>` label is the
1344
- * server-resolved id (from `diagnostics.ping`), which the function router
1345
- * accepts directly.
1361
+ * `functionsBaseUrl` override is configured.
1346
1362
  */
1347
1363
  const DEFAULT_FUNCTIONS_HOST = "fn.amba.host";
1364
+ const PROJECT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1348
1365
  /**
1349
1366
  * Internal state — one instance per page. Created lazily by `Amba.configure()`.
1350
1367
  */
@@ -1366,15 +1383,73 @@ let _configuredProjectId = null;
1366
1383
  let _configuredApiUrl = DEFAULT_API_URL;
1367
1384
  let _configuredClientKey = null;
1368
1385
  let _currentSessionToken = null;
1386
+ let _currentRefreshToken = null;
1387
+ let _currentSessionExpiresAt = null;
1388
+ let _currentUser = null;
1389
+ const BROWSER_IDENTITY_VERSION = 1;
1390
+ const DEFAULT_SESSION_TTL_MS = 3600 * 1e3;
1391
+ function identityStorageKey(projectId) {
1392
+ return `identity:v${BROWSER_IDENTITY_VERSION}:project:${encodeURIComponent(projectId)}`;
1393
+ }
1394
+ function isNonEmptyString(value) {
1395
+ return typeof value === "string" && value.trim().length > 0;
1396
+ }
1397
+ function containsControlCharacter(value) {
1398
+ return Array.from(value).some((char) => {
1399
+ const code = char.charCodeAt(0);
1400
+ return code <= 31 || code === 127;
1401
+ });
1402
+ }
1403
+ /** Read + validate a project-bound identity snapshot. Corrupt or cross-project
1404
+ * data is removed instead of being handed to the core. */
1405
+ function readPersistedIdentity(persistence, projectId) {
1406
+ const key = identityStorageKey(projectId);
1407
+ const raw = persistence.read(key);
1408
+ if (!raw) return null;
1409
+ try {
1410
+ const value = JSON.parse(raw);
1411
+ const hasTokens = value.session_token != null || value.refresh_token != null;
1412
+ if (value.version !== BROWSER_IDENTITY_VERSION || value.project_id !== projectId || !isNonEmptyString(value.anonymous_id) || hasTokens && !isNonEmptyString(value.app_user_id) || value.session_token != null && !isNonEmptyString(value.refresh_token) || value.session_token != null && !isNonEmptyString(value.session_token) || value.refresh_token != null && !isNonEmptyString(value.refresh_token) || value.expires_at != null && (!isNonEmptyString(value.expires_at) || !Number.isFinite(Date.parse(value.expires_at)))) throw new Error("invalid identity snapshot");
1413
+ const expiresAt = isNonEmptyString(value.expires_at) ? Date.parse(value.expires_at) : NaN;
1414
+ const sessionExpired = value.session_token != null && Number.isFinite(expiresAt) && expiresAt <= Date.now();
1415
+ return {
1416
+ version: BROWSER_IDENTITY_VERSION,
1417
+ project_id: projectId,
1418
+ anonymous_id: value.anonymous_id,
1419
+ app_user_id: isNonEmptyString(value.app_user_id) ? value.app_user_id : null,
1420
+ session_token: sessionExpired ? null : value.session_token ?? null,
1421
+ refresh_token: value.refresh_token ?? null,
1422
+ expires_at: sessionExpired ? null : value.expires_at ?? null,
1423
+ user: value.user && typeof value.user === "object" && isNonEmptyString(value.user.id) ? value.user : null
1424
+ };
1425
+ } catch {
1426
+ persistence.delete(key);
1427
+ return null;
1428
+ }
1429
+ }
1430
+ function writeIdentitySnapshot() {
1431
+ if (!_persistence?.available || !_configuredProjectId || !coreInstance) return;
1432
+ const snapshot = {
1433
+ version: BROWSER_IDENTITY_VERSION,
1434
+ project_id: _configuredProjectId,
1435
+ anonymous_id: coreInstance.anonymousId(),
1436
+ app_user_id: coreInstance.appUserId() ?? null,
1437
+ session_token: _currentSessionToken,
1438
+ refresh_token: _currentRefreshToken,
1439
+ expires_at: _currentSessionExpiresAt,
1440
+ user: _currentUser
1441
+ };
1442
+ _persistence.write(identityStorageKey(_configuredProjectId), JSON.stringify(snapshot));
1443
+ }
1369
1444
  /**
1370
1445
  * Functions invocation state. `_configuredFunctionsBaseUrl` is the optional
1371
- * override from `Amba.configure({ functionsBaseUrl })`. When it's null, the
1372
- * default-host path resolves the project id once via `diagnostics.ping` and
1373
- * caches it in `_resolvedFunctionsProjectId` so subsequent invokes skip the
1374
- * round-trip. Both reset on every `configure()`.
1446
+ * override from `Amba.configure({ functionsBaseUrl })`. The default-host path
1447
+ * caches its resolved project id and shares an in-flight diagnostics fallback
1448
+ * across concurrent invokes. All three reset on every `configure()`.
1375
1449
  */
1376
1450
  let _configuredFunctionsBaseUrl = null;
1377
1451
  let _resolvedFunctionsProjectId = null;
1452
+ let _functionsProjectIdInflight = null;
1378
1453
  /**
1379
1454
  * Offline-resilience state. `_persistence` is the localStorage-backed store
1380
1455
  * captured at configure-time; `_offlineQueue` is lazily built the first time
@@ -1442,7 +1517,7 @@ async function replayOutboxOp(op) {
1442
1517
  switch (op.kind) {
1443
1518
  case "collection.insert":
1444
1519
  await coreCall((c) => c.collectionsInsert(op.collection, encodeJson({
1445
- ...op.payload ?? {},
1520
+ ...op.payload,
1446
1521
  idempotency_key: op.idempotencyKey
1447
1522
  })));
1448
1523
  return;
@@ -1563,12 +1638,12 @@ function toWireTransactionOp(op) {
1563
1638
  return wire;
1564
1639
  }
1565
1640
  function snapshotSession(user) {
1566
- if (!user) return null;
1641
+ if (!user || !_currentSessionToken) return null;
1567
1642
  return {
1568
- sessionToken: "",
1569
- refreshToken: "",
1643
+ sessionToken: _currentSessionToken,
1644
+ refreshToken: _currentRefreshToken ?? "",
1570
1645
  user,
1571
- expiresAt: ""
1646
+ expiresAt: _currentSessionExpiresAt ?? ""
1572
1647
  };
1573
1648
  }
1574
1649
  function notifyAuthSubscribers(session) {
@@ -1579,7 +1654,11 @@ function notifyAuthSubscribers(session) {
1579
1654
  }
1580
1655
  }
1581
1656
  async function notifyFromAuthResult(result) {
1582
- if (result?.session_token) _currentSessionToken = result.session_token;
1657
+ _currentSessionToken = result.session_token;
1658
+ _currentRefreshToken = result.refresh_token;
1659
+ _currentSessionExpiresAt = result.expires_at && Number.isFinite(Date.parse(result.expires_at)) ? result.expires_at : new Date(Date.now() + DEFAULT_SESSION_TTL_MS).toISOString();
1660
+ _currentUser = result.user;
1661
+ writeIdentitySnapshot();
1583
1662
  notifyAuthSubscribers(snapshotSession(result.user));
1584
1663
  }
1585
1664
  /**
@@ -1626,22 +1705,156 @@ function buildAiStreamRequest(path, request) {
1626
1705
  /**
1627
1706
  * Resolve the full invocation URL for a deployed function.
1628
1707
  *
1629
- * With `functionsBaseUrl` configured we POST to `<base>/<name>` verbatim
1630
- * (custom domains / non-prod). Otherwise we resolve the server-side project
1631
- * id once (via `diagnostics.ping`, cached) and target
1632
- * `https://<projectId>.fn.amba.host/<name>` — the function router accepts the
1633
- * project id as the host label directly.
1708
+ * Resolution checks `functionsBaseUrl` first, then uses a UUID-shaped
1709
+ * configured project id directly, and finally falls back to a deduped
1710
+ * `diagnostics.ping` for the authoritative server-side project id. The
1711
+ * resolved default-host id is cached for subsequent invokes.
1634
1712
  */
1635
1713
  async function resolveFunctionUrl(name) {
1636
1714
  if (_configuredFunctionsBaseUrl) return `${_configuredFunctionsBaseUrl.replace(/\/$/, "")}/${encodeURIComponent(name)}`;
1637
- if (!_resolvedFunctionsProjectId) {
1638
- const ping = await Amba.diagnostics.ping();
1639
- if (!ping.ok || !ping.server_project_id) throw new AmbaApiError("FUNCTIONS_HOST_UNRESOLVED", "Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.", ping);
1640
- _resolvedFunctionsProjectId = ping.server_project_id;
1715
+ return `https://${await resolveFunctionsProjectId()}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(name)}`;
1716
+ }
1717
+ async function resolveFunctionsProjectId() {
1718
+ if (_resolvedFunctionsProjectId) return _resolvedFunctionsProjectId;
1719
+ if (_configuredProjectId && PROJECT_ID_PATTERN.test(_configuredProjectId)) {
1720
+ _resolvedFunctionsProjectId = _configuredProjectId.toLowerCase();
1721
+ return _resolvedFunctionsProjectId;
1641
1722
  }
1642
- return `https://${_resolvedFunctionsProjectId}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(name)}`;
1723
+ if (!_functionsProjectIdInflight) {
1724
+ const configuredProjectId = _configuredProjectId;
1725
+ const resolution = (async () => {
1726
+ const ping = await Amba.diagnostics.ping();
1727
+ if (!ping.ok || !ping.server_project_id) throw new AmbaApiError("FUNCTIONS_HOST_UNRESOLVED", "Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.", ping);
1728
+ if (_configuredProjectId === configuredProjectId && !_configuredFunctionsBaseUrl) _resolvedFunctionsProjectId = ping.server_project_id;
1729
+ return ping.server_project_id;
1730
+ })();
1731
+ _functionsProjectIdInflight = resolution;
1732
+ resolution.then(() => {
1733
+ if (_functionsProjectIdInflight === resolution) _functionsProjectIdInflight = null;
1734
+ }, () => {
1735
+ if (_functionsProjectIdInflight === resolution) _functionsProjectIdInflight = null;
1736
+ });
1737
+ }
1738
+ return _functionsProjectIdInflight;
1643
1739
  }
1644
1740
  /**
1741
+ * Immutable browser collection scope for a server-verified group context.
1742
+ * Each method carries its own `groupId` into the Rust request path; no mutable
1743
+ * global header state is used, so parallel group A/group B calls cannot leak.
1744
+ *
1745
+ * Group-scoped writes deliberately bypass the global offline outbox. Replaying
1746
+ * a queued group mutation without its membership context would be unsafe;
1747
+ * callers receive the real network outcome and can retry in the same scope.
1748
+ */
1749
+ var AmbaGroupScopedClient = class AmbaGroupScopedClient {
1750
+ groupId;
1751
+ constructor(groupId) {
1752
+ const normalized = groupId.trim();
1753
+ if (!normalized || containsControlCharacter(normalized)) throw new AmbaApiError("VALIDATION_ERROR", "Amba.asGroup: groupId must be non-empty and contain no control characters");
1754
+ this.groupId = normalized;
1755
+ }
1756
+ asGroup(groupId) {
1757
+ return new AmbaGroupScopedClient(groupId);
1758
+ }
1759
+ inGroup(groupId) {
1760
+ return this.asGroup(groupId);
1761
+ }
1762
+ collection(name) {
1763
+ const groupId = this.groupId;
1764
+ return new CollectionSubscription({
1765
+ baseUrl: _configuredApiUrl,
1766
+ authHeaders: () => {
1767
+ const headers = realtimeAuthHeaders();
1768
+ return headers ? {
1769
+ ...headers,
1770
+ "X-Group-Context": groupId
1771
+ } : null;
1772
+ }
1773
+ }, name);
1774
+ }
1775
+ collections = {
1776
+ find: async (name, options = {}) => await coreCall((c) => c.collectionsFind(name, encodeJson(options), this.groupId)),
1777
+ findOne: async (name, id) => await coreCall((c) => c.collectionsFindOne(name, id, this.groupId)),
1778
+ insert: async (name, row) => await coreCall((c) => c.collectionsInsert(name, encodeJson(row), this.groupId)),
1779
+ update: async (name, id, set) => await coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set), this.groupId)),
1780
+ delete: async (name, id) => await coreCall((c) => c.collectionsDelete(name, id, this.groupId)),
1781
+ upsert: async (name, row, options) => await coreCall((c) => c.collectionsUpsert(name, encodeJson(row), encodeJson({
1782
+ on_conflict: options.onConflict,
1783
+ conflict_target: options.conflictTarget
1784
+ }), this.groupId)),
1785
+ deleteWhere: async (name, filter) => await coreCall((c) => c.collectionsDeleteWhere(name, encodeJson(filter), this.groupId)),
1786
+ restore: async (name, id) => await coreCall((c) => c.collectionsRestore(name, id, this.groupId)),
1787
+ transaction: async (operations) => {
1788
+ return (await coreCall((c) => c.collectionsTransaction(encodeJson(operations.map(toWireTransactionOp)), this.groupId))).data;
1789
+ },
1790
+ findNearest: async (name, options) => await coreCall((c) => c.collectionsFindNearest(name, encodeJson(options), this.groupId)),
1791
+ count: async (name, filter) => await coreCall((c) => c.collectionsCount(name, filter ? encodeJson(filter) : void 0, this.groupId)),
1792
+ optimisticInsert: async (name, row, view) => withOptimistic({
1793
+ apply: view.apply,
1794
+ mutate: () => coreCall((c) => c.collectionsInsert(name, encodeJson(row), this.groupId)),
1795
+ rollback: view.rollback
1796
+ }),
1797
+ optimisticUpdate: async (name, id, set, view) => withOptimistic({
1798
+ apply: view.apply,
1799
+ mutate: () => coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set), this.groupId)),
1800
+ rollback: view.rollback
1801
+ }),
1802
+ optimisticDelete: async (name, id, view) => withOptimistic({
1803
+ apply: view.apply,
1804
+ mutate: () => coreCall((c) => c.collectionsDelete(name, id, this.groupId)),
1805
+ rollback: view.rollback
1806
+ }),
1807
+ where: {
1808
+ eq: (column, value) => ({
1809
+ column,
1810
+ op: "eq",
1811
+ value
1812
+ }),
1813
+ ne: (column, value) => ({
1814
+ column,
1815
+ op: "ne",
1816
+ value
1817
+ }),
1818
+ gt: (column, value) => ({
1819
+ column,
1820
+ op: "gt",
1821
+ value
1822
+ }),
1823
+ gte: (column, value) => ({
1824
+ column,
1825
+ op: "gte",
1826
+ value
1827
+ }),
1828
+ lt: (column, value) => ({
1829
+ column,
1830
+ op: "lt",
1831
+ value
1832
+ }),
1833
+ lte: (column, value) => ({
1834
+ column,
1835
+ op: "lte",
1836
+ value
1837
+ }),
1838
+ in: (column, values) => ({
1839
+ column,
1840
+ op: "in",
1841
+ value: values
1842
+ }),
1843
+ isNull: (column) => ({
1844
+ column,
1845
+ op: "is_null"
1846
+ }),
1847
+ isNotNull: (column) => ({
1848
+ column,
1849
+ op: "is_not_null"
1850
+ }),
1851
+ and: (...filters) => ({ and: filters }),
1852
+ or: (...filters) => ({ or: filters }),
1853
+ not: (filter) => ({ not: filter })
1854
+ }
1855
+ };
1856
+ };
1857
+ /**
1645
1858
  * Top-level SDK namespace.
1646
1859
  *
1647
1860
  * Stateless from the JS side — all state lives in the SDK runtime. Calling
@@ -1670,7 +1883,7 @@ const Amba = {
1670
1883
  } else await initCore();
1671
1884
  }
1672
1885
  const persistence = new LocalStoragePersistence(config.storagePrefix ?? "amba");
1673
- persistence.hydrate();
1886
+ const restoredIdentity = readPersistedIdentity(persistence, config.projectId);
1674
1887
  let nextCore;
1675
1888
  try {
1676
1889
  const { wasmClass } = await getCoreRuntime();
@@ -1683,6 +1896,13 @@ const Amba = {
1683
1896
  consent_required: config.consentRequired,
1684
1897
  debug: config.debug
1685
1898
  }), globalThis.fetch.bind(globalThis));
1899
+ if (restoredIdentity) nextCore.hydrateIdentity(encodeJson({
1900
+ anonymous_id: restoredIdentity.anonymous_id,
1901
+ app_user_id: restoredIdentity.app_user_id,
1902
+ session_token: restoredIdentity.session_token,
1903
+ refresh_token: restoredIdentity.refresh_token,
1904
+ expires_at: restoredIdentity.expires_at
1905
+ }));
1686
1906
  } catch (err) {
1687
1907
  throw toAmbaApiError(err);
1688
1908
  }
@@ -1692,8 +1912,15 @@ const Amba = {
1692
1912
  _configuredProjectId = config.projectId;
1693
1913
  _configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;
1694
1914
  _configuredClientKey = config.clientKey;
1915
+ _currentSessionToken = nextCore.isAuthenticated() ? restoredIdentity?.session_token ?? null : null;
1916
+ _currentRefreshToken = restoredIdentity?.refresh_token ?? null;
1917
+ _currentSessionExpiresAt = nextCore.isAuthenticated() ? restoredIdentity?.expires_at ?? null : null;
1918
+ _currentUser = nextCore.isAuthenticated() ? restoredIdentity?.user ?? null : null;
1695
1919
  _configuredFunctionsBaseUrl = config.functionsBaseUrl ?? null;
1696
1920
  _resolvedFunctionsProjectId = null;
1921
+ _functionsProjectIdInflight = null;
1922
+ if (!_configuredFunctionsBaseUrl && !PROJECT_ID_PATTERN.test(config.projectId)) resolveFunctionsProjectId().catch(() => {});
1923
+ writeIdentitySnapshot();
1697
1924
  },
1698
1925
  get projectId() {
1699
1926
  if (!_configuredProjectId) throw new AmbaApiError("NOT_INITIALIZED", "amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.");
@@ -1846,6 +2073,10 @@ const Amba = {
1846
2073
  signOut: async (rotateAnonymousId = false) => {
1847
2074
  await coreCall((c) => c.signOut(rotateAnonymousId));
1848
2075
  _currentSessionToken = null;
2076
+ _currentRefreshToken = null;
2077
+ _currentSessionExpiresAt = null;
2078
+ _currentUser = null;
2079
+ writeIdentitySnapshot();
1849
2080
  notifyAuthSubscribers(null);
1850
2081
  },
1851
2082
  refresh: async () => {
@@ -1854,11 +2085,17 @@ const Amba = {
1854
2085
  return r;
1855
2086
  },
1856
2087
  me: async () => {
1857
- return await coreCall((c) => c.me());
2088
+ const user = await coreCall((c) => c.me());
2089
+ _currentUser = user;
2090
+ writeIdentitySnapshot();
2091
+ return user;
1858
2092
  },
1859
2093
  getSession: async () => {
1860
2094
  if (!requireCore().isAuthenticated()) return null;
1861
- return snapshotSession(await coreCall((c) => c.me()));
2095
+ const user = await coreCall((c) => c.me());
2096
+ _currentUser = user;
2097
+ writeIdentitySnapshot();
2098
+ return snapshotSession(user);
1862
2099
  },
1863
2100
  getAnonymousId: async () => {
1864
2101
  return requireCore().anonymousId();
@@ -1889,6 +2126,14 @@ const Amba = {
1889
2126
  await coreCall((c) => c.sessionsRevoke(sessionId));
1890
2127
  }
1891
2128
  },
2129
+ asGroup: (groupId) => {
2130
+ requireCore();
2131
+ return new AmbaGroupScopedClient(groupId);
2132
+ },
2133
+ inGroup: (groupId) => {
2134
+ requireCore();
2135
+ return new AmbaGroupScopedClient(groupId);
2136
+ },
1892
2137
  collection: (name) => new CollectionSubscription({
1893
2138
  baseUrl: _configuredApiUrl,
1894
2139
  authHeaders: realtimeAuthHeaders
@@ -2227,7 +2472,7 @@ const Amba = {
2227
2472
  const method = options?.method ?? "POST";
2228
2473
  const headers = {
2229
2474
  "X-Api-Key": _configuredClientKey,
2230
- ...options?.headers ?? {}
2475
+ ...options?.headers
2231
2476
  };
2232
2477
  if (_currentSessionToken) headers["Authorization"] = `Bearer ${_currentSessionToken}`;
2233
2478
  let requestBody;
@@ -2438,8 +2683,8 @@ function getCoreWasm() {
2438
2683
  * `verify-sdk-version-coherence` GH action gates every publish on the
2439
2684
  * pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
2440
2685
  */
2441
- const SDK_VERSION = "4.0.8";
2686
+ const SDK_VERSION = "4.0.9";
2442
2687
 
2443
2688
  //#endregion
2444
- export { Amba, AmbaApiError, CollectionSubscription, ConversationSubscription, GamificationSubscription, SDK_VERSION, getCoreWasm, withOptimistic };
2689
+ export { Amba, AmbaApiError, AmbaGroupScopedClient, CollectionSubscription, ConversationSubscription, GamificationSubscription, SDK_VERSION, getCoreWasm, withOptimistic };
2445
2690
  //# sourceMappingURL=index.js.map