@layers/amba-web 4.0.3 → 4.0.5

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.d.ts CHANGED
@@ -19,6 +19,16 @@ interface AmbaConfig {
19
19
  * Defaults to `"https://api.amba.dev"`.
20
20
  */
21
21
  apiUrl?: string;
22
+ /**
23
+ * Base URL for `Amba.functions.invoke()`. Set this to call your deployed
24
+ * functions through a custom domain or a non-prod environment, e.g.
25
+ * `"https://api.example.com"` or `"https://<id>.staging.fn.amba.host"`.
26
+ * `invoke(name, body)` POSTs to `"<functionsBaseUrl>/<name>"`.
27
+ *
28
+ * When omitted, the SDK resolves the default host automatically on first
29
+ * use (one cached round-trip) — no configuration needed for production.
30
+ */
31
+ functionsBaseUrl?: string;
22
32
  /**
23
33
  * If `true`, blocks all telemetry until `setConsent(granted)` is called.
24
34
  * Required for SOC2 / GDPR strict modes.
@@ -104,6 +114,38 @@ interface OrderBy {
104
114
  column: string;
105
115
  direction: "asc" | "desc";
106
116
  }
117
+ /**
118
+ * Eager-load related rows from another collection in the same `find` call,
119
+ * avoiding an N+1 round-trip per base row. Single-level (MVP).
120
+ *
121
+ * belongs-to — each base row's `localKey` column points at the related row:
122
+ * { collection: "novels", localKey: "novel_id", as: "novel", single: true }
123
+ * → row.novel = the matching novels row (or null)
124
+ *
125
+ * has-many — the related collection's `foreignKey` column points back at the
126
+ * base row's id:
127
+ * { collection: "chapters", foreignKey: "novel_id", as: "chapters" }
128
+ * → row.chapters = the matching chapters rows (array)
129
+ *
130
+ * The included collection's own read policy + soft-delete are honored.
131
+ */
132
+ interface IncludeClause {
133
+ /** Target collection to eager-load from. */
134
+ collection: string;
135
+ /** belongs-to: FK column on the base row pointing at the target. */
136
+ localKey?: string;
137
+ /**
138
+ * Join column on the target. belongs-to: defaults to `id`. has-many:
139
+ * required — the FK column on the target pointing back at the base id.
140
+ */
141
+ foreignKey?: string;
142
+ /** Result key the nested data lands under on each base row. */
143
+ as: string;
144
+ /** belongs-to only: nest a single object instead of an array. */
145
+ single?: boolean;
146
+ /** has-many only: cap related rows per base row (default 100, max 1000). */
147
+ limit?: number;
148
+ }
107
149
  interface FindOptions {
108
150
  filter?: Filter;
109
151
  order?: OrderBy[];
@@ -111,12 +153,61 @@ interface FindOptions {
111
153
  cursor?: string;
112
154
  select?: string[];
113
155
  include_deleted?: boolean;
156
+ /** Eager-load related rows from other collections (kills N+1). */
157
+ include?: IncludeClause[];
114
158
  }
115
159
  interface FindResponse<T> {
116
160
  data: T[];
117
161
  next_cursor?: string | null;
118
162
  has_more: boolean;
119
163
  }
164
+ /**
165
+ * Options for {@link Amba.collections.upsert}.
166
+ *
167
+ * `conflictTarget` is the set of columns covered by the unique index the
168
+ * upsert arbitrates on (e.g. `["user_id", "kind"]`). Every column must be
169
+ * present in the inserted row. `onConflict: "ignore"` keeps the existing
170
+ * row and returns it; `"update"` merges the supplied columns into it.
171
+ */
172
+ interface UpsertOptions {
173
+ onConflict: "ignore" | "update";
174
+ conflictTarget: string[];
175
+ }
176
+ /** Result of {@link Amba.collections.deleteWhere} — the soft-deleted row count. */
177
+ interface DeleteManyResult {
178
+ data: {
179
+ deleted: number;
180
+ };
181
+ }
182
+ /**
183
+ * One operation in an atomic multi-collection transaction
184
+ * ({@link Amba.collections.transaction}). Mirrors the single-row methods:
185
+ * `insert` carries `row` (+ optional upsert controls); `update` carries `id` +
186
+ * `set` (+ optional `expected` compare-and-set); `delete` carries `id` (+
187
+ * optional `expected`). `where` is an extra guard predicate AND'd onto the
188
+ * owner scope.
189
+ */
190
+ interface TransactionOp {
191
+ collection: string;
192
+ op: "insert" | "update" | "delete";
193
+ id?: string;
194
+ row?: Record<string, unknown>;
195
+ set?: Record<string, unknown>;
196
+ where?: Record<string, unknown>;
197
+ expected?: Record<string, unknown>;
198
+ onConflict?: "ignore" | "update";
199
+ conflictTarget?: string[];
200
+ }
201
+ /** One per-op result returned by a committed transaction. */
202
+ interface TransactionOpResult {
203
+ index: number;
204
+ op: "insert" | "update" | "delete";
205
+ collection: string;
206
+ id: string | null;
207
+ created?: boolean;
208
+ deleted?: boolean;
209
+ row?: Record<string, unknown>;
210
+ }
120
211
  interface MediaAsset {
121
212
  id: string;
122
213
  bucket: string;
@@ -142,27 +233,120 @@ interface PushToken {
142
233
  bundle_id?: string | null;
143
234
  created_at: string;
144
235
  }
145
- type EntitlementSource = "revenue_cat" | "app_store" | "play_store" | "stripe" | "promo_code" | "manual";
236
+ /**
237
+ * Store an entitlement's purchase resolved from. `"web"` is the neutral bucket
238
+ * for any non-native checkout (it replaced the former `"stripe"` value).
239
+ */
240
+ type EntitlementStore = "app_store" | "play_store" | "web";
241
+ /** Lifecycle phase of the subscription period backing the entitlement. */
242
+ type EntitlementPeriodType = "trial" | "intro" | "normal";
243
+ /**
244
+ * A server-evaluated entitlement grant, mirroring the
245
+ * `GET /v1/client/entitlements` response field-for-field. `store` and
246
+ * `period_type` are nullable, CHECK-constrained text columns, typed
247
+ * loosely (`… | (string & {}) | null`) so an unexpected value from a newer
248
+ * server never breaks decoding while still autocompleting the known set.
249
+ */
146
250
  interface UserEntitlement {
147
- id: string;
148
- name: string;
251
+ /** Project-defined entitlement key (e.g. `"pro"`) — what `has()` checks. */
252
+ entitlement_id: string;
149
253
  is_active: boolean;
150
- source: EntitlementSource;
151
- expires_at?: string | null;
152
- granted_at?: string | null;
254
+ product_id?: string | null;
255
+ store?: EntitlementStore | (string & {}) | null;
256
+ purchase_date?: string | null;
257
+ expiration_date?: string | null;
258
+ period_type?: EntitlementPeriodType | (string & {}) | null;
259
+ }
260
+ /** Result of `entitlements.restore()` — the active set after a server re-sync. */
261
+ interface RestoreResult {
262
+ restored_count: number;
263
+ entitlements: UserEntitlement[];
264
+ }
265
+ /** A product behind an offering package (provider-neutral). */
266
+ interface OfferingProduct {
267
+ product_id: string;
268
+ display_name?: string | null;
269
+ description?: string | null;
270
+ product_type?: string | null;
271
+ /** Map of neutral store id → that store's product identifier. */
272
+ store_product_refs: Record<string, string>;
273
+ grants_entitlement_id?: string | null;
274
+ trial_period_days?: number | null;
275
+ /** ISO-8601 duration display hint (e.g. "P1M", "P1Y"). */
276
+ duration?: string | null;
277
+ }
278
+ /** A package — one product at a position within an offering. */
279
+ interface OfferingPackage {
280
+ package_id: string;
281
+ position: number;
153
282
  metadata: Record<string, unknown>;
283
+ product: OfferingProduct | null;
284
+ }
285
+ interface Offering {
286
+ offering_id: string;
287
+ display_name?: string | null;
288
+ description?: string | null;
289
+ /** Whether this is the default offering to show. */
290
+ current: boolean;
291
+ metadata: Record<string, unknown>;
292
+ packages: OfferingPackage[];
293
+ }
294
+ /** The shape `offerings()` returns. */
295
+ interface OfferingsResult {
296
+ offerings: Offering[];
297
+ current_offering_id: string | null;
154
298
  }
155
299
  interface AiMessage {
156
300
  role: string;
157
301
  content: unknown;
158
302
  }
303
+ /**
304
+ * A single vendor-neutral image for vision (multimodal) prompts. Provide a
305
+ * remote `url` OR base64 `data` (+ optional `mime`). Amba maps it to the
306
+ * model's native image format — you never touch provider-specific shapes.
307
+ */
308
+ interface AiImageInput {
309
+ /** Remote image URL. Use this OR `data`, not both. */
310
+ url?: string;
311
+ /** Base64-encoded image bytes (no `data:` prefix). */
312
+ data?: string;
313
+ /** MIME type for a base64 image (e.g. `"image/png"`). Defaults to image/png. */
314
+ mime?: string;
315
+ }
316
+ /** A vendor-neutral content block inside a message's `content` array. */
317
+ type AiContentBlock = {
318
+ type: "text";
319
+ text: string;
320
+ } | {
321
+ type: "image";
322
+ url?: string;
323
+ data?: string;
324
+ mime?: string;
325
+ };
159
326
  interface AiMessageRequest {
160
327
  prompt_slug: string;
161
328
  variables?: Record<string, unknown>;
162
329
  messages?: AiMessage[];
330
+ /**
331
+ * Image(s) to send to a vision-capable prompt. They attach to the last user
332
+ * message as vendor-neutral content blocks; the backend maps them to the
333
+ * model's native image format. The prompt's model must be vision-capable, or
334
+ * the call returns `ai_model_not_multimodal`. Omit for text-only prompts.
335
+ */
336
+ images?: AiImageInput[];
163
337
  max_tokens?: number;
164
338
  temperature?: number;
165
339
  enable_prompt_cache?: boolean;
340
+ /**
341
+ * Opt into a streamed (SSE) response from the backend. NOTE: the buffered
342
+ * `ai.anthropic.messages.create()` decodes a single JSON envelope, so it
343
+ * ignores this flag — to consume streamed tokens use the dedicated
344
+ * `ai.anthropic.messages.stream()` / `ai.openai.chat.completions.stream()`
345
+ * methods, which set `stream: true` for you and yield text deltas as they
346
+ * arrive plus a final usage event. The field exists so the request type
347
+ * matches the backend contract.
348
+ */
349
+ stream?: boolean;
166
350
  }
167
351
  interface AiUsage {
168
352
  input_tokens: number;
@@ -175,6 +359,13 @@ interface AiMessageResponse {
175
359
  usage: AiUsage;
176
360
  stop_reason?: string | null;
177
361
  model: string;
362
+ /**
363
+ * Computed USD cost of this call (input + output + cache token rates,
364
+ * summed server-side). `null` when the model isn't in the cost table — the
365
+ * call still succeeds; the cost is just unattributed. Read it to surface
366
+ * per-call spend without a separate analytics round-trip.
367
+ */
368
+ cost_usd?: number | null;
178
369
  }
179
370
  /**
180
371
  * Resolved remote config returned by `config.fetch()`.
@@ -267,17 +458,23 @@ interface ChallengeProgress {
267
458
  claimed_at?: string | null;
268
459
  }
269
460
  interface CurrencyBalance {
270
- currency_id: string;
271
- key: string;
461
+ currency_code: string;
462
+ name: string;
463
+ is_premium: boolean;
272
464
  balance: number;
273
- updated_at: string;
465
+ lifetime_earned: number;
466
+ lifetime_spent: number;
274
467
  }
275
468
  interface CurrencyTransaction {
276
469
  id: string;
277
- currency_id: string;
278
- delta: number;
470
+ app_user_id?: string;
471
+ currency_code: string;
472
+ amount: number;
279
473
  balance_after: number;
280
- reason?: string | null;
474
+ transaction_type?: string;
475
+ source?: string;
476
+ reference_id?: string | null;
477
+ idempotency_key?: string | null;
281
478
  created_at: string;
282
479
  }
283
480
  interface InventoryItem {
@@ -688,6 +885,8 @@ interface LeagueCohortMember {
688
885
  display_name?: string | null;
689
886
  score: number;
690
887
  rank: number;
888
+ /** `true` for the requesting user's own row (server-derived; no id leak). */
889
+ is_me?: boolean;
691
890
  }
692
891
  interface LeagueMembership {
693
892
  cohort: LeagueCohort | null;
@@ -705,6 +904,60 @@ interface MediaListResponse {
705
904
  data: MediaAsset[];
706
905
  next_cursor?: string | null;
707
906
  }
907
+ /** A single asset within a media catalog, resolved to a public URL. */
908
+ interface MediaCatalogItem {
909
+ media_id: string;
910
+ position: number;
911
+ caption?: string | null;
912
+ metadata: Record<string, unknown>;
913
+ filename: string;
914
+ mime_type: string;
915
+ /** Stable, long-cacheable public URL for the asset. */
916
+ url: string;
917
+ thumbnail_url?: string | null;
918
+ alt_text?: string | null;
919
+ blurhash?: string | null;
920
+ }
921
+ /**
922
+ * A curated, slug-addressable bundle of media assets fetched in one call.
923
+ * Items are returned in their configured order.
924
+ */
925
+ interface MediaCatalog {
926
+ slug: string;
927
+ name: string;
928
+ description?: string | null;
929
+ metadata: Record<string, unknown>;
930
+ updated_at: string;
931
+ items: MediaCatalogItem[];
932
+ }
933
+ /**
934
+ * One rule a candidate event would trigger, as returned by
935
+ * `Amba.events.explain`. `would` describes the effect that WOULD be produced
936
+ * if the event were tracked for real; `note` (when present) flags a per-user
937
+ * gate (daily limit, cooldown, balance cap) that would suppress or reduce it.
938
+ */
939
+ interface ExplainMatchedRule {
940
+ rule_type: "xp" | "currency_grant" | "feed" | "streak" | "webhook";
941
+ rule_id: string;
942
+ name: string;
943
+ would: Record<string, unknown>;
944
+ note?: string;
945
+ }
946
+ /**
947
+ * Result of a read-only event rule dry-run (`Amba.events.explain`). Producing
948
+ * this commits no side effect — it is a preview of what tracking the event
949
+ * would do across XP, currency grants, feed items, streaks, and webhooks.
950
+ */
951
+ interface EventExplainResult {
952
+ event: {
953
+ name: string;
954
+ properties: Record<string, unknown>;
955
+ user_id: string | null;
956
+ };
957
+ matched: ExplainMatchedRule[];
958
+ evaluated_surfaces: string[];
959
+ unmatched_reason_samples?: string[];
960
+ }
708
961
  /**
709
962
  * Options for `Amba.collections.findNearest`.
710
963
  *
@@ -727,6 +980,46 @@ interface NearestOptions {
727
980
  interface NearestResponse<T> {
728
981
  data: T[];
729
982
  }
983
+ /**
984
+ * When a local notification should fire. Exactly one shape:
985
+ *
986
+ * - `{ at: Date }` — fire once at an absolute time.
987
+ * - `{ inSeconds, repeats? }` — fire after a relative delay.
988
+ *
989
+ * Local notifications are scheduled and fired by the device itself, with
990
+ * no server involvement — distinct from remote push (`Amba.push.*`).
991
+ * Browser scheduling is partial; see `notifications.scheduleLocal`.
992
+ */
993
+ type LocalNotificationTrigger = {
994
+ at: Date | string;
995
+ } | {
996
+ inSeconds: number;
997
+ repeats?: boolean;
998
+ };
999
+ /** Input to `Amba.notifications.scheduleLocal`. */
1000
+ interface ScheduleLocalNotificationInput {
1001
+ /** Optional stable id; omit to let the platform mint one. */
1002
+ id?: string;
1003
+ /** Notification title (required, non-empty). */
1004
+ title: string;
1005
+ /** Notification body text. */
1006
+ body: string;
1007
+ /** Arbitrary payload delivered to the app when the notification is tapped. */
1008
+ data?: Record<string, unknown>;
1009
+ /** When to fire it. */
1010
+ trigger: LocalNotificationTrigger;
1011
+ }
1012
+ /** Handle returned by `scheduleLocal`; `id` is accepted by `cancelLocal`. */
1013
+ interface LocalNotificationHandle {
1014
+ id: string;
1015
+ }
1016
+ /** A pending local notification, as reported by `listScheduledLocal`. */
1017
+ interface ScheduledLocalNotification {
1018
+ id: string;
1019
+ title: string;
1020
+ body: string;
1021
+ data?: Record<string, unknown>;
1022
+ }
730
1023
  //#endregion
731
1024
  //#region src/realtime.d.ts
732
1025
  /**
@@ -825,6 +1118,184 @@ declare class ConversationSubscription<T = unknown> {
825
1118
  constructor(ctx: RealtimeContext, conversationId: string);
826
1119
  subscribe(cb: (change: MessageChange<T>) => void, options?: SubscribeOptions): Unsubscribe;
827
1120
  }
1121
+ /** Which slice of the signed-in user's gamification state changed. */
1122
+ type GamificationKind = "xp" | "level" | "achievement" | "streak";
1123
+ /**
1124
+ * A live change to the signed-in user's gamification state. `kind` says which
1125
+ * slice moved; `data` carries the new state inline so you can update without a
1126
+ * re-fetch:
1127
+ * - `xp` — `{ total_xp, level, delta }`
1128
+ * - `level` — `{ level, total_xp }`
1129
+ * - `achievement` — `{ achievement_id, unlocked }`
1130
+ * - `streak` — `{ streak_id, count, status, ... }`
1131
+ *
1132
+ * If a payload was too large to deliver inline, `truncated` is `true` and `data`
1133
+ * is empty — re-fetch with `Amba.xp.getBalance()` / `Amba.streaks.getAll()` etc.
1134
+ */
1135
+ interface GamificationChange<T = Record<string, unknown>> {
1136
+ kind: GamificationKind;
1137
+ data: T;
1138
+ truncated?: boolean;
1139
+ }
1140
+ /**
1141
+ * Builder returned by `Amba.gamification`. Call `.subscribe(cb)` to receive the
1142
+ * signed-in user's xp / level / achievement / streak changes live instead of
1143
+ * polling.
1144
+ *
1145
+ * const unsub = Amba.gamification.subscribe((change) => {
1146
+ * // change.kind: "xp" | "level" | "achievement" | "streak"
1147
+ * // change.data: the new state
1148
+ * });
1149
+ * unsub(); // stop listening
1150
+ *
1151
+ * Delivery is scoped to the signed-in user — you only ever receive your own
1152
+ * gamification changes, never another user's. Reconnects with the same
1153
+ * exponential backoff as collection subscriptions.
1154
+ */
1155
+ declare class GamificationSubscription {
1156
+ private readonly ctx;
1157
+ constructor(ctx: RealtimeContext);
1158
+ subscribe(cb: (change: GamificationChange) => void, options?: SubscribeOptions): Unsubscribe;
1159
+ }
1160
+ //#endregion
1161
+ //#region src/ai-stream.d.ts
1162
+ /** A streamed token delta — append `text` to your running output buffer. */
1163
+ interface AiStreamDelta {
1164
+ type: "delta";
1165
+ /** The incremental text the model just produced. */
1166
+ text: string;
1167
+ }
1168
+ /**
1169
+ * Terminal event. Emitted once when the stream completes cleanly. `usage`
1170
+ * carries the token accounting accumulated across the stream (zeroed fields
1171
+ * when the vendor didn't report a given counter); `model` / `stopReason` are
1172
+ * populated when the vendor surfaced them.
1173
+ */
1174
+ interface AiStreamDone {
1175
+ type: "done";
1176
+ usage: AiUsage;
1177
+ model?: string;
1178
+ stopReason?: string | null;
1179
+ }
1180
+ /** Vendor-neutral event yielded by the streaming AI methods. */
1181
+ type AiStreamEvent = AiStreamDelta | AiStreamDone;
1182
+ /** Options accepted by the streaming AI methods. */
1183
+ interface AiStreamOptions {
1184
+ /**
1185
+ * Optional callback invoked for every text delta as it arrives — a
1186
+ * convenience over iterating, useful when you only care about appending
1187
+ * tokens to the UI. Fires in addition to the yielded `delta` events.
1188
+ */
1189
+ onChunk?: (text: string) => void;
1190
+ }
1191
+ //#endregion
1192
+ //#region src/resilience.d.ts
1193
+ /**
1194
+ * Offline mutation queue + optimistic-update helpers — framework-agnostic
1195
+ * orchestration that sits ABOVE the collection / event write methods.
1196
+ *
1197
+ * Nothing here touches the SDK runtime directly: the queue is handed a
1198
+ * small `KvStore` (durable key/value persistence) and a `WriteExecutor`
1199
+ * (the function that actually performs a queued mutation against the API).
1200
+ * That keeps the same code identical across every JS SDK target — only the
1201
+ * adapters that supply storage + the executor differ per platform.
1202
+ *
1203
+ * Design:
1204
+ * - A write that fails because the device is offline (a transport-level
1205
+ * network error, or `navigator.onLine === false`) is appended to a
1206
+ * durable outbox instead of throwing all the way up.
1207
+ * - Each queued op carries a stable idempotency key. On replay the key is
1208
+ * forwarded to the API's collection-write idempotency support so a
1209
+ * mutation that actually reached the server before the connection
1210
+ * dropped is never applied twice.
1211
+ * - On reconnect (`online` event) the outbox is flushed in FIFO order;
1212
+ * each op is removed only after it succeeds, so a mid-flush failure
1213
+ * leaves the remainder intact for the next attempt.
1214
+ */
1215
+ /**
1216
+ * Minimal durable key/value contract the outbox persists through. Both the
1217
+ * browser `localStorage` adapter and the React Native AsyncStorage adapter
1218
+ * satisfy this — the queue never assumes anything else.
1219
+ */
1220
+ interface KvStore {
1221
+ get(key: string): Promise<string | null> | string | null;
1222
+ set(key: string, value: string): Promise<void> | void;
1223
+ delete(key: string): Promise<void> | void;
1224
+ }
1225
+ /** The kinds of mutation the outbox can durably replay. */
1226
+ type OutboxOpKind = "collection.insert" | "collection.update" | "collection.delete" | "track";
1227
+ /**
1228
+ * A single durably-queued mutation. `idempotencyKey` is reused verbatim on
1229
+ * every replay attempt so the server dedupes a COLLECTION write that landed
1230
+ * before the connection dropped (the events `track` API has no idempotency
1231
+ * channel, so `track` replays are at-least-once — a rare reconnect duplicate
1232
+ * is acceptable for fire-and-forget analytics). `enqueuedAt` is an ISO
1233
+ * timestamp for inspection.
1234
+ */
1235
+ interface OutboxOp {
1236
+ /** Stable per-op id (the idempotency key for collection writes). */
1237
+ idempotencyKey: string;
1238
+ kind: OutboxOpKind;
1239
+ /** Target collection name (absent for `track`). */
1240
+ collection?: string;
1241
+ /** Row id for update / delete. */
1242
+ id?: string;
1243
+ /** Insert row / update set payload. */
1244
+ payload?: Record<string, unknown>;
1245
+ /** Event name for `track`. */
1246
+ event?: string;
1247
+ /** Event properties for `track`. */
1248
+ properties?: Record<string, unknown>;
1249
+ /** Whether the tracked event was telemetry-tier. */
1250
+ telemetry?: boolean;
1251
+ /**
1252
+ * ISO timestamp the `track` originally OCCURRED, captured at enqueue. Replayed
1253
+ * as the events body `timestamp` (→ `occurred_at`) so a buffered event keeps
1254
+ * its real time instead of being re-stamped at flush. (Attribution is by the
1255
+ * session user at replay time — the events route has no body-id channel.)
1256
+ */
1257
+ occurredAt?: string;
1258
+ enqueuedAt: string;
1259
+ }
1260
+ /** Result of a {@link OfflineQueue.flush}. */
1261
+ interface FlushResult {
1262
+ /** Ops successfully replayed and removed from the outbox. */
1263
+ flushed: number;
1264
+ /**
1265
+ * Ops dropped because their replay failed deterministically (a coded server
1266
+ * rejection like `VALIDATION_ERROR` / `NOT_FOUND` that would fail identically
1267
+ * forever). Dropping them prevents one poisoned op from wedging the queue;
1268
+ * the drain continues past it.
1269
+ */
1270
+ dropped: number;
1271
+ /** Ops still pending after the flush (a replay failed and stopped it). */
1272
+ remaining: number;
1273
+ }
1274
+ /**
1275
+ * Apply a local change immediately, fire the real mutation, and roll the
1276
+ * local change back if the mutation fails. Framework-agnostic: `apply` and
1277
+ * `rollback` operate on whatever local view you hand them (a React state
1278
+ * setter, a store, an in-memory cache).
1279
+ *
1280
+ * Resolves with the mutation's result on success. On failure it invokes
1281
+ * `rollback` (with the value `apply` returned, so you can restore a captured
1282
+ * snapshot) and rethrows the original error.
1283
+ *
1284
+ * @example
1285
+ * await withOptimistic({
1286
+ * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },
1287
+ * mutate: () => Amba.collections.insert("todos", draft),
1288
+ * rollback: (prev) => setTodos(prev),
1289
+ * });
1290
+ */
1291
+ declare function withOptimistic<TApply, TResult>(spec: {
1292
+ /** Mutate the local view now. Return a snapshot for `rollback` to restore. */
1293
+ apply: () => TApply;
1294
+ /** The real async mutation against the API. */
1295
+ mutate: () => Promise<TResult>;
1296
+ /** Undo the local change. Receives whatever `apply` returned. */
1297
+ rollback: (applied: TApply) => void;
1298
+ }): Promise<TResult>;
828
1299
  //#endregion
829
1300
  //#region src/error.d.ts
830
1301
  /**
@@ -844,14 +1315,58 @@ declare class ConversationSubscription<T = unknown> {
844
1315
  * `new AmbaApiError("MY_CODE", "...")` without coordinating with this
845
1316
  * package's TypeScript types.
846
1317
  */
847
- type AmbaApiErrorCode = "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMITED" | "VALIDATION_ERROR" | "NETWORK_ERROR" | "HTTP_ERROR" | "CIRCUIT_OPEN" | "CONSENT_NOT_GRANTED" | "NOT_INITIALIZED" | "INVALID_CONFIG" | "INVALID_ARGUMENT" | "PUSH_PERMISSION_DENIED" | "PUSH_REGISTRATION_FAILED" | "NOT_SANDBOX_TIER" | "UNKNOWN_ERROR" | (string & {});
1318
+ type AmbaApiErrorCode = "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMITED" | "VALIDATION_ERROR" | "NETWORK_ERROR" | "HTTP_ERROR" | "CIRCUIT_OPEN" | "CONSENT_NOT_GRANTED" | "NOT_INITIALIZED" | "INVALID_CONFIG" | "INVALID_ARGUMENT" | "PUSH_PERMISSION_DENIED" | "PUSH_REGISTRATION_FAILED" | "LOCAL_NOTIFICATIONS_UNAVAILABLE" | "LOCAL_NOTIFICATIONS_PERMISSION_DENIED" | "NOT_SANDBOX_TIER" | "UNKNOWN_ERROR" | (string & {});
848
1319
  declare class AmbaApiError extends Error {
849
1320
  readonly code: AmbaApiErrorCode;
850
1321
  readonly details?: unknown;
1322
+ /**
1323
+ * Server-side request / trace id (the `X-Amba-Request-Id` response
1324
+ * header, falling back to the `request_id` field in the standard error
1325
+ * envelope). Present on errors thrown by `Amba.functions.invoke()` so a
1326
+ * failed run can be correlated with server logs
1327
+ * (`functions logs --request-id <traceId>`). `undefined` when the failure
1328
+ * happened before a response was received (e.g. a transport error).
1329
+ */
1330
+ traceId?: string;
851
1331
  constructor(code: AmbaApiErrorCode, message: string, details?: unknown);
852
1332
  }
853
1333
  //#endregion
1334
+ //#region src/local-notifications.d.ts
1335
+ /**
1336
+ * The web `notifications` namespace. Web capability is partial — see the
1337
+ * module-level doc. The returned handle includes `scheduledVia` so apps
1338
+ * can detect whether the notification was handed to the OS
1339
+ * (`"os-trigger"`) or relies on an in-page timer (`"timer"`).
1340
+ */
1341
+ declare const notifications: {
1342
+ scheduleLocal(input: ScheduleLocalNotificationInput): Promise<LocalNotificationHandle & {
1343
+ scheduledVia: "os-trigger" | "timer";
1344
+ }>;
1345
+ cancelLocal(id: string): Promise<void>;
1346
+ listScheduledLocal(): Promise<ScheduledLocalNotification[]>;
1347
+ };
1348
+ type LocalNotificationsNamespace = typeof notifications;
1349
+ //#endregion
854
1350
  //#region src/index.d.ts
1351
+ /** Options for `Amba.functions.invoke`. */
1352
+ interface InvokeOptions {
1353
+ /** HTTP method. Defaults to `"POST"`. */
1354
+ method?: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
1355
+ /** Extra headers merged on top of the injected auth headers. */
1356
+ headers?: Record<string, string>;
1357
+ /**
1358
+ * Called with the server-side request / trace id (the
1359
+ * `X-Amba-Request-Id` response header) as soon as a response is
1360
+ * received — on both success and failure. Use it to correlate a run with
1361
+ * server logs (`functions logs --request-id <traceId>`). `null` when the
1362
+ * server returned no trace id (e.g. a pre-auth rejection that doesn't
1363
+ * stamp the header).
1364
+ *
1365
+ * The same id is also attached to any thrown `AmbaApiError` as
1366
+ * `error.traceId`, so error-path correlation works without a callback.
1367
+ */
1368
+ onTraceId?: (traceId: string | null) => void;
1369
+ }
855
1370
  /**
856
1371
  * Top-level SDK namespace.
857
1372
  *
@@ -900,6 +1415,23 @@ declare const Amba: {
900
1415
  track: (event: string, properties?: Record<string, unknown>, options?: {
901
1416
  telemetry?: boolean;
902
1417
  }) => Promise<void>;
1418
+ /**
1419
+ * Dry-run an event against the rule engine WITHOUT firing it. Returns
1420
+ * which rules would match and what each WOULD produce — XP awards,
1421
+ * currency grants, feed items, streak qualification, and webhook
1422
+ * deliveries — so you can predict the consequences of a `track` call
1423
+ * before committing any side effect. Nothing is written.
1424
+ *
1425
+ * Pass `{ userId }` to also preview per-user gating (daily limits,
1426
+ * cooldowns, balance caps); omit it for the user-independent base match.
1427
+ *
1428
+ * @example
1429
+ * const plan = await Amba.events.explain('lesson_completed', { score: 90 });
1430
+ * // plan.matched → [{ rule_type: 'xp', would: { xp_delta: 25 }, ... }, ...]
1431
+ */
1432
+ explain: (event: string, properties?: Record<string, unknown>, options?: {
1433
+ userId?: string | null;
1434
+ }) => Promise<EventExplainResult>;
903
1435
  };
904
1436
  /** Authentication flows. */
905
1437
  auth: {
@@ -1009,6 +1541,32 @@ declare const Amba: {
1009
1541
  * code is `EMAIL_ALREADY_LINKED`.
1010
1542
  */
1011
1543
  linkEmailOtp: (email: string, code: string) => Promise<User>;
1544
+ /**
1545
+ * Upgrade an anonymous (or any current) session by attaching an
1546
+ * email + password credential — converting the current user in place
1547
+ * to an email-identified account so its data (streak, library,
1548
+ * progress, `anonymous_id`) is preserved rather than minting a new
1549
+ * user.
1550
+ *
1551
+ * Guarantees (symmetric to `linkEmailOtp`):
1552
+ *
1553
+ * - **Same `user_id`** before and after.
1554
+ * - **Session token unchanged.** No rotation; `onAuthStateChange`
1555
+ * does NOT fire.
1556
+ * - **Idempotent.** Re-linking the same email to the same user
1557
+ * succeeds.
1558
+ * - **Collision.** If the email is already owned by a different user,
1559
+ * throws an error with `code = "EMAIL_ALREADY_LINKED"` and
1560
+ * `details.conflicting_user_id` populated.
1561
+ *
1562
+ * After linking, the user can sign in with `signInWithEmail(email,
1563
+ * password)`. Requires an active session — throws `UNAUTHORIZED`
1564
+ * otherwise.
1565
+ *
1566
+ * @returns the updated user record (with `email` set and
1567
+ * `email_verified = true`).
1568
+ */
1569
+ linkEmailPassword: (email: string, password: string) => Promise<User>;
1012
1570
  /**
1013
1571
  * Detach the SMS-OTP identifier from the current session's user.
1014
1572
  * Refused with `LAST_IDENTIFIER` if this would leave the account
@@ -1121,6 +1679,54 @@ declare const Amba: {
1121
1679
  deleted: boolean;
1122
1680
  };
1123
1681
  }>;
1682
+ /**
1683
+ * Insert a row, resolving a unique-index conflict instead of failing.
1684
+ *
1685
+ * With `options.onConflict: "ignore"` a conflicting row is left
1686
+ * untouched and returned; with `"update"` the supplied columns are
1687
+ * merged into the existing row. `options.conflictTarget` names the
1688
+ * unique-index columns to arbitrate on (e.g. `["user_id", "kind"]`) and
1689
+ * every one must be present in `row`. Auto-RLS still applies — the
1690
+ * ignore-fallback / merge is scoped to the caller's own rows.
1691
+ *
1692
+ * @example
1693
+ * await Amba.collections.upsert("prefs",
1694
+ * { kind: "theme", value: "dark" },
1695
+ * { onConflict: "update", conflictTarget: ["kind"] });
1696
+ */
1697
+ upsert: <T = unknown>(name: string, row: Record<string, unknown>, options: UpsertOptions) => Promise<T>;
1698
+ /**
1699
+ * Bulk soft-delete every row matching `filter`. Returns the count of
1700
+ * rows deleted. Auto-RLS scopes the delete to the caller's own rows;
1701
+ * soft-deleted rows can be brought back with {@link restore}.
1702
+ *
1703
+ * @example
1704
+ * const { data } = await Amba.collections.deleteWhere(
1705
+ * "todos", Amba.collections.where.eq("status", "done"));
1706
+ * console.log(`${data.deleted} removed`);
1707
+ */
1708
+ deleteWhere: (name: string, filter: Filter) => Promise<DeleteManyResult>;
1709
+ /**
1710
+ * Restore a previously soft-deleted row by id (clears `deleted_at`).
1711
+ * Returns the restored row. Only rows currently soft-deleted and owned
1712
+ * by the caller can be restored; anything else resolves as not-found.
1713
+ */
1714
+ restore: <T = unknown>(name: string, id: string) => Promise<T>;
1715
+ /**
1716
+ * Run an ordered list of writes (insert / update / delete, across any
1717
+ * collections) as ONE atomic transaction. The whole batch commits, or — if
1718
+ * any op fails — rolls back entirely; a partial multi-write can never
1719
+ * persist. Resolves to the per-op results; throws naming the failing op
1720
+ * index (`details.opIndex`) on rollback. Each op honors the same auto-RLS /
1721
+ * owner-scoping / compare-and-set rules as the single-row methods.
1722
+ *
1723
+ * @example
1724
+ * await Amba.collections.transaction([
1725
+ * { collection: "carts", op: "insert", row: { sku: "widget", qty: 2 } },
1726
+ * { collection: "inventory", op: "update", id: invId, set: { reserved: 2 } },
1727
+ * ]);
1728
+ */
1729
+ transaction: (operations: TransactionOp[]) => Promise<TransactionOpResult[]>;
1124
1730
  /**
1125
1731
  * Vector-search the collection (SDK 4.0). Returns the `k` nearest
1126
1732
  * neighbors of `options.query_vector` against the column named
@@ -1140,6 +1746,45 @@ declare const Amba: {
1140
1746
  * a bare number on the JS side.
1141
1747
  */
1142
1748
  count: (name: string, filter?: Filter) => Promise<number>;
1749
+ /**
1750
+ * Optimistic insert — update a local view immediately, fire the real
1751
+ * insert, and roll the local view back if it fails. The reconcile is your
1752
+ * own (replace the temporary local row with the server's authoritative
1753
+ * row in your `apply`/`rollback` handlers). Thin convenience over the
1754
+ * exported {@link withOptimistic} so the common single-write case reads
1755
+ * clean; for multi-step flows use `withOptimistic` directly.
1756
+ *
1757
+ * Optimistic writes always hit the network directly — they are NOT
1758
+ * diverted to the offline outbox even when `Amba.offline` is enabled.
1759
+ * Optimistic UI needs an unambiguous outcome: this resolves only on a
1760
+ * confirmed server write, and rejects (running your `rollback`) on any
1761
+ * failure including being offline. To buffer-and-replay instead, use the
1762
+ * plain `insert`/`update`/`delete` with the outbox on.
1763
+ *
1764
+ * @example
1765
+ * await Amba.collections.optimisticInsert("todos", draft, {
1766
+ * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },
1767
+ * rollback: (prev) => setTodos(prev),
1768
+ * });
1769
+ */
1770
+ optimisticInsert: <T = unknown, TApply = unknown>(name: string, row: Record<string, unknown>, view: {
1771
+ apply: () => TApply;
1772
+ rollback: (applied: TApply) => void;
1773
+ }) => Promise<T>;
1774
+ /** Optimistic update — see {@link optimisticInsert}. */
1775
+ optimisticUpdate: <T = unknown, TApply = unknown>(name: string, id: string, set: Record<string, unknown>, view: {
1776
+ apply: () => TApply;
1777
+ rollback: (applied: TApply) => void;
1778
+ }) => Promise<T>;
1779
+ /** Optimistic delete — see {@link optimisticInsert}. */
1780
+ optimisticDelete: <TApply = unknown>(name: string, id: string, view: {
1781
+ apply: () => TApply;
1782
+ rollback: (applied: TApply) => void;
1783
+ }) => Promise<{
1784
+ data: {
1785
+ deleted: boolean;
1786
+ };
1787
+ }>;
1143
1788
  /** Filter DSL helpers — pass into `find({ filter: f })`. */
1144
1789
  where: {
1145
1790
  eq: (column: string, value: FilterValue) => Filter;
@@ -1169,7 +1814,7 @@ declare const Amba: {
1169
1814
  retentionDays?: number;
1170
1815
  }) => Promise<PresignData>;
1171
1816
  /**
1172
- * Complete-flow upload: presign → PUT to R2 → commit.
1817
+ * Complete-flow upload: presign → PUT to object storage → commit.
1173
1818
  * Returns the finalized MediaAsset.
1174
1819
  */
1175
1820
  upload: (params: {
@@ -1187,7 +1832,7 @@ declare const Amba: {
1187
1832
  list: (prefix?: string) => Promise<MediaListResponse>;
1188
1833
  /**
1189
1834
  * Soft-delete the asset (SDK 4.0). Accepts the asset id returned
1190
- * by `presign` / `commit` / `list`. The corresponding R2 object
1835
+ * by `presign` / `commit` / `list`. The corresponding storage object
1191
1836
  * is removed asynchronously on the server.
1192
1837
  */
1193
1838
  delete: (assetId: string) => Promise<void>;
@@ -1196,11 +1841,23 @@ declare const Amba: {
1196
1841
  * the Uint8Array returned by the engine, so consumers can
1197
1842
  * call `.text()` / `.arrayBuffer()` / `.stream()` directly or
1198
1843
  * pipe to a `URL.createObjectURL()` for preview. The two-step
1199
- * fetch (asset metadata → CDN GET) is handled server-side on
1200
- * the WASM side; this method is single-call from the consumer.
1844
+ * fetch (asset metadata → CDN GET) is handled inside the SDK
1845
+ * core; this method is single-call from the consumer.
1201
1846
  */
1202
1847
  download: (assetId: string) => Promise<Blob>;
1203
1848
  };
1849
+ /** Curated public-asset catalogs. */
1850
+ media: {
1851
+ /**
1852
+ * Fetch a public media catalog by slug
1853
+ * (`GET /v1/client/media/catalogs/:slug`). A catalog is a named,
1854
+ * curated bundle of assets your app loads in one call — each item
1855
+ * resolves to a stable, long-cacheable public URL, returned in the
1856
+ * order configured for the catalog. Only catalogs published as public
1857
+ * are readable; the call needs the client key but no signed-in user.
1858
+ */
1859
+ catalog: (slug: string) => Promise<MediaCatalog>;
1860
+ };
1204
1861
  /** Offline change replay (SDK 4.0). */
1205
1862
  sync: {
1206
1863
  /**
@@ -1221,6 +1878,53 @@ declare const Amba: {
1221
1878
  */
1222
1879
  pullChanges: (since: PullChangesCursor) => Promise<PullChangesResult>;
1223
1880
  };
1881
+ /**
1882
+ * Automatic offline mutation queue (opt-in).
1883
+ *
1884
+ * Once enabled, a collection write (`insert` / `update` / `delete`) or a
1885
+ * `track` that fails because the device is offline is buffered to a durable
1886
+ * local outbox instead of throwing — then replayed in order the moment
1887
+ * connectivity returns. Replays reuse a per-op idempotency key so a write
1888
+ * that actually reached the server before the connection dropped is never
1889
+ * applied twice.
1890
+ *
1891
+ * This is distinct from `Amba.sync` (manual change replay): `offline` is
1892
+ * the automatic, fire-and-forget safety net for ordinary writes.
1893
+ *
1894
+ * @example
1895
+ * Amba.offline.enable(); // call once after configure()
1896
+ * await Amba.collections.insert("todos", { title: "Buy milk" }); // queues if offline
1897
+ * // ...reconnect...
1898
+ * console.log(await Amba.offline.pendingCount()); // drains automatically
1899
+ */
1900
+ offline: {
1901
+ /**
1902
+ * Turn the outbox on and start auto-flushing on reconnect. Idempotent —
1903
+ * a repeat call detaches the prior `online` listener before re-attaching,
1904
+ * so it never stacks listeners. Immediately attempts a flush in case ops
1905
+ * were persisted in a prior session. Returns a disable function that
1906
+ * detaches the reconnect listener (queued ops are NOT dropped — call
1907
+ * `clear()` for that).
1908
+ *
1909
+ * The disabler is tied to THIS activation: once a later `enable()` (or a
1910
+ * `configure()`) supersedes it, calling it is a no-op — so a stale disabler
1911
+ * can never turn a freshly re-enabled outbox off.
1912
+ */
1913
+ enable: () => (() => void);
1914
+ /** Whether the outbox is currently enabled. */
1915
+ isEnabled: () => boolean;
1916
+ /** Count of writes awaiting replay. */
1917
+ pendingCount: () => Promise<number>;
1918
+ /** Snapshot the pending ops (a copy — safe to inspect). */
1919
+ pending: () => Promise<OutboxOp[]>;
1920
+ /**
1921
+ * Force a replay attempt now (also runs automatically on reconnect).
1922
+ * Resolves with `{ flushed, dropped, remaining }` (dropped = ops the server rejected deterministically on replay, removed so they cannot wedge the queue).
1923
+ */
1924
+ flush: () => Promise<FlushResult>;
1925
+ /** Drop every queued write without replaying. */
1926
+ clear: () => Promise<void>;
1927
+ };
1224
1928
  /** Push notifications. */
1225
1929
  push: {
1226
1930
  register: (token: string, platform: PushPlatform, bundleId?: string) => Promise<PushToken>;
@@ -1228,19 +1932,142 @@ declare const Amba: {
1228
1932
  getTokens: () => Promise<PushToken[]>;
1229
1933
  subscribe: (topic: string) => Promise<void>;
1230
1934
  unsubscribe: (topic: string) => Promise<void>;
1935
+ /**
1936
+ * Auto re-register the device's Web Push subscription when the browser
1937
+ * rotates it.
1938
+ *
1939
+ * The browser can silently roll a push subscription (key rollover,
1940
+ * permission re-grant); when it does, the service worker fires
1941
+ * `pushsubscriptionchange` and the old endpoint stops delivering. Call
1942
+ * this from your service worker (where that event fires) and the SDK
1943
+ * re-registers the fresh endpoint through the idempotent
1944
+ * {@link register} path automatically.
1945
+ *
1946
+ * No-op (returns a no-op detacher) in runtimes without service-worker /
1947
+ * push support, so it's safe to call unconditionally. Returns an
1948
+ * unsubscribe function.
1949
+ *
1950
+ * @example
1951
+ * // in your service worker
1952
+ * Amba.push.enableAutoRotation();
1953
+ */
1954
+ enableAutoRotation: () => (() => void);
1231
1955
  };
1232
- /** RevenueCat/App Store/Stripe entitlements. */
1956
+ /**
1957
+ * On-device local notifications — `scheduleLocal` / `cancelLocal` /
1958
+ * `listScheduledLocal`. Fire from the device without a server
1959
+ * round-trip (reminders, streak nudges), distinct from remote push
1960
+ * (`Amba.push.*`). Browser scheduling is partial: the OS-backed path
1961
+ * needs the experimental Notification Triggers API + an active Service
1962
+ * Worker (Chromium today); without it the SDK falls back to an in-page
1963
+ * timer and reports `scheduledVia: "timer"` so the degradation is
1964
+ * visible — never a silent no-op. See `./local-notifications.ts`.
1965
+ */
1966
+ notifications: {
1967
+ scheduleLocal(input: ScheduleLocalNotificationInput): Promise<LocalNotificationHandle & {
1968
+ scheduledVia: "os-trigger" | "timer";
1969
+ }>;
1970
+ cancelLocal(id: string): Promise<void>;
1971
+ listScheduledLocal(): Promise<ScheduledLocalNotification[]>;
1972
+ };
1973
+ /** Subscription entitlements — feature gates resolved server-side. */
1233
1974
  entitlements: {
1234
1975
  list: () => Promise<UserEntitlement[]>;
1235
1976
  has: (name: string) => Promise<boolean>;
1977
+ /**
1978
+ * Restore purchases — re-sync the user's owned entitlements from the
1979
+ * server and return the active set. Required for App Store review (3.1.1);
1980
+ * wire it to a "Restore Purchases" button. Rejects (access unchanged) if
1981
+ * the server can't reach the validation engine — show a retry message.
1982
+ */
1983
+ restore: () => Promise<RestoreResult>;
1236
1984
  };
1985
+ /**
1986
+ * Subscription offerings — the packages your paywall renders, provider-
1987
+ * neutral. Pass an `offeringId` to fetch one offering; omit it for all.
1988
+ */
1989
+ offerings: (offeringId?: string) => Promise<OfferingsResult>;
1237
1990
  /** LLM proxy. */
1238
1991
  ai: {
1239
1992
  anthropic: {
1240
1993
  messages: {
1241
1994
  create: (request: AiMessageRequest) => Promise<AiMessageResponse>;
1995
+ /**
1996
+ * Stream the response token-by-token instead of waiting for the full
1997
+ * buffered reply. Returns an async iterable of vendor-neutral events:
1998
+ * zero or more `{type:'delta', text}` as tokens arrive, then exactly
1999
+ * one `{type:'done', usage, model, stopReason}` when the stream ends.
2000
+ *
2001
+ * ```ts
2002
+ * let answer = "";
2003
+ * for await (const ev of Amba.ai.anthropic.messages.stream({ prompt_slug: "chat" })) {
2004
+ * if (ev.type === "delta") answer += ev.text;
2005
+ * else console.log("tokens:", ev.usage.output_tokens);
2006
+ * }
2007
+ * ```
2008
+ *
2009
+ * Pass `{ onChunk }` to also receive each delta via callback. Throws
2010
+ * an `AmbaApiError` on a transport failure or a non-2xx response.
2011
+ */
2012
+ stream: (request: AiMessageRequest, options?: AiStreamOptions) => AsyncGenerator<AiStreamEvent, void, unknown>;
1242
2013
  };
1243
2014
  };
2015
+ openai: {
2016
+ chat: {
2017
+ completions: {
2018
+ /**
2019
+ * Stream an OpenAI-routed prompt token-by-token. Same event shape
2020
+ * as `Amba.ai.anthropic.messages.stream` — the vendor split is just
2021
+ * a stable wire path; the server resolves the real provider from the
2022
+ * prompt's registered configuration.
2023
+ */
2024
+ stream: (request: AiMessageRequest, options?: AiStreamOptions) => AsyncGenerator<AiStreamEvent, void, unknown>;
2025
+ };
2026
+ };
2027
+ };
2028
+ };
2029
+ /**
2030
+ * Invoke a deployed function by name, with the project's client key (and
2031
+ * the live session token, if any) injected automatically.
2032
+ *
2033
+ * This is the authenticated way to call your functions — prefer it over
2034
+ * hand-rolling `fetch` against a function URL, which leaves the call
2035
+ * unauthenticated (a footgun when the function holds secrets).
2036
+ *
2037
+ * The return value is unchanged (the parsed body, typed `T`). To
2038
+ * correlate a run with server logs, read the trace id additively:
2039
+ * pass `options.onTraceId` to receive the `X-Amba-Request-Id` on every
2040
+ * response, and on failure read `error.traceId` off the thrown
2041
+ * `AmbaApiError`. Then `functions logs --request-id <traceId>` pulls the
2042
+ * matching server log.
2043
+ *
2044
+ * @example
2045
+ * const result = await Amba.functions.invoke<{ ok: boolean }>(
2046
+ * 'send_welcome_email',
2047
+ * { to: 'user@example.com' },
2048
+ * { onTraceId: (id) => console.log('trace', id) },
2049
+ * );
2050
+ *
2051
+ * @example
2052
+ * try {
2053
+ * await Amba.functions.invoke('charge', { amount: 100 });
2054
+ * } catch (err) {
2055
+ * if (err instanceof AmbaApiError) {
2056
+ * console.error(`failed (trace ${err.traceId}): ${err.message}`);
2057
+ * }
2058
+ * }
2059
+ *
2060
+ * @param name The deployed function name.
2061
+ * @param body JSON-serializable request body (omitted for GET).
2062
+ * @param options Method, extra headers, and an `onTraceId` callback.
2063
+ * @returns The parsed JSON response (or the raw text if not JSON).
2064
+ * @throws AmbaApiError on a network failure or a non-2xx response. When
2065
+ * the function / router returns the standard `{ error: { code, message } }`
2066
+ * envelope, those surface as the thrown error's `code` + `message`; the
2067
+ * server trace id (when present) is attached as `error.traceId`.
2068
+ */
2069
+ functions: {
2070
+ invoke: <T = unknown>(name: string, body?: unknown, options?: InvokeOptions) => Promise<T>;
1244
2071
  };
1245
2072
  /** Remote config bundle. */
1246
2073
  config: {
@@ -1340,6 +2167,22 @@ declare const Amba: {
1340
2167
  getAll: () => Promise<Streak[]>;
1341
2168
  qualify: (streakKey: string) => Promise<Streak>;
1342
2169
  };
2170
+ /**
2171
+ * Live gamification updates for the signed-in user. Call `.subscribe(cb)` to
2172
+ * receive xp / level / achievement / streak changes the instant they happen,
2173
+ * instead of polling `xp.getBalance()` / `streaks.getAll()` on a timer.
2174
+ *
2175
+ * @example
2176
+ * const unsub = Amba.gamification.subscribe((change) => {
2177
+ * // change.kind: "xp" | "level" | "achievement" | "streak"
2178
+ * if (change.kind === "level") showLevelUp(change.data.level);
2179
+ * });
2180
+ * // later: unsub();
2181
+ *
2182
+ * You only ever receive your own gamification changes — never another user's.
2183
+ * Requires an authenticated session.
2184
+ */
2185
+ readonly gamification: GamificationSubscription;
1343
2186
  /** Activity feeds. */
1344
2187
  feeds: {
1345
2188
  getActivity: (feed?: string, cursor?: string) => Promise<FeedResponse>;
@@ -1559,7 +2402,7 @@ declare function getCoreWasm(): AmbaCoreWasm;
1559
2402
  * `verify-sdk-version-coherence` GH action gates every publish on the
1560
2403
  * pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
1561
2404
  */
1562
- declare const SDK_VERSION = "4.0.3";
2405
+ declare const SDK_VERSION = "4.0.5";
1563
2406
  //#endregion
1564
- export { Achievement, AchievementProgress, AiMessage, AiMessageRequest, AiMessageResponse, AiUsage, Amba, AmbaApiError, type AmbaApiErrorCode, AmbaConfig, AndFilter, AppSession, AuthResult, CatalogItem, Challenge, ChallengeProgress, CollectionSubscription, ConditionFilter, ConfigBundle, ConsumeRequest, ContentItem, Conversation, ConversationParticipant, ConversationSubscription, ConversationType, CreateConversationRequest, CurrencyBalance, CurrencyTransaction, DeepLink, DeepLinkCreate, DiagnosticsEnvironment, EntitlementSource, FeedItem, FeedResponse, Filter, FilterValue, FindOptions, FindResponse, FlagAssignment, Friendship, FriendshipState, Group, GroupCreate, GroupMember, GroupUpdate, InventoryItem, Leaderboard, LeaderboardEntry, LeagueCohort, LeagueCohortMember, LeagueCohortView, LeagueMembership, LeagueTier, LinkedIdentifier, ListMessagesOptions, MarkReadResult, MediaAsset, MediaListResponse, Message, type MessageChange, MessageType, NearestOptions, NearestResponse, NotFilter, OnboardingStatus, OrFilter, OrderBy, PingResult, PresignData, PullChangesCursor, PullChangesResult, PurchaseOption, PurchaseRequest, PurchaseResult, PushChangesResult, PushPlatform, PushToken, type RealtimeChange, ReferralClaim, ReferralCode, Report, ReportRequest, Review, ReviewCreate, ReviewUpdate, Role, type RowChangeOp, SDK_VERSION, SendMessageRequest, Session, SocialProvider, Store, Streak, type SubscribeOptions, SyncChange, SyncConflict, type Unsubscribe, User, UserEntitlement, XpBalance, XpTransaction, getCoreWasm };
2407
+ export { Achievement, AchievementProgress, AiContentBlock, AiImageInput, AiMessage, AiMessageRequest, AiMessageResponse, type AiStreamDelta, type AiStreamDone, type AiStreamEvent, type AiStreamOptions, AiUsage, Amba, AmbaApiError, type AmbaApiErrorCode, AmbaConfig, AndFilter, AppSession, AuthResult, CatalogItem, Challenge, ChallengeProgress, CollectionSubscription, ConditionFilter, ConfigBundle, ConsumeRequest, ContentItem, Conversation, ConversationParticipant, ConversationSubscription, ConversationType, CreateConversationRequest, CurrencyBalance, CurrencyTransaction, DeepLink, DeepLinkCreate, DeleteManyResult, DiagnosticsEnvironment, EntitlementPeriodType, EntitlementStore, EventExplainResult, ExplainMatchedRule, FeedItem, FeedResponse, Filter, FilterValue, FindOptions, FindResponse, FlagAssignment, type FlushResult, Friendship, FriendshipState, type GamificationChange, type GamificationKind, GamificationSubscription, Group, GroupCreate, GroupMember, GroupUpdate, IncludeClause, InventoryItem, InvokeOptions, type KvStore, Leaderboard, LeaderboardEntry, LeagueCohort, LeagueCohortMember, LeagueCohortView, LeagueMembership, LeagueTier, LinkedIdentifier, ListMessagesOptions, LocalNotificationHandle, LocalNotificationTrigger, type LocalNotificationsNamespace, MarkReadResult, MediaAsset, MediaCatalog, MediaCatalogItem, MediaListResponse, Message, type MessageChange, MessageType, NearestOptions, NearestResponse, NotFilter, Offering, OfferingPackage, OfferingProduct, OfferingsResult, OnboardingStatus, OrFilter, OrderBy, type OutboxOp, type OutboxOpKind, PingResult, PresignData, PullChangesCursor, PullChangesResult, PurchaseOption, PurchaseRequest, PurchaseResult, PushChangesResult, PushPlatform, PushToken, type RealtimeChange, ReferralClaim, ReferralCode, Report, ReportRequest, RestoreResult, Review, ReviewCreate, ReviewUpdate, Role, type RowChangeOp, SDK_VERSION, ScheduleLocalNotificationInput, ScheduledLocalNotification, SendMessageRequest, Session, SocialProvider, Store, Streak, type SubscribeOptions, SyncChange, SyncConflict, TransactionOp, TransactionOpResult, type Unsubscribe, UpsertOptions, User, UserEntitlement, XpBalance, XpTransaction, getCoreWasm, withOptimistic };
1565
2408
  //# sourceMappingURL=index.d.ts.map