@layers/amba-web 4.0.6 → 4.0.7

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.
@@ -0,0 +1,2437 @@
1
+ import { AmbaCoreWasm } from "@layers/amba-core";
2
+
3
+ //#region src/types.d.ts
4
+
5
+ /**
6
+ * Public type surface for `@layers/amba-web`.
7
+ *
8
+ * Mirrors the amba API wire format. Field names match the wire format
9
+ * (snake_case) where it would cause confusion to translate; idiomatic
10
+ * camelCase is reserved for the SDK's own option objects.
11
+ */
12
+ interface AmbaConfig {
13
+ /** Project id from the developer console or CLI (`proj_…`). */
14
+ projectId: string;
15
+ /** Client key from the developer console or CLI (`amba_ck_…`). */
16
+ clientKey: string;
17
+ /**
18
+ * Override the API base URL for non-prod environments.
19
+ * Defaults to `"https://api.amba.dev"`.
20
+ */
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;
32
+ /**
33
+ * If `true`, blocks all telemetry until `setConsent(granted)` is called.
34
+ * Required for SOC2 / GDPR strict modes.
35
+ */
36
+ consentRequired?: boolean;
37
+ /** Verbose SDK logging. */
38
+ debug?: boolean;
39
+ /** Override the localStorage key prefix. Defaults to `"amba"`. */
40
+ storagePrefix?: string;
41
+ /**
42
+ * Advanced: explicit URL or RequestInfo for the @layers/amba-core WASM file.
43
+ * Browser bundlers should not need this; the browser entry supplies a
44
+ * bundler-safe core runtime automatically.
45
+ */
46
+ wasmUrl?: RequestInfo | URL;
47
+ /**
48
+ * Advanced: explicit compiled module, response, or bytes for the WASM core.
49
+ * Mutually exclusive with `wasmUrl`.
50
+ */
51
+ wasmModule?: Response | BufferSource | WebAssembly.Module;
52
+ }
53
+ type SocialProvider = "apple" | "google";
54
+ type PushPlatform = "apns" | "fcm" | "web";
55
+ interface User {
56
+ id: string;
57
+ email?: string | null;
58
+ /** E.164-normalized phone number set by SMS-OTP verify/link flows. */
59
+ phone?: string | null;
60
+ /** `true` once email-OTP verify or email-OTP link completed for this email. */
61
+ email_verified?: boolean;
62
+ /** `true` once SMS-OTP verify or SMS-OTP link completed for this phone. */
63
+ phone_verified?: boolean;
64
+ display_name?: string | null;
65
+ avatar_url?: string | null;
66
+ external_id?: string | null;
67
+ anonymous_id?: string | null;
68
+ auth_providers: string[];
69
+ properties: Record<string, unknown>;
70
+ first_seen_at?: string;
71
+ last_seen_at?: string;
72
+ created_at?: string;
73
+ updated_at?: string;
74
+ }
75
+ /**
76
+ * A single identifier attached to the current user — returned by
77
+ * `Amba.auth.getCurrentIdentifiers()`. `identifier_masked` is server-masked
78
+ * (phone last-4 with country-code prefix, `a***@domain` for email); the raw
79
+ * value stays server-side. Social providers (`apple`, `google`) have no
80
+ * value to surface — `identifier_masked` is `null` in that case.
81
+ */
82
+ interface LinkedIdentifier {
83
+ provider: "sms_otp" | "email_otp" | "apple" | "google";
84
+ verified: boolean;
85
+ identifier_masked: string | null;
86
+ }
87
+ interface AuthResult {
88
+ session_token: string;
89
+ refresh_token: string;
90
+ user: User;
91
+ anonymous_id?: string | null;
92
+ expires_at?: string | null;
93
+ }
94
+ /**
95
+ * Snapshot of the current session as visible to the SDK consumer.
96
+ *
97
+ * `sessionToken` / `refreshToken` / `expiresAt` are SDK-managed and held
98
+ * internally for the auto-refresh dance, so they're empty strings on the
99
+ * consumer side — present for shape-compatibility. Callers who need to
100
+ * introspect should branch on `user` / `Amba.isAuthenticated`.
101
+ */
102
+ interface Session {
103
+ sessionToken: string;
104
+ refreshToken: string;
105
+ user: User;
106
+ expiresAt: string;
107
+ }
108
+ type FilterValue = string | number | boolean | null | unknown[];
109
+ interface ConditionFilter {
110
+ column: string;
111
+ op: string;
112
+ value?: FilterValue;
113
+ }
114
+ interface AndFilter {
115
+ and: Filter[];
116
+ }
117
+ interface OrFilter {
118
+ or: Filter[];
119
+ }
120
+ interface NotFilter {
121
+ not: Filter;
122
+ }
123
+ type Filter = ConditionFilter | AndFilter | OrFilter | NotFilter;
124
+ interface OrderBy {
125
+ column: string;
126
+ direction: "asc" | "desc";
127
+ }
128
+ /**
129
+ * Eager-load related rows from another collection in the same `find` call,
130
+ * avoiding an N+1 round-trip per base row.
131
+ *
132
+ * belongs-to — each base row's `localKey` column points at the related row:
133
+ * { collection: "novels", localKey: "novel_id", as: "novel", single: true }
134
+ * → row.novel = the matching novels row (or null)
135
+ *
136
+ * has-many — the related collection's `foreignKey` column points back at the
137
+ * base row's id:
138
+ * { collection: "chapters", foreignKey: "novel_id", as: "chapters" }
139
+ * → row.chapters = the matching chapters rows (array)
140
+ *
141
+ * The included collection's own read policy + soft-delete are honored.
142
+ */
143
+ interface IncludeClause {
144
+ /** Target collection to eager-load from. */
145
+ collection: string;
146
+ /** belongs-to: FK column on the base row pointing at the target. */
147
+ localKey?: string;
148
+ /**
149
+ * Join column on the target. belongs-to: defaults to `id`. has-many:
150
+ * required — the FK column on the target pointing back at the base id.
151
+ */
152
+ foreignKey?: string;
153
+ /** Result key the nested data lands under on each base row. */
154
+ as: string;
155
+ /** belongs-to only: nest a single object instead of an array. */
156
+ single?: boolean;
157
+ /** has-many only: cap related rows per base row (default 100, max 1000). */
158
+ limit?: number;
159
+ /**
160
+ * Filtered include — an extra predicate on the related rows, in the
161
+ * server's where-DSL shape (`{ status: "published" }`,
162
+ * `{ score: { gte: 10 } }`). The target collection's own read policy +
163
+ * soft-delete still apply.
164
+ */
165
+ where?: Record<string, unknown>;
166
+ /**
167
+ * Projection on the included rows — only these columns are returned
168
+ * (join keys are handled internally).
169
+ */
170
+ select?: string[];
171
+ /**
172
+ * Nested include — eager-load relations OF the included rows
173
+ * (e.g. novels → chapters → comments). Max depth 3; max 10 clauses
174
+ * total per find.
175
+ */
176
+ include?: IncludeClause[];
177
+ }
178
+ interface FindOptions {
179
+ filter?: Filter;
180
+ order?: OrderBy[];
181
+ limit?: number;
182
+ cursor?: string;
183
+ select?: string[];
184
+ include_deleted?: boolean;
185
+ /** Eager-load related rows from other collections (kills N+1). */
186
+ include?: IncludeClause[];
187
+ }
188
+ interface FindResponse<T> {
189
+ data: T[];
190
+ next_cursor?: string | null;
191
+ has_more: boolean;
192
+ }
193
+ /**
194
+ * Options for {@link Amba.collections.upsert}.
195
+ *
196
+ * `conflictTarget` is the set of columns covered by the unique index the
197
+ * upsert arbitrates on (e.g. `["user_id", "kind"]`). Every column must be
198
+ * present in the inserted row. `onConflict: "ignore"` keeps the existing
199
+ * row and returns it; `"update"` merges the supplied columns into it.
200
+ */
201
+ interface UpsertOptions {
202
+ onConflict: "ignore" | "update";
203
+ conflictTarget: string[];
204
+ }
205
+ /** Result of {@link Amba.collections.deleteWhere} — the soft-deleted row count. */
206
+ interface DeleteManyResult {
207
+ data: {
208
+ deleted: number;
209
+ };
210
+ }
211
+ /**
212
+ * One operation in an atomic multi-collection transaction
213
+ * ({@link Amba.collections.transaction}). Mirrors the single-row methods:
214
+ * `insert` carries `row` (+ optional upsert controls); `update` carries `id` +
215
+ * `set` (+ optional `expected` compare-and-set); `delete` carries `id` (+
216
+ * optional `expected`). `where` is an extra guard predicate AND'd onto the
217
+ * owner scope.
218
+ */
219
+ interface TransactionOp {
220
+ collection: string;
221
+ op: "insert" | "update" | "delete";
222
+ id?: string;
223
+ row?: Record<string, unknown>;
224
+ set?: Record<string, unknown>;
225
+ where?: Record<string, unknown>;
226
+ expected?: Record<string, unknown>;
227
+ onConflict?: "ignore" | "update";
228
+ conflictTarget?: string[];
229
+ }
230
+ /** One per-op result returned by a committed transaction. */
231
+ interface TransactionOpResult {
232
+ index: number;
233
+ op: "insert" | "update" | "delete";
234
+ collection: string;
235
+ id: string | null;
236
+ created?: boolean;
237
+ deleted?: boolean;
238
+ row?: Record<string, unknown>;
239
+ }
240
+ interface MediaAsset {
241
+ id: string;
242
+ bucket: string;
243
+ key: string;
244
+ url: string;
245
+ mime_type: string;
246
+ size_bytes: number;
247
+ width?: number | null;
248
+ height?: number | null;
249
+ retention_days?: number | null;
250
+ created_at: string;
251
+ }
252
+ interface PresignData {
253
+ upload_id: string;
254
+ upload_url: string;
255
+ upload_headers: Array<[string, string]>;
256
+ asset_id: string;
257
+ }
258
+ interface PushToken {
259
+ id: string;
260
+ token: string;
261
+ platform: PushPlatform;
262
+ bundle_id?: string | null;
263
+ created_at: string;
264
+ }
265
+ /**
266
+ * Store an entitlement's purchase resolved from. `"web"` is the neutral bucket
267
+ * for any non-native checkout (it replaced the former `"stripe"` value).
268
+ */
269
+ type EntitlementStore = "app_store" | "play_store" | "web";
270
+ /** Lifecycle phase of the subscription period backing the entitlement. */
271
+ type EntitlementPeriodType = "trial" | "intro" | "normal";
272
+ /**
273
+ * A server-evaluated entitlement grant, mirroring the
274
+ * `GET /v1/client/entitlements` response field-for-field. `store` and
275
+ * `period_type` are nullable, CHECK-constrained text columns, typed
276
+ * loosely (`… | (string & {}) | null`) so an unexpected value from a newer
277
+ * server never breaks decoding while still autocompleting the known set.
278
+ */
279
+ interface UserEntitlement {
280
+ /** Project-defined entitlement key (e.g. `"pro"`) — what `has()` checks. */
281
+ entitlement_id: string;
282
+ is_active: boolean;
283
+ product_id?: string | null;
284
+ store?: EntitlementStore | (string & {}) | null;
285
+ purchase_date?: string | null;
286
+ expiration_date?: string | null;
287
+ period_type?: EntitlementPeriodType | (string & {}) | null;
288
+ }
289
+ /** Result of `entitlements.restore()` — the active set after a server re-sync. */
290
+ interface RestoreResult {
291
+ restored_count: number;
292
+ entitlements: UserEntitlement[];
293
+ }
294
+ /** A product behind an offering package (provider-neutral). */
295
+ interface OfferingProduct {
296
+ product_id: string;
297
+ display_name?: string | null;
298
+ description?: string | null;
299
+ product_type?: string | null;
300
+ /** Map of neutral store id → that store's product identifier. */
301
+ store_product_refs: Record<string, string>;
302
+ grants_entitlement_id?: string | null;
303
+ trial_period_days?: number | null;
304
+ /** ISO-8601 duration display hint (e.g. "P1M", "P1Y"). */
305
+ duration?: string | null;
306
+ }
307
+ /** A package — one product at a position within an offering. */
308
+ interface OfferingPackage {
309
+ package_id: string;
310
+ position: number;
311
+ metadata: Record<string, unknown>;
312
+ product: OfferingProduct | null;
313
+ }
314
+ interface Offering {
315
+ offering_id: string;
316
+ display_name?: string | null;
317
+ description?: string | null;
318
+ /** Whether this is the default offering to show. */
319
+ current: boolean;
320
+ metadata: Record<string, unknown>;
321
+ packages: OfferingPackage[];
322
+ }
323
+ /** The shape `offerings()` returns. */
324
+ interface OfferingsResult {
325
+ offerings: Offering[];
326
+ current_offering_id: string | null;
327
+ }
328
+ interface AiMessage {
329
+ role: string;
330
+ content: unknown;
331
+ }
332
+ /**
333
+ * A single vendor-neutral image for vision (multimodal) prompts. Provide a
334
+ * remote `url` OR base64 `data` (+ optional `mime`). Amba maps it to the
335
+ * model's native image format — you never touch provider-specific shapes.
336
+ */
337
+ interface AiImageInput {
338
+ /** Remote image URL. Use this OR `data`, not both. */
339
+ url?: string;
340
+ /** Base64-encoded image bytes (no `data:` prefix). */
341
+ data?: string;
342
+ /** MIME type for a base64 image (e.g. `"image/png"`). Defaults to image/png. */
343
+ mime?: string;
344
+ }
345
+ /** A vendor-neutral content block inside a message's `content` array. */
346
+ type AiContentBlock = {
347
+ type: "text";
348
+ text: string;
349
+ } | {
350
+ type: "image";
351
+ url?: string;
352
+ data?: string;
353
+ mime?: string;
354
+ };
355
+ interface AiMessageRequest {
356
+ prompt_slug: string;
357
+ variables?: Record<string, unknown>;
358
+ messages?: AiMessage[];
359
+ /**
360
+ * Image(s) to send to a vision-capable prompt. They attach to the last user
361
+ * message as vendor-neutral content blocks; the backend maps them to the
362
+ * model's native image format. The prompt's model must be vision-capable, or
363
+ * the call returns `ai_model_not_multimodal`. Omit for text-only prompts.
364
+ */
365
+ images?: AiImageInput[];
366
+ max_tokens?: number;
367
+ temperature?: number;
368
+ enable_prompt_cache?: boolean;
369
+ /**
370
+ * Opt into a streamed (SSE) response from the backend. NOTE: the buffered
371
+ * `ai.anthropic.messages.create()` decodes a single JSON envelope, so it
372
+ * ignores this flag — to consume streamed tokens use the dedicated
373
+ * `ai.anthropic.messages.stream()` / `ai.openai.chat.completions.stream()`
374
+ * methods, which set `stream: true` for you and yield text deltas as they
375
+ * arrive plus a final usage event. The field exists so the request type
376
+ * matches the backend contract.
377
+ */
378
+ stream?: boolean;
379
+ }
380
+ interface AiUsage {
381
+ input_tokens: number;
382
+ output_tokens: number;
383
+ cache_creation_input_tokens?: number;
384
+ cache_read_input_tokens?: number;
385
+ }
386
+ interface AiMessageResponse {
387
+ content: unknown[];
388
+ usage: AiUsage;
389
+ stop_reason?: string | null;
390
+ model: string;
391
+ /**
392
+ * Computed USD cost of this call (input + output + cache token rates,
393
+ * summed server-side). `null` when the model isn't in the cost table — the
394
+ * call still succeeds; the cost is just unattributed. Read it to surface
395
+ * per-call spend without a separate analytics round-trip.
396
+ */
397
+ cost_usd?: number | null;
398
+ }
399
+ /**
400
+ * Resolved remote config returned by `config.fetch()`.
401
+ *
402
+ * Server: `GET /v1/client/config` returns body `{ "data": { key → value, ... } }`
403
+ * plus an `ETag` response header carrying the version stamp. The SDK
404
+ * lifts the `data` map into `values` and parses the ETag prefix into
405
+ * `version`.
406
+ *
407
+ * `version` is `null` when the server didn't send an ETag (e.g. local
408
+ * dev with an empty config-versions row).
409
+ */
410
+ interface ConfigBundle {
411
+ version: string | null;
412
+ values: Record<string, unknown>;
413
+ }
414
+ interface FlagAssignment {
415
+ name: string;
416
+ enabled: boolean;
417
+ variant?: string | null;
418
+ payload?: unknown;
419
+ }
420
+ /** Customer-facing environment label, derived server-side. */
421
+ type DiagnosticsEnvironment = "production" | "staging" | "sandbox";
422
+ interface PingResult {
423
+ /**
424
+ * `true` on a successful round-trip. `false` only when the server
425
+ * reached the route handler but couldn't resolve the lookup; in
426
+ * that case `error` carries a stable code string. Transport
427
+ * failures throw `AmbaApiError` and never produce a `PingResult`.
428
+ */
429
+ ok: boolean;
430
+ /**
431
+ * Project id the server resolved from the API key. Compare to the
432
+ * value you passed to `Amba.configure({ projectId })` — they should
433
+ * match.
434
+ */
435
+ server_project_id: string | null;
436
+ /** `production` / `staging` / `sandbox`, server-derived. */
437
+ environment: DiagnosticsEnvironment | null;
438
+ /**
439
+ * Last 4 hex chars of the sha256 of the API key the server saw.
440
+ * Pastes alongside the same suffix in the developer console UI.
441
+ */
442
+ key_fingerprint: string;
443
+ /**
444
+ * Server-measured handler latency, in milliseconds. Excludes
445
+ * client-side network time.
446
+ */
447
+ latency_ms: number;
448
+ /**
449
+ * `null` on success. On server-side failure, a stable code (e.g.
450
+ * `"DIAGNOSTICS_INTERNAL_ERROR"`).
451
+ */
452
+ error: string | null;
453
+ }
454
+ interface Achievement {
455
+ id: string;
456
+ key: string;
457
+ name: string;
458
+ description?: string | null;
459
+ icon_url?: string | null;
460
+ /** XP granted to the user on unlock. Mirrors the wire field `xp_reward`. */
461
+ xp_reward: number;
462
+ criteria: unknown;
463
+ }
464
+ interface AchievementProgress {
465
+ achievement_id: string;
466
+ key: string;
467
+ progress: number;
468
+ unlocked: boolean;
469
+ unlocked_at?: string | null;
470
+ }
471
+ interface Challenge {
472
+ id: string;
473
+ key: string;
474
+ name: string;
475
+ description?: string | null;
476
+ starts_at: string;
477
+ ends_at: string;
478
+ criteria: unknown;
479
+ reward: unknown;
480
+ }
481
+ interface ChallengeProgress {
482
+ challenge_id: string;
483
+ progress: number;
484
+ completed: boolean;
485
+ claimed: boolean;
486
+ completed_at?: string | null;
487
+ claimed_at?: string | null;
488
+ }
489
+ interface CurrencyBalance {
490
+ currency_code: string;
491
+ name: string;
492
+ is_premium: boolean;
493
+ balance: number;
494
+ lifetime_earned: number;
495
+ lifetime_spent: number;
496
+ }
497
+ interface CurrencyTransaction {
498
+ id: string;
499
+ app_user_id?: string;
500
+ currency_code: string;
501
+ amount: number;
502
+ balance_after: number;
503
+ transaction_type?: string;
504
+ source?: string;
505
+ reference_id?: string | null;
506
+ idempotency_key?: string | null;
507
+ created_at: string;
508
+ }
509
+ interface InventoryItem {
510
+ id: string;
511
+ catalog_item_id: string;
512
+ sku: string;
513
+ quantity: number;
514
+ acquired_at: string;
515
+ metadata: unknown;
516
+ }
517
+ interface PurchaseRequest {
518
+ sku: string;
519
+ quantity?: number;
520
+ currency_key?: string;
521
+ }
522
+ interface ConsumeRequest {
523
+ item_id: string;
524
+ quantity: number;
525
+ }
526
+ interface Leaderboard {
527
+ id: string;
528
+ key: string;
529
+ name: string;
530
+ period: string;
531
+ direction: string;
532
+ starts_at?: string | null;
533
+ ends_at?: string | null;
534
+ }
535
+ interface LeaderboardEntry {
536
+ rank: number;
537
+ user_id: string;
538
+ display_name?: string | null;
539
+ avatar_url?: string | null;
540
+ score: number;
541
+ }
542
+ interface Store {
543
+ id: string;
544
+ key: string;
545
+ name: string;
546
+ }
547
+ interface PurchaseOption {
548
+ id: string;
549
+ store_id: string;
550
+ sku: string;
551
+ name: string;
552
+ price_cents: number;
553
+ currency: string;
554
+ period?: string | null;
555
+ metadata: unknown;
556
+ }
557
+ interface PurchaseResult {
558
+ id: string;
559
+ user_id: string;
560
+ purchase_option_id: string;
561
+ status: string;
562
+ purchased_at: string;
563
+ receipt: unknown;
564
+ }
565
+ interface XpBalance {
566
+ user_id: string;
567
+ total_xp: number;
568
+ current_level: number;
569
+ xp_into_level: number;
570
+ xp_to_next_level: number;
571
+ updated_at: string;
572
+ /**
573
+ * XP earned in the current rolling window. The window is server-defined
574
+ * via the project's XP rules. Older servers that don't populate this
575
+ * field decode as `0`.
576
+ */
577
+ xp_this_period: number;
578
+ }
579
+ interface XpTransaction {
580
+ id: string;
581
+ delta: number;
582
+ reason?: string | null;
583
+ source?: string | null;
584
+ created_at: string;
585
+ }
586
+ interface Streak {
587
+ id: string;
588
+ /**
589
+ * Stable human-readable key, e.g. `"daily_play"`. Use this to qualify:
590
+ * `Amba.streaks.qualify('daily_play')`.
591
+ */
592
+ key: string;
593
+ /** Display name of the streak the user is on. */
594
+ name: string;
595
+ /** Current consecutive qualifying-event count. Resets to 0 on break. */
596
+ current_count: number;
597
+ /** All-time best `current_count` for this user + streak. */
598
+ longest_count: number;
599
+ /** ISO timestamp of the last successful qualify. */
600
+ last_qualified_at?: string | null;
601
+ updated_at: string;
602
+ /**
603
+ * Lifecycle state.
604
+ * - `"active"` — streak is alive
605
+ * - `"broken"` — user missed a period; `current_count` reset to 0
606
+ * - `"frozen"` — a shield protected the streak from a miss
607
+ */
608
+ status: 'active' | 'broken' | 'frozen';
609
+ /**
610
+ * Unused streak-shield count. Auto-granted per the streak's configured
611
+ * cadence, capped at the streak's `max_freezes`.
612
+ */
613
+ freezes_remaining: number;
614
+ }
615
+ interface FeedItem {
616
+ id: string;
617
+ actor_id: string;
618
+ verb: string;
619
+ object_type: string;
620
+ object_id: string;
621
+ target_type?: string | null;
622
+ target_id?: string | null;
623
+ data: unknown;
624
+ created_at: string;
625
+ }
626
+ interface FeedResponse {
627
+ data: FeedItem[];
628
+ next_cursor?: string | null;
629
+ }
630
+ type FriendshipState = "pending" | "accepted" | "blocked" | "declined";
631
+ interface Friendship {
632
+ id: string;
633
+ user_id: string;
634
+ friend_id: string;
635
+ state: FriendshipState;
636
+ created_at: string;
637
+ }
638
+ interface Group {
639
+ id: string;
640
+ name: string;
641
+ description?: string | null;
642
+ avatar_url?: string | null;
643
+ created_by: string;
644
+ member_count: number;
645
+ created_at: string;
646
+ updated_at: string;
647
+ metadata: unknown;
648
+ }
649
+ interface GroupMember {
650
+ group_id: string;
651
+ user_id: string;
652
+ role: string;
653
+ joined_at: string;
654
+ }
655
+ interface GroupCreate {
656
+ name: string;
657
+ description?: string;
658
+ avatar_url?: string;
659
+ metadata?: unknown;
660
+ }
661
+ interface GroupUpdate {
662
+ name?: string;
663
+ description?: string;
664
+ avatar_url?: string;
665
+ metadata?: unknown;
666
+ }
667
+ type ConversationType = "direct" | "group";
668
+ type MessageType = "text" | "image" | "system";
669
+ interface Message {
670
+ id: string;
671
+ conversation_id: string;
672
+ sender_id: string;
673
+ body: string;
674
+ /** Server-tagged message kind. Defaults to `"text"` when omitted. */
675
+ message_type?: MessageType | null;
676
+ metadata: unknown;
677
+ created_at: string;
678
+ }
679
+ interface Conversation {
680
+ id: string;
681
+ /** `"direct"` or `"group"` — present on rows returned by the API. */
682
+ type?: ConversationType | null;
683
+ /** Group conversation name. `null` for direct conversations. */
684
+ name?: string | null;
685
+ /**
686
+ * Participant ids on the row. Optional because the API's POST
687
+ * `/conversations` response doesn't include them — only the
688
+ * list-endpoint does. SDK consumers should fall back to the
689
+ * server's richer `conversation_participants` shape if they need
690
+ * display names.
691
+ */
692
+ participants?: string[];
693
+ last_message?: Message | null;
694
+ metadata?: unknown;
695
+ created_at: string;
696
+ updated_at: string;
697
+ }
698
+ /**
699
+ * Request body for `Amba.messaging.createConversation`. Mirrors the
700
+ * server's `CreateConversationInput`. The caller is implicitly added
701
+ * to `participant_ids` — clients don't have to include their own
702
+ * `user_id`.
703
+ */
704
+ interface CreateConversationRequest {
705
+ participant_ids: string[];
706
+ type?: ConversationType;
707
+ /** Optional name — only meaningful for `type === "group"`. */
708
+ name?: string;
709
+ }
710
+ /**
711
+ * Request body for `Amba.messaging.sendMessage`. The conversation id
712
+ * is a path parameter and is passed separately to the SDK method;
713
+ * this object is the JSON body the server reads.
714
+ */
715
+ interface SendMessageRequest {
716
+ body: string;
717
+ message_type?: MessageType;
718
+ metadata?: Record<string, unknown>;
719
+ }
720
+ /** Pagination options for `Amba.messaging.listMessages`. */
721
+ interface ListMessagesOptions {
722
+ limit?: number;
723
+ offset?: number;
724
+ }
725
+ /**
726
+ * Response shape from `Amba.messaging.markRead`. Mirrors the updated
727
+ * `conversation_participants` row the server returns.
728
+ */
729
+ interface MarkReadResult {
730
+ conversation_id?: string | null;
731
+ app_user_id?: string | null;
732
+ last_read_at?: string | null;
733
+ }
734
+ /**
735
+ * A `conversation_participants` row — returned by
736
+ * `Amba.messaging.addParticipant`. All fields are optional so older
737
+ * servers that trim columns still decode.
738
+ */
739
+ interface ConversationParticipant {
740
+ id?: string | null;
741
+ conversation_id?: string | null;
742
+ app_user_id?: string | null;
743
+ joined_at?: string | null;
744
+ last_read_at?: string | null;
745
+ }
746
+ interface Report {
747
+ id: string;
748
+ reporter_id: string;
749
+ target_type: string;
750
+ target_id: string;
751
+ reason: string;
752
+ status: string;
753
+ notes?: string | null;
754
+ created_at: string;
755
+ resolved_at?: string | null;
756
+ }
757
+ interface ReportRequest {
758
+ target_id: string;
759
+ reason: string;
760
+ notes?: string;
761
+ }
762
+ interface ReferralCode {
763
+ id: string;
764
+ code: string;
765
+ owner_id: string;
766
+ uses_count: number;
767
+ max_uses?: number | null;
768
+ expires_at?: string | null;
769
+ created_at: string;
770
+ }
771
+ interface ReferralClaim {
772
+ id: string;
773
+ code_id: string;
774
+ referrer_id: string;
775
+ referee_id: string;
776
+ reward: unknown;
777
+ claimed_at: string;
778
+ }
779
+ interface Review {
780
+ id: string;
781
+ author_id: string;
782
+ target_type: string;
783
+ target_id: string;
784
+ rating: number;
785
+ title?: string | null;
786
+ body?: string | null;
787
+ created_at: string;
788
+ updated_at: string;
789
+ }
790
+ interface ReviewCreate {
791
+ target_type: string;
792
+ target_id: string;
793
+ rating: number;
794
+ title?: string;
795
+ body?: string;
796
+ }
797
+ interface ReviewUpdate {
798
+ rating?: number;
799
+ title?: string;
800
+ body?: string;
801
+ }
802
+ interface Role {
803
+ id: string;
804
+ key: string;
805
+ name: string;
806
+ permissions: string[];
807
+ }
808
+ interface CatalogItem {
809
+ id: string;
810
+ sku: string;
811
+ name: string;
812
+ description?: string | null;
813
+ price_cents?: number | null;
814
+ currency?: string | null;
815
+ metadata: unknown;
816
+ }
817
+ interface ContentItem {
818
+ id: string;
819
+ channel: string;
820
+ title?: string | null;
821
+ body?: string | null;
822
+ data: unknown;
823
+ published_at: string;
824
+ user_state: unknown;
825
+ }
826
+ interface DeepLink {
827
+ id: string;
828
+ url: string;
829
+ short_code: string;
830
+ target_path?: string | null;
831
+ metadata: unknown;
832
+ created_at: string;
833
+ }
834
+ interface DeepLinkCreate {
835
+ target_path: string;
836
+ metadata?: unknown;
837
+ short_code?: string;
838
+ }
839
+ interface OnboardingStatus {
840
+ current_step: string | null;
841
+ completed_steps: string[];
842
+ remaining_steps: string[];
843
+ completed: boolean;
844
+ completed_at?: string | null;
845
+ }
846
+ /**
847
+ * Active customer-defined app session row. Returned by
848
+ * `Amba.sessions.list()`. Distinct from the auth-session token held by
849
+ * the SDK itself — these track app-foreground sessions the customer
850
+ * opted to record via `sessions.start` / `sessions.end` on the server.
851
+ */
852
+ interface AppSession {
853
+ id: string;
854
+ user_id: string;
855
+ started_at: string;
856
+ ended_at?: string | null;
857
+ duration_secs?: number | null;
858
+ metadata?: unknown;
859
+ }
860
+ /**
861
+ * A single buffered mutation queued for replay. The client constructs
862
+ * these while offline and replays the batch via
863
+ * `Amba.sync.pushChanges(changes)` on reconnect.
864
+ */
865
+ interface SyncChange {
866
+ entity_type: string;
867
+ entity_id: string;
868
+ /** `"insert" | "update" | "delete"`. */
869
+ action: string;
870
+ data?: unknown;
871
+ /** Client-side ISO-8601 timestamp the mutation was captured. */
872
+ client_timestamp: string;
873
+ }
874
+ interface SyncConflict {
875
+ entity_type: string;
876
+ entity_id: string;
877
+ /** Always `"server_wins"` in 4.0; reserved for future strategies. */
878
+ resolution: string;
879
+ server_data?: unknown;
880
+ }
881
+ interface PushChangesResult {
882
+ applied: number;
883
+ conflicts: SyncConflict[];
884
+ checkpoint_token: string;
885
+ }
886
+ interface PullChangesResult {
887
+ changes: SyncChange[];
888
+ checkpoint_token: string;
889
+ has_more?: boolean;
890
+ }
891
+ /**
892
+ * Cursor input to `Amba.sync.pullChanges`. `entity_type` scopes the
893
+ * pull to a single collection; `checkpoint_token` is the opaque cursor
894
+ * returned by the previous `pushChanges` / `pullChanges` reply.
895
+ */
896
+ interface PullChangesCursor {
897
+ entity_type: string;
898
+ checkpoint_token?: string;
899
+ }
900
+ interface LeagueCohort {
901
+ id: string;
902
+ league_id: string;
903
+ /** ISO-8601 date (YYYY-MM-DD) that opens the cohort's 7-day window. */
904
+ week_start: string;
905
+ /** `"active" | "settling" | "closed"`. */
906
+ status: string;
907
+ }
908
+ interface LeagueTier {
909
+ id: string;
910
+ name: string;
911
+ tier_order: number;
912
+ }
913
+ interface LeagueCohortMember {
914
+ display_name?: string | null;
915
+ score: number;
916
+ rank: number;
917
+ /** `true` for the requesting user's own row (server-derived; no id leak). */
918
+ is_me?: boolean;
919
+ }
920
+ interface LeagueMembership {
921
+ cohort: LeagueCohort | null;
922
+ league: LeagueTier | null;
923
+ rank: number | null;
924
+ score: number;
925
+ member_count: number;
926
+ }
927
+ interface LeagueCohortView {
928
+ cohort: LeagueCohort | null;
929
+ league: LeagueTier | null;
930
+ members: LeagueCohortMember[];
931
+ }
932
+ interface MediaListResponse {
933
+ data: MediaAsset[];
934
+ next_cursor?: string | null;
935
+ }
936
+ /** A single asset within a media catalog, resolved to a public URL. */
937
+ interface MediaCatalogItem {
938
+ media_id: string;
939
+ position: number;
940
+ caption?: string | null;
941
+ metadata: Record<string, unknown>;
942
+ filename: string;
943
+ mime_type: string;
944
+ /** Stable, long-cacheable public URL for the asset. */
945
+ url: string;
946
+ thumbnail_url?: string | null;
947
+ alt_text?: string | null;
948
+ blurhash?: string | null;
949
+ }
950
+ /**
951
+ * A curated, slug-addressable bundle of media assets fetched in one call.
952
+ * Items are returned in their configured order.
953
+ */
954
+ interface MediaCatalog {
955
+ slug: string;
956
+ name: string;
957
+ description?: string | null;
958
+ metadata: Record<string, unknown>;
959
+ updated_at: string;
960
+ items: MediaCatalogItem[];
961
+ }
962
+ /**
963
+ * One rule a candidate event would trigger, as returned by
964
+ * `Amba.events.explain`. `would` describes the effect that WOULD be produced
965
+ * if the event were tracked for real; `note` (when present) flags a per-user
966
+ * gate (daily limit, cooldown, balance cap) that would suppress or reduce it.
967
+ */
968
+ interface ExplainMatchedRule {
969
+ rule_type: "xp" | "currency_grant" | "feed" | "streak" | "webhook";
970
+ rule_id: string;
971
+ name: string;
972
+ would: Record<string, unknown>;
973
+ note?: string;
974
+ }
975
+ /**
976
+ * Result of a read-only event rule dry-run (`Amba.events.explain`). Producing
977
+ * this commits no side effect — it is a preview of what tracking the event
978
+ * would do across XP, currency grants, feed items, streaks, and webhooks.
979
+ */
980
+ interface EventExplainResult {
981
+ event: {
982
+ name: string;
983
+ properties: Record<string, unknown>;
984
+ user_id: string | null;
985
+ };
986
+ matched: ExplainMatchedRule[];
987
+ evaluated_surfaces: string[];
988
+ unmatched_reason_samples?: string[];
989
+ }
990
+ /**
991
+ * Options for `Amba.collections.findNearest`.
992
+ *
993
+ * Spec note: the canonical 4.0 signature is `findNearest(name, vector, k, filter?)`,
994
+ * but the underlying server route requires a `vector_field` column name
995
+ * to scan against — the SDK widens the contract to a single options
996
+ * object so `vector_field` is unambiguous in TS. (Documented in the
997
+ * 4.0 migration guide.)
998
+ */
999
+ interface NearestOptions {
1000
+ /** Name of the column carrying the vector to compare against. */
1001
+ vector_field: string;
1002
+ /** Query vector. Length must match the column's configured dimensions. */
1003
+ query_vector: number[];
1004
+ /** Number of nearest neighbors to return. Must be > 0. */
1005
+ k: number;
1006
+ /** Optional WHERE filter applied before nearest-neighbor ranking. */
1007
+ filter?: Filter;
1008
+ }
1009
+ interface NearestResponse<T> {
1010
+ data: T[];
1011
+ }
1012
+ /**
1013
+ * When a local notification should fire. Exactly one shape:
1014
+ *
1015
+ * - `{ at: Date }` — fire once at an absolute time.
1016
+ * - `{ inSeconds, repeats? }` — fire after a relative delay.
1017
+ *
1018
+ * Local notifications are scheduled and fired by the device itself, with
1019
+ * no server involvement — distinct from remote push (`Amba.push.*`).
1020
+ * Browser scheduling is partial; see `notifications.scheduleLocal`.
1021
+ */
1022
+ type LocalNotificationTrigger = {
1023
+ at: Date | string;
1024
+ } | {
1025
+ inSeconds: number;
1026
+ repeats?: boolean;
1027
+ };
1028
+ /** Input to `Amba.notifications.scheduleLocal`. */
1029
+ interface ScheduleLocalNotificationInput {
1030
+ /** Optional stable id; omit to let the platform mint one. */
1031
+ id?: string;
1032
+ /** Notification title (required, non-empty). */
1033
+ title: string;
1034
+ /** Notification body text. */
1035
+ body: string;
1036
+ /** Arbitrary payload delivered to the app when the notification is tapped. */
1037
+ data?: Record<string, unknown>;
1038
+ /** When to fire it. */
1039
+ trigger: LocalNotificationTrigger;
1040
+ }
1041
+ /** Handle returned by `scheduleLocal`; `id` is accepted by `cancelLocal`. */
1042
+ interface LocalNotificationHandle {
1043
+ id: string;
1044
+ }
1045
+ /** A pending local notification, as reported by `listScheduledLocal`. */
1046
+ interface ScheduledLocalNotification {
1047
+ id: string;
1048
+ title: string;
1049
+ body: string;
1050
+ data?: Record<string, unknown>;
1051
+ }
1052
+ //#endregion
1053
+ //#region src/realtime.d.ts
1054
+ /**
1055
+ * Live collection subscriptions for `@layers/amba-web` (PROTOTYPE — T13).
1056
+ *
1057
+ * Public surface is intentionally tiny and provider-agnostic:
1058
+ *
1059
+ * const unsub = Amba.collection("messages")
1060
+ * .where("circle_id", circleId)
1061
+ * .subscribe((change) => {
1062
+ * // change.op is "INSERT" | "UPDATE" | "DELETE"
1063
+ * // change.row is the affected row
1064
+ * });
1065
+ * unsub(); // stop listening
1066
+ *
1067
+ * The transport (a streamed HTTP response) and the server-side fan-out
1068
+ * mechanism are deliberately NOT named here — customers only ever see
1069
+ * `.subscribe()`. Internally this opens a long-lived streamed GET to the
1070
+ * realtime endpoint and reconnects with backoff if the connection drops.
1071
+ *
1072
+ * Delivery mirrors `Amba.collection.find()` exactly: you receive only rows
1073
+ * your session owns, optionally narrowed by `.where(...)` equality filters.
1074
+ */
1075
+ type RowChangeOp = "INSERT" | "UPDATE" | "DELETE";
1076
+ interface RealtimeChange<T = unknown> {
1077
+ /** Which kind of write happened. */
1078
+ op: RowChangeOp;
1079
+ /**
1080
+ * The affected row. For deletes this is a partial row (at minimum `id`).
1081
+ * If the row was too large to deliver inline, `truncated` is `true` and
1082
+ * `row` carries just `id` — re-fetch with `Amba.collection.find()` /
1083
+ * `findOne()` when you need the full body.
1084
+ */
1085
+ row: T;
1086
+ truncated?: boolean;
1087
+ }
1088
+ interface SubscribeOptions {
1089
+ /** Called once when the live connection is (re)established. */
1090
+ onOpen?: () => void;
1091
+ /** Called on a connection error before the SDK retries. */
1092
+ onError?: (err: unknown) => void;
1093
+ }
1094
+ /** Unsubscribe handle returned by `.subscribe(...)`. Call it to stop. */
1095
+ type Unsubscribe = () => void;
1096
+ /**
1097
+ * Resolved at call time so the latest session token is used. Returns the
1098
+ * auth headers, or `null` when there's no live session (subscribe throws).
1099
+ */
1100
+ interface RealtimeContext {
1101
+ baseUrl: string;
1102
+ authHeaders: () => Record<string, string> | null;
1103
+ }
1104
+ /**
1105
+ * Builder returned by `Amba.collection(name)`. Chain `.where(...)` to
1106
+ * narrow, then `.subscribe(cb)` to start streaming.
1107
+ */
1108
+ declare class CollectionSubscription<T = unknown> {
1109
+ private readonly ctx;
1110
+ private readonly name;
1111
+ private readonly filters;
1112
+ constructor(ctx: RealtimeContext, name: string);
1113
+ /** Add an equality filter. Chainable. All filters must match (AND). */
1114
+ where(column: string, value: unknown): this;
1115
+ /**
1116
+ * Start listening. `cb` fires once per matching row change. Returns an
1117
+ * unsubscribe handle — call it to close the connection.
1118
+ */
1119
+ subscribe(cb: (change: RealtimeChange<T>) => void, options?: SubscribeOptions): Unsubscribe;
1120
+ }
1121
+ /**
1122
+ * A live message change in a conversation. `op` is "INSERT" for a new
1123
+ * message; moderation removals arrive as "DELETE". For a delete (or when a
1124
+ * message was too large to deliver inline) `row` may be partial — re-fetch
1125
+ * with `Amba.messaging.listMessages(conversationId)` when you need the full
1126
+ * body.
1127
+ */
1128
+ type MessageChange<T = unknown> = RealtimeChange<T>;
1129
+ /**
1130
+ * Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`
1131
+ * to receive new messages live instead of polling.
1132
+ *
1133
+ * const unsub = Amba.messaging
1134
+ * .conversation(conversationId)
1135
+ * .subscribe((change) => {
1136
+ * // change.op === "INSERT"; change.row is the new message
1137
+ * });
1138
+ * unsub(); // stop listening
1139
+ *
1140
+ * Delivery is scoped to conversation participants — you receive messages for
1141
+ * a conversation only if your session is a member of it. Reconnects with the
1142
+ * same exponential backoff as collection subscriptions.
1143
+ */
1144
+ declare class ConversationSubscription<T = unknown> {
1145
+ private readonly ctx;
1146
+ private readonly conversationId;
1147
+ constructor(ctx: RealtimeContext, conversationId: string);
1148
+ subscribe(cb: (change: MessageChange<T>) => void, options?: SubscribeOptions): Unsubscribe;
1149
+ }
1150
+ /** Which slice of the signed-in user's gamification state changed. */
1151
+ type GamificationKind = "xp" | "level" | "achievement" | "streak";
1152
+ /**
1153
+ * A live change to the signed-in user's gamification state. `kind` says which
1154
+ * slice moved; `data` carries the new state inline so you can update without a
1155
+ * re-fetch:
1156
+ * - `xp` — `{ total_xp, level, delta }`
1157
+ * - `level` — `{ level, total_xp }`
1158
+ * - `achievement` — `{ achievement_id, unlocked }`
1159
+ * - `streak` — `{ streak_id, count, status, ... }`
1160
+ *
1161
+ * If a payload was too large to deliver inline, `truncated` is `true` and `data`
1162
+ * is empty — re-fetch with `Amba.xp.getBalance()` / `Amba.streaks.getAll()` etc.
1163
+ */
1164
+ interface GamificationChange<T = Record<string, unknown>> {
1165
+ kind: GamificationKind;
1166
+ data: T;
1167
+ truncated?: boolean;
1168
+ }
1169
+ /**
1170
+ * Builder returned by `Amba.gamification`. Call `.subscribe(cb)` to receive the
1171
+ * signed-in user's xp / level / achievement / streak changes live instead of
1172
+ * polling.
1173
+ *
1174
+ * const unsub = Amba.gamification.subscribe((change) => {
1175
+ * // change.kind: "xp" | "level" | "achievement" | "streak"
1176
+ * // change.data: the new state
1177
+ * });
1178
+ * unsub(); // stop listening
1179
+ *
1180
+ * Delivery is scoped to the signed-in user — you only ever receive your own
1181
+ * gamification changes, never another user's. Reconnects with the same
1182
+ * exponential backoff as collection subscriptions.
1183
+ */
1184
+ declare class GamificationSubscription {
1185
+ private readonly ctx;
1186
+ constructor(ctx: RealtimeContext);
1187
+ subscribe(cb: (change: GamificationChange) => void, options?: SubscribeOptions): Unsubscribe;
1188
+ }
1189
+ //#endregion
1190
+ //#region src/ai-stream.d.ts
1191
+ /** A streamed token delta — append `text` to your running output buffer. */
1192
+ interface AiStreamDelta {
1193
+ type: "delta";
1194
+ /** The incremental text the model just produced. */
1195
+ text: string;
1196
+ }
1197
+ /**
1198
+ * Terminal event. Emitted once when the stream completes cleanly. `usage`
1199
+ * carries the token accounting accumulated across the stream (zeroed fields
1200
+ * when the vendor didn't report a given counter); `model` / `stopReason` are
1201
+ * populated when the vendor surfaced them.
1202
+ */
1203
+ interface AiStreamDone {
1204
+ type: "done";
1205
+ usage: AiUsage;
1206
+ model?: string;
1207
+ stopReason?: string | null;
1208
+ }
1209
+ /** Vendor-neutral event yielded by the streaming AI methods. */
1210
+ type AiStreamEvent = AiStreamDelta | AiStreamDone;
1211
+ /** Options accepted by the streaming AI methods. */
1212
+ interface AiStreamOptions {
1213
+ /**
1214
+ * Optional callback invoked for every text delta as it arrives — a
1215
+ * convenience over iterating, useful when you only care about appending
1216
+ * tokens to the UI. Fires in addition to the yielded `delta` events.
1217
+ */
1218
+ onChunk?: (text: string) => void;
1219
+ }
1220
+ //#endregion
1221
+ //#region src/resilience.d.ts
1222
+ /**
1223
+ * Offline mutation queue + optimistic-update helpers — framework-agnostic
1224
+ * orchestration that sits ABOVE the collection / event write methods.
1225
+ *
1226
+ * Nothing here touches the SDK runtime directly: the queue is handed a
1227
+ * small `KvStore` (durable key/value persistence) and a `WriteExecutor`
1228
+ * (the function that actually performs a queued mutation against the API).
1229
+ * That keeps the same code identical across every JS SDK target — only the
1230
+ * adapters that supply storage + the executor differ per platform.
1231
+ *
1232
+ * Design:
1233
+ * - A write that fails because the device is offline (a transport-level
1234
+ * network error, or `navigator.onLine === false`) is appended to a
1235
+ * durable outbox instead of throwing all the way up.
1236
+ * - Each queued op carries a stable idempotency key. On replay the key is
1237
+ * forwarded to the API's collection-write idempotency support so a
1238
+ * mutation that actually reached the server before the connection
1239
+ * dropped is never applied twice.
1240
+ * - On reconnect (`online` event) the outbox is flushed in FIFO order;
1241
+ * each op is removed only after it succeeds, so a mid-flush failure
1242
+ * leaves the remainder intact for the next attempt.
1243
+ */
1244
+ /**
1245
+ * Minimal durable key/value contract the outbox persists through. Both the
1246
+ * browser `localStorage` adapter and the React Native AsyncStorage adapter
1247
+ * satisfy this — the queue never assumes anything else.
1248
+ */
1249
+ interface KvStore {
1250
+ get(key: string): Promise<string | null> | string | null;
1251
+ set(key: string, value: string): Promise<void> | void;
1252
+ delete(key: string): Promise<void> | void;
1253
+ }
1254
+ /** The kinds of mutation the outbox can durably replay. */
1255
+ type OutboxOpKind = "collection.insert" | "collection.update" | "collection.delete" | "track";
1256
+ /**
1257
+ * A single durably-queued mutation. `idempotencyKey` is reused verbatim on
1258
+ * every replay attempt so the server dedupes a COLLECTION write that landed
1259
+ * before the connection dropped (the events `track` API has no idempotency
1260
+ * channel, so `track` replays are at-least-once — a rare reconnect duplicate
1261
+ * is acceptable for fire-and-forget analytics). `enqueuedAt` is an ISO
1262
+ * timestamp for inspection.
1263
+ */
1264
+ interface OutboxOp {
1265
+ /** Stable per-op id (the idempotency key for collection writes). */
1266
+ idempotencyKey: string;
1267
+ kind: OutboxOpKind;
1268
+ /** Target collection name (absent for `track`). */
1269
+ collection?: string;
1270
+ /** Row id for update / delete. */
1271
+ id?: string;
1272
+ /** Insert row / update set payload. */
1273
+ payload?: Record<string, unknown>;
1274
+ /** Event name for `track`. */
1275
+ event?: string;
1276
+ /** Event properties for `track`. */
1277
+ properties?: Record<string, unknown>;
1278
+ /** Whether the tracked event was telemetry-tier. */
1279
+ telemetry?: boolean;
1280
+ /**
1281
+ * ISO timestamp the `track` originally OCCURRED, captured at enqueue. Replayed
1282
+ * as the events body `timestamp` (→ `occurred_at`) so a buffered event keeps
1283
+ * its real time instead of being re-stamped at flush. (Attribution is by the
1284
+ * session user at replay time — the events route has no body-id channel.)
1285
+ */
1286
+ occurredAt?: string;
1287
+ enqueuedAt: string;
1288
+ }
1289
+ /** Result of a {@link OfflineQueue.flush}. */
1290
+ interface FlushResult {
1291
+ /** Ops successfully replayed and removed from the outbox. */
1292
+ flushed: number;
1293
+ /**
1294
+ * Ops dropped because their replay failed deterministically (a coded server
1295
+ * rejection like `VALIDATION_ERROR` / `NOT_FOUND` that would fail identically
1296
+ * forever). Dropping them prevents one poisoned op from wedging the queue;
1297
+ * the drain continues past it.
1298
+ */
1299
+ dropped: number;
1300
+ /** Ops still pending after the flush (a replay failed and stopped it). */
1301
+ remaining: number;
1302
+ }
1303
+ /**
1304
+ * Apply a local change immediately, fire the real mutation, and roll the
1305
+ * local change back if the mutation fails. Framework-agnostic: `apply` and
1306
+ * `rollback` operate on whatever local view you hand them (a React state
1307
+ * setter, a store, an in-memory cache).
1308
+ *
1309
+ * Resolves with the mutation's result on success. On failure it invokes
1310
+ * `rollback` (with the value `apply` returned, so you can restore a captured
1311
+ * snapshot) and rethrows the original error.
1312
+ *
1313
+ * @example
1314
+ * await withOptimistic({
1315
+ * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },
1316
+ * mutate: () => Amba.collections.insert("todos", draft),
1317
+ * rollback: (prev) => setTodos(prev),
1318
+ * });
1319
+ */
1320
+ declare function withOptimistic<TApply, TResult>(spec: {
1321
+ /** Mutate the local view now. Return a snapshot for `rollback` to restore. */
1322
+ apply: () => TApply;
1323
+ /** The real async mutation against the API. */
1324
+ mutate: () => Promise<TResult>;
1325
+ /** Undo the local change. Receives whatever `apply` returned. */
1326
+ rollback: (applied: TApply) => void;
1327
+ }): Promise<TResult>;
1328
+ //#endregion
1329
+ //#region src/error.d.ts
1330
+ /**
1331
+ * Typed error class for the amba SDK + helper that converts arbitrary
1332
+ * thrown values (native Error, plain objects, runtime-emitted errors)
1333
+ * into a stable `AmbaApiError` instance.
1334
+ *
1335
+ * Callers can `instanceof`-narrow safely, branch on `.code`, and pull
1336
+ * extra payload from `.details` (raw HTTP body, validation field paths,
1337
+ * etc.). The wrapper methods on `Amba` wrap every async call in
1338
+ * `toAmbaApiError`, so consumers see one error shape regardless of
1339
+ * which layer (network, runtime, validation) failed.
1340
+ */
1341
+ /**
1342
+ * Stable error codes surfaced by the SDK. Strings rather than an enum
1343
+ * so customers can extend with custom codes by simply throwing
1344
+ * `new AmbaApiError("MY_CODE", "...")` without coordinating with this
1345
+ * package's TypeScript types.
1346
+ */
1347
+ 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 & {});
1348
+ declare class AmbaApiError extends Error {
1349
+ readonly code: AmbaApiErrorCode;
1350
+ readonly details?: unknown;
1351
+ /**
1352
+ * Server-side request / trace id (the `X-Amba-Request-Id` response
1353
+ * header, falling back to the `request_id` field in the standard error
1354
+ * envelope). Present on errors thrown by `Amba.functions.invoke()` so a
1355
+ * failed run can be correlated with server logs
1356
+ * (`functions logs --request-id <traceId>`). `undefined` when the failure
1357
+ * happened before a response was received (e.g. a transport error).
1358
+ */
1359
+ traceId?: string;
1360
+ constructor(code: AmbaApiErrorCode, message: string, details?: unknown);
1361
+ }
1362
+ //#endregion
1363
+ //#region src/local-notifications.d.ts
1364
+ /**
1365
+ * The web `notifications` namespace. Web capability is partial — see the
1366
+ * module-level doc. The returned handle includes `scheduledVia` so apps
1367
+ * can detect whether the notification was handed to the OS
1368
+ * (`"os-trigger"`) or relies on an in-page timer (`"timer"`).
1369
+ */
1370
+ declare const notifications: {
1371
+ scheduleLocal(input: ScheduleLocalNotificationInput): Promise<LocalNotificationHandle & {
1372
+ scheduledVia: "os-trigger" | "timer";
1373
+ }>;
1374
+ cancelLocal(id: string): Promise<void>;
1375
+ listScheduledLocal(): Promise<ScheduledLocalNotification[]>;
1376
+ };
1377
+ type LocalNotificationsNamespace = typeof notifications;
1378
+ //#endregion
1379
+ //#region src/index.d.ts
1380
+ /** Options for `Amba.functions.invoke`. */
1381
+ interface InvokeOptions {
1382
+ /** HTTP method. Defaults to `"POST"`. */
1383
+ method?: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
1384
+ /** Extra headers merged on top of the injected auth headers. */
1385
+ headers?: Record<string, string>;
1386
+ /**
1387
+ * Called with the server-side request / trace id (the
1388
+ * `X-Amba-Request-Id` response header) as soon as a response is
1389
+ * received — on both success and failure. Use it to correlate a run with
1390
+ * server logs (`functions logs --request-id <traceId>`). `null` when the
1391
+ * server returned no trace id (e.g. a pre-auth rejection that doesn't
1392
+ * stamp the header).
1393
+ *
1394
+ * The same id is also attached to any thrown `AmbaApiError` as
1395
+ * `error.traceId`, so error-path correlation works without a callback.
1396
+ */
1397
+ onTraceId?: (traceId: string | null) => void;
1398
+ }
1399
+ /**
1400
+ * Top-level SDK namespace.
1401
+ *
1402
+ * Stateless from the JS side — all state lives in the SDK runtime. Calling
1403
+ * `Amba.configure()` multiple times replaces the previous instance.
1404
+ */
1405
+ declare const Amba: {
1406
+ /**
1407
+ * Initialize the SDK. Must be called once at app start.
1408
+ *
1409
+ * Seeds an `anonymous_id` from localStorage (if present) or generates a
1410
+ * fresh one.
1411
+ */
1412
+ configure: (config: AmbaConfig) => Promise<void>;
1413
+ /** Project id this SDK instance is scoped to. */
1414
+ readonly projectId: string;
1415
+ /** Anonymous id — stable per browser. */
1416
+ readonly anonymousId: string;
1417
+ /** Authenticated user id, if a session is live. */
1418
+ readonly appUserId: string | undefined;
1419
+ /** Whether a session token is currently held. */
1420
+ readonly isAuthenticated: boolean;
1421
+ /** Toggle verbose logging at runtime. */
1422
+ setDebug: (enabled: boolean) => void;
1423
+ /** Track engagement events. */
1424
+ events: {
1425
+ /**
1426
+ * Track an event.
1427
+ *
1428
+ * The default route writes to the engagement meter — events that
1429
+ * fan out to segments, workflows, and push. Pass
1430
+ * `{ telemetry: true }` for high-volume, fire-and-forget events
1431
+ * that DON'T need fan-out: UI button clicks, scroll-depth markers,
1432
+ * game-loop telemetry. Telemetry events are billed at $0.10 per 1M
1433
+ * overage vs $0.50 per 10K for engagement events — a ~500×
1434
+ * difference that mirrors the underlying compute cost.
1435
+ *
1436
+ * Telemetry events are persisted and queryable via analytics, but
1437
+ * skip segment recompute, streak qualification, currency grant
1438
+ * rules, and `last_seen_at` updates.
1439
+ *
1440
+ * @example
1441
+ * await Amba.events.track('purchase_completed', { amount: 9.99 });
1442
+ * await Amba.events.track('scroll_depth', { px: 1024 }, { telemetry: true });
1443
+ */
1444
+ track: (event: string, properties?: Record<string, unknown>, options?: {
1445
+ telemetry?: boolean;
1446
+ }) => Promise<void>;
1447
+ /**
1448
+ * Dry-run an event against the rule engine WITHOUT firing it. Returns
1449
+ * which rules would match and what each WOULD produce — XP awards,
1450
+ * currency grants, feed items, streak qualification, and webhook
1451
+ * deliveries — so you can predict the consequences of a `track` call
1452
+ * before committing any side effect. Nothing is written.
1453
+ *
1454
+ * Pass `{ userId }` to also preview per-user gating (daily limits,
1455
+ * cooldowns, balance caps); omit it for the user-independent base match.
1456
+ *
1457
+ * @example
1458
+ * const plan = await Amba.events.explain('lesson_completed', { score: 90 });
1459
+ * // plan.matched → [{ rule_type: 'xp', would: { xp_delta: 25 }, ... }, ...]
1460
+ */
1461
+ explain: (event: string, properties?: Record<string, unknown>, options?: {
1462
+ userId?: string | null;
1463
+ }) => Promise<EventExplainResult>;
1464
+ };
1465
+ /** Authentication flows. */
1466
+ auth: {
1467
+ signInAnonymously: () => Promise<AuthResult>;
1468
+ signInWithEmail: (email: string, password: string) => Promise<AuthResult>;
1469
+ signUpWithEmail: (email: string, password: string) => Promise<AuthResult>;
1470
+ /**
1471
+ * Sign in with an Apple identity token (the JWT returned by Sign
1472
+ * in with Apple). Replaces the SDK 3.x polymorphic
1473
+ * `signInWithSocial('apple', token)` form — that surface is
1474
+ * removed in 4.0.
1475
+ */
1476
+ signInWithApple: (identityToken: string) => Promise<AuthResult>;
1477
+ /**
1478
+ * Sign in with a Google ID token (the JWT returned by Google
1479
+ * Sign-In). Replaces the SDK 3.x polymorphic
1480
+ * `signInWithSocial('google', token)` form — that surface is
1481
+ * removed in 4.0.
1482
+ */
1483
+ signInWithGoogle: (idToken: string) => Promise<AuthResult>;
1484
+ /**
1485
+ * Request a one-time passcode for `email`. The server emails a
1486
+ * 6-digit code; the user types it into the app, then call
1487
+ * `verifyEmailOtp(email, code)` to exchange for a session. Always
1488
+ * resolves successfully regardless of email registration — the
1489
+ * server uses uniform responses to prevent account enumeration.
1490
+ */
1491
+ requestEmailOtp: (email: string) => Promise<void>;
1492
+ /**
1493
+ * Exchange `(email, code)` for a session. Returns the same
1494
+ * `AuthResult` shape as `signInWithEmail` and persists tokens for
1495
+ * subsequent calls. Rejects with a single error code for every
1496
+ * failure mode (wrong/expired/exhausted) — the server intentionally
1497
+ * does not distinguish them to prevent enumeration.
1498
+ */
1499
+ verifyEmailOtp: (email: string, code: string) => Promise<AuthResult>;
1500
+ /**
1501
+ * Request a one-time passcode by SMS. `phone` MUST be E.164
1502
+ * (starts with `+`, 8–15 total digits). The SDK rejects
1503
+ * anything else with a `VALIDATION_ERROR` before the network
1504
+ * call. Server sends a 6-digit code; user types it into the
1505
+ * app, then call `verifySmsOtp(phone, code)` to exchange for
1506
+ * a session. Always resolves successfully regardless of phone
1507
+ * registration — uniform responses prevent enumeration.
1508
+ */
1509
+ requestSmsOtp: (phone: string) => Promise<void>;
1510
+ /**
1511
+ * Exchange `(phone, code)` for a session. Same shape +
1512
+ * failure semantics as `verifyEmailOtp`. Persists tokens for
1513
+ * subsequent calls.
1514
+ */
1515
+ verifySmsOtp: (phone: string, code: string) => Promise<AuthResult>;
1516
+ /**
1517
+ * Request a magic-link email for `email`. The server emails the
1518
+ * link; the user clicks it, which delivers a one-time token back
1519
+ * to the app. Exchange that token for a session with
1520
+ * `verifyMagicLink(token)`. Always resolves successfully
1521
+ * regardless of email registration (uniform responses prevent
1522
+ * account enumeration).
1523
+ */
1524
+ requestMagicLink: (email: string) => Promise<void>;
1525
+ /**
1526
+ * Exchange a magic-link `token` for a session. Returns the same
1527
+ * `AuthResult` shape as `signInWithEmail` and persists tokens for
1528
+ * subsequent calls.
1529
+ */
1530
+ verifyMagicLink: (token: string) => Promise<AuthResult>;
1531
+ /**
1532
+ * Link an additional auth provider to the current session — used to
1533
+ * upgrade an anonymous session to an identified one without losing
1534
+ * the user's data. `provider` is one of the supported identity
1535
+ * strings (e.g. `"apple"`, `"google"`); `credential` is the
1536
+ * provider-issued token (identity-token / id-token / OTP code).
1537
+ */
1538
+ linkAccount: (provider: string, credential: string) => Promise<AuthResult>;
1539
+ /**
1540
+ * Attach a verified phone number to the **current session's user** —
1541
+ * without minting new tokens and without changing `user_id`.
1542
+ *
1543
+ * Flow: call `requestSmsOtp(phone)` first to mail the 6-digit code,
1544
+ * then call THIS instead of `verifySmsOtp` when the desired outcome
1545
+ * is "decorate the current (often anonymous) user with this phone"
1546
+ * rather than "switch to the user who owns this phone".
1547
+ *
1548
+ * Guarantees (symmetric to `linkEmailOtp`):
1549
+ *
1550
+ * - **Same `user_id`** before and after — collections, holds, XP,
1551
+ * `anonymous_id` all carry over.
1552
+ * - **Session token unchanged.** The existing token stays valid;
1553
+ * `onAuthStateChange` does NOT fire (no rotation).
1554
+ * - **Idempotent.** Re-linking the same phone to the same user
1555
+ * succeeds — no-op apart from `last_seen_at`.
1556
+ * - **Collision.** If the phone is already verified to a different
1557
+ * user, throws an error with `code = "PHONE_ALREADY_LINKED"` and
1558
+ * `details.conflicting_user_id` populated. The caller decides
1559
+ * whether to abort or switch identities via `verifySmsOtp`.
1560
+ *
1561
+ * Requires an active session — throws `UNAUTHORIZED` otherwise.
1562
+ *
1563
+ * @returns the updated user record (with `phone` set and
1564
+ * `phone_verified = true`).
1565
+ */
1566
+ linkSmsOtp: (phone: string, code: string) => Promise<User>;
1567
+ /**
1568
+ * Attach a verified email to the current session's user. Symmetric
1569
+ * to `linkSmsOtp` — see that for the full guarantee list. Collision
1570
+ * code is `EMAIL_ALREADY_LINKED`.
1571
+ */
1572
+ linkEmailOtp: (email: string, code: string) => Promise<User>;
1573
+ /**
1574
+ * Upgrade an anonymous (or any current) session by attaching an
1575
+ * email + password credential — converting the current user in place
1576
+ * to an email-identified account so its data (streak, library,
1577
+ * progress, `anonymous_id`) is preserved rather than minting a new
1578
+ * user.
1579
+ *
1580
+ * Guarantees (symmetric to `linkEmailOtp`):
1581
+ *
1582
+ * - **Same `user_id`** before and after.
1583
+ * - **Session token unchanged.** No rotation; `onAuthStateChange`
1584
+ * does NOT fire.
1585
+ * - **Idempotent.** Re-linking the same email to the same user
1586
+ * succeeds.
1587
+ * - **Collision.** If the email is already owned by a different user,
1588
+ * throws an error with `code = "EMAIL_ALREADY_LINKED"` and
1589
+ * `details.conflicting_user_id` populated.
1590
+ *
1591
+ * After linking, the user can sign in with `signInWithEmail(email,
1592
+ * password)`. Requires an active session — throws `UNAUTHORIZED`
1593
+ * otherwise.
1594
+ *
1595
+ * @returns the updated user record (with `email` set and
1596
+ * `email_verified = true`).
1597
+ */
1598
+ linkEmailPassword: (email: string, password: string) => Promise<User>;
1599
+ /**
1600
+ * Detach the SMS-OTP identifier from the current session's user.
1601
+ * Refused with `LAST_IDENTIFIER` if this would leave the account
1602
+ * with no recoverable identifier (link another first, then unlink).
1603
+ * No token rotation.
1604
+ */
1605
+ unlinkSmsOtp: () => Promise<User>;
1606
+ /** Detach the email-OTP identifier. Same guarantees as `unlinkSmsOtp`. */
1607
+ unlinkEmailOtp: () => Promise<User>;
1608
+ /** Detach the Apple social identity from the current session's user. */
1609
+ unlinkApple: () => Promise<User>;
1610
+ /** Detach the Google social identity from the current session's user. */
1611
+ unlinkGoogle: () => Promise<User>;
1612
+ /**
1613
+ * Snapshot the identifier providers attached to the current
1614
+ * session's user. Lets a host app gate "should I show the verify-
1615
+ * email prompt" without a profile round-trip.
1616
+ *
1617
+ * Identifier values come back **masked** — raw values stay
1618
+ * server-side.
1619
+ */
1620
+ getCurrentIdentifiers: () => Promise<LinkedIdentifier[]>;
1621
+ signOut: (rotateAnonymousId?: boolean) => Promise<void>;
1622
+ refresh: () => Promise<AuthResult>;
1623
+ me: () => Promise<User>;
1624
+ /**
1625
+ * Snapshot the current session, or `null` if no session is live.
1626
+ * sessionToken/refreshToken/expiresAt come back as empty strings —
1627
+ * they're SDK-managed and not read-back-able from the core. Use
1628
+ * `Amba.isAuthenticated` if you only need the boolean.
1629
+ */
1630
+ getSession: () => Promise<Session | null>;
1631
+ /**
1632
+ * Stable anonymous identifier (kept in memory by the SDK runtime).
1633
+ * Sync today, async for parity with the RN/native shape where the
1634
+ * platform may need to await a persistence read.
1635
+ */
1636
+ getAnonymousId: () => Promise<string>;
1637
+ /**
1638
+ * Subscribe to session changes. Fires after every `signIn*`,
1639
+ * `signUp*`, `refresh`, and `signOut` call. Returns an
1640
+ * unsubscribe function — call it to remove the callback.
1641
+ *
1642
+ * Does NOT fire an initial snapshot when subscribed; call
1643
+ * `getSession()` once after subscribing if you need the
1644
+ * current state up front.
1645
+ */
1646
+ onAuthStateChange: (cb: (session: Session | null) => void) => (() => void);
1647
+ };
1648
+ /** Profile read/update (SDK 4.0). */
1649
+ users: {
1650
+ /**
1651
+ * Get a user profile. `userId` omitted → current user
1652
+ * (`GET /v1/client/users/me`); `userId` set → public-safe profile
1653
+ * of the other user (`GET /v1/client/users/{id}`).
1654
+ */
1655
+ get: (userId?: string) => Promise<User>;
1656
+ /**
1657
+ * Update a profile. Two shapes:
1658
+ * - `update(patch)` — current user (`PATCH /users/me`).
1659
+ * - `update(userId, patch)` — admin-style cross-user update
1660
+ * (`PATCH /users/{id}`); the server requires the caller to
1661
+ * hold `users:write` and 403s otherwise.
1662
+ *
1663
+ * Allowed keys (both shapes): `display_name`, `avatar_url`,
1664
+ * `properties`, `timezone`.
1665
+ */
1666
+ update: {
1667
+ (patch: Partial<User>): Promise<User>;
1668
+ (userId: string, patch: Partial<User>): Promise<User>;
1669
+ };
1670
+ };
1671
+ /** List + revoke customer-recorded app sessions (SDK 4.0). */
1672
+ sessions: {
1673
+ /** `GET /v1/client/sessions` — list the caller's app sessions. */
1674
+ list: () => Promise<AppSession[]>;
1675
+ /**
1676
+ * `POST /v1/client/sessions/:id/end` — end the session with the
1677
+ * given id. The 4.0 `revoke` contract returns unit; customers who
1678
+ * need the ended row should use the lower-level `sessions.end`
1679
+ * helper on the core (not exposed on the public SDK).
1680
+ */
1681
+ revoke: (sessionId: string) => Promise<void>;
1682
+ };
1683
+ /**
1684
+ * Live collection subscriptions (SDK 4.0). Returns a builder — chain
1685
+ * `.where(column, value)` to narrow, then `.subscribe(cb)` to start
1686
+ * receiving row changes. `subscribe` returns an unsubscribe handle.
1687
+ *
1688
+ * @example
1689
+ * const unsub = Amba.collection("messages")
1690
+ * .where("circle_id", circleId)
1691
+ * .subscribe((change) => {
1692
+ * // change.op: "INSERT" | "UPDATE" | "DELETE"; change.row: the row
1693
+ * });
1694
+ * // later: unsub();
1695
+ *
1696
+ * Delivery mirrors `Amba.collections.find()` — you only receive rows your
1697
+ * session owns. Requires an authenticated session.
1698
+ */
1699
+ collection: <T = unknown>(name: string) => CollectionSubscription<T>;
1700
+ /** Typed collection CRUD with auto-RLS. */
1701
+ collections: {
1702
+ find: <T = unknown>(name: string, options?: FindOptions) => Promise<FindResponse<T>>;
1703
+ findOne: <T = unknown>(name: string, id: string) => Promise<T>;
1704
+ insert: <T = unknown>(name: string, row: Record<string, unknown>) => Promise<T>;
1705
+ update: <T = unknown>(name: string, id: string, set: Record<string, unknown>) => Promise<T>;
1706
+ delete: (name: string, id: string) => Promise<{
1707
+ data: {
1708
+ deleted: boolean;
1709
+ };
1710
+ }>;
1711
+ /**
1712
+ * Insert a row, resolving a unique-index conflict instead of failing.
1713
+ *
1714
+ * With `options.onConflict: "ignore"` a conflicting row is left
1715
+ * untouched and returned; with `"update"` the supplied columns are
1716
+ * merged into the existing row. `options.conflictTarget` names the
1717
+ * unique-index columns to arbitrate on (e.g. `["user_id", "kind"]`) and
1718
+ * every one must be present in `row`. Auto-RLS still applies — the
1719
+ * ignore-fallback / merge is scoped to the caller's own rows.
1720
+ *
1721
+ * @example
1722
+ * await Amba.collections.upsert("prefs",
1723
+ * { kind: "theme", value: "dark" },
1724
+ * { onConflict: "update", conflictTarget: ["kind"] });
1725
+ */
1726
+ upsert: <T = unknown>(name: string, row: Record<string, unknown>, options: UpsertOptions) => Promise<T>;
1727
+ /**
1728
+ * Bulk soft-delete every row matching `filter`. Returns the count of
1729
+ * rows deleted. Auto-RLS scopes the delete to the caller's own rows;
1730
+ * soft-deleted rows can be brought back with {@link restore}.
1731
+ *
1732
+ * @example
1733
+ * const { data } = await Amba.collections.deleteWhere(
1734
+ * "todos", Amba.collections.where.eq("status", "done"));
1735
+ * console.log(`${data.deleted} removed`);
1736
+ */
1737
+ deleteWhere: (name: string, filter: Filter) => Promise<DeleteManyResult>;
1738
+ /**
1739
+ * Restore a previously soft-deleted row by id (clears `deleted_at`).
1740
+ * Returns the restored row. Only rows currently soft-deleted and owned
1741
+ * by the caller can be restored; anything else resolves as not-found.
1742
+ */
1743
+ restore: <T = unknown>(name: string, id: string) => Promise<T>;
1744
+ /**
1745
+ * Run an ordered list of writes (insert / update / delete, across any
1746
+ * collections) as ONE atomic transaction. The whole batch commits, or — if
1747
+ * any op fails — rolls back entirely; a partial multi-write can never
1748
+ * persist. Resolves to the per-op results; throws naming the failing op
1749
+ * index (`details.opIndex`) on rollback. Each op honors the same auto-RLS /
1750
+ * owner-scoping / compare-and-set rules as the single-row methods.
1751
+ *
1752
+ * @example
1753
+ * await Amba.collections.transaction([
1754
+ * { collection: "carts", op: "insert", row: { sku: "widget", qty: 2 } },
1755
+ * { collection: "inventory", op: "update", id: invId, set: { reserved: 2 } },
1756
+ * ]);
1757
+ */
1758
+ transaction: (operations: TransactionOp[]) => Promise<TransactionOpResult[]>;
1759
+ /**
1760
+ * Vector-search the collection (SDK 4.0). Returns the `k` nearest
1761
+ * neighbors of `options.query_vector` against the column named
1762
+ * `options.vector_field`. Distance metric and index strategy are
1763
+ * server-side; pass `options.filter` to narrow the candidate set
1764
+ * before ranking.
1765
+ *
1766
+ * Spec note: the canonical `findNearest(name, vector, k, filter?)`
1767
+ * shape omits the column name; the SDK widens the contract to a
1768
+ * single typed options object so `vector_field` is required and
1769
+ * unambiguous in TS.
1770
+ */
1771
+ findNearest: <T = unknown>(name: string, options: NearestOptions) => Promise<NearestResponse<T>>;
1772
+ /**
1773
+ * Count rows in the collection (SDK 4.0). Pass an optional filter
1774
+ * to scope; omit it for the total. The server's response unwraps to
1775
+ * a bare number on the JS side.
1776
+ */
1777
+ count: (name: string, filter?: Filter) => Promise<number>;
1778
+ /**
1779
+ * Optimistic insert — update a local view immediately, fire the real
1780
+ * insert, and roll the local view back if it fails. The reconcile is your
1781
+ * own (replace the temporary local row with the server's authoritative
1782
+ * row in your `apply`/`rollback` handlers). Thin convenience over the
1783
+ * exported {@link withOptimistic} so the common single-write case reads
1784
+ * clean; for multi-step flows use `withOptimistic` directly.
1785
+ *
1786
+ * Optimistic writes always hit the network directly — they are NOT
1787
+ * diverted to the offline outbox even when `Amba.offline` is enabled.
1788
+ * Optimistic UI needs an unambiguous outcome: this resolves only on a
1789
+ * confirmed server write, and rejects (running your `rollback`) on any
1790
+ * failure including being offline. To buffer-and-replay instead, use the
1791
+ * plain `insert`/`update`/`delete` with the outbox on.
1792
+ *
1793
+ * @example
1794
+ * await Amba.collections.optimisticInsert("todos", draft, {
1795
+ * apply: () => { const prev = todos; setTodos([...todos, draft]); return prev; },
1796
+ * rollback: (prev) => setTodos(prev),
1797
+ * });
1798
+ */
1799
+ optimisticInsert: <T = unknown, TApply = unknown>(name: string, row: Record<string, unknown>, view: {
1800
+ apply: () => TApply;
1801
+ rollback: (applied: TApply) => void;
1802
+ }) => Promise<T>;
1803
+ /** Optimistic update — see {@link optimisticInsert}. */
1804
+ optimisticUpdate: <T = unknown, TApply = unknown>(name: string, id: string, set: Record<string, unknown>, view: {
1805
+ apply: () => TApply;
1806
+ rollback: (applied: TApply) => void;
1807
+ }) => Promise<T>;
1808
+ /** Optimistic delete — see {@link optimisticInsert}. */
1809
+ optimisticDelete: <TApply = unknown>(name: string, id: string, view: {
1810
+ apply: () => TApply;
1811
+ rollback: (applied: TApply) => void;
1812
+ }) => Promise<{
1813
+ data: {
1814
+ deleted: boolean;
1815
+ };
1816
+ }>;
1817
+ /** Filter DSL helpers — pass into `find({ filter: f })`. */
1818
+ where: {
1819
+ eq: (column: string, value: FilterValue) => Filter;
1820
+ ne: (column: string, value: FilterValue) => Filter;
1821
+ gt: (column: string, value: FilterValue) => Filter;
1822
+ gte: (column: string, value: FilterValue) => Filter;
1823
+ lt: (column: string, value: FilterValue) => Filter;
1824
+ lte: (column: string, value: FilterValue) => Filter;
1825
+ in: (column: string, values: FilterValue[]) => Filter;
1826
+ notIn: (column: string, values: FilterValue[]) => Filter;
1827
+ like: (column: string, pattern: string) => Filter;
1828
+ ilike: (column: string, pattern: string) => Filter;
1829
+ isNull: (column: string) => Filter;
1830
+ isNotNull: (column: string) => Filter;
1831
+ and: (...filters: Filter[]) => Filter;
1832
+ or: (...filters: Filter[]) => Filter;
1833
+ not: (filter: Filter) => Filter;
1834
+ };
1835
+ };
1836
+ /** Media uploads + CDN URL helpers. */
1837
+ storage: {
1838
+ presign: (params: {
1839
+ bucket: string;
1840
+ filename: string;
1841
+ mimeType: string;
1842
+ sizeBytes: number;
1843
+ retentionDays?: number;
1844
+ }) => Promise<PresignData>;
1845
+ /**
1846
+ * Complete-flow upload: presign → PUT to object storage → commit.
1847
+ * Returns the finalized MediaAsset.
1848
+ */
1849
+ upload: (params: {
1850
+ bucket: string;
1851
+ file: File | Blob;
1852
+ filename?: string;
1853
+ retentionDays?: number;
1854
+ }) => Promise<MediaAsset>;
1855
+ /**
1856
+ * List media assets (SDK 4.0). Pass `prefix` to scope to a key
1857
+ * prefix (filename / path-like). Without it, returns the caller's
1858
+ * full list. Server enforces RLS — only the calling user's assets
1859
+ * are returned.
1860
+ */
1861
+ list: (prefix?: string) => Promise<MediaListResponse>;
1862
+ /**
1863
+ * Soft-delete the asset (SDK 4.0). Accepts the asset id returned
1864
+ * by `presign` / `commit` / `list`. The corresponding storage object
1865
+ * is removed asynchronously on the server.
1866
+ */
1867
+ delete: (assetId: string) => Promise<void>;
1868
+ /**
1869
+ * Download an asset's bytes (SDK 4.0). Returns a `Blob` wrapping
1870
+ * the Uint8Array returned by the engine, so consumers can
1871
+ * call `.text()` / `.arrayBuffer()` / `.stream()` directly or
1872
+ * pipe to a `URL.createObjectURL()` for preview. The two-step
1873
+ * fetch (asset metadata → CDN GET) is handled inside the SDK
1874
+ * core; this method is single-call from the consumer.
1875
+ */
1876
+ download: (assetId: string) => Promise<Blob>;
1877
+ };
1878
+ /** Curated public-asset catalogs. */
1879
+ media: {
1880
+ /**
1881
+ * Fetch a public media catalog by slug
1882
+ * (`GET /v1/client/media/catalogs/:slug`). A catalog is a named,
1883
+ * curated bundle of assets your app loads in one call — each item
1884
+ * resolves to a stable, long-cacheable public URL, returned in the
1885
+ * order configured for the catalog. Only catalogs published as public
1886
+ * are readable; the call needs the client key but no signed-in user.
1887
+ */
1888
+ catalog: (slug: string) => Promise<MediaCatalog>;
1889
+ };
1890
+ /** Offline change replay (SDK 4.0). */
1891
+ sync: {
1892
+ /**
1893
+ * Replay a batch of buffered mutations
1894
+ * (`POST /v1/client/sync`). The server applies them in order and
1895
+ * returns the count of successfully-applied changes plus any
1896
+ * per-row conflicts. Conflict resolution in 4.0 is always
1897
+ * server-wins; the `SyncConflict.resolution` field is reserved
1898
+ * for future strategies.
1899
+ */
1900
+ pushChanges: (changes: SyncChange[]) => Promise<PushChangesResult>;
1901
+ /**
1902
+ * Pull remote-origin changes since the supplied cursor
1903
+ * (`GET /v1/client/sync`). Pass the `checkpoint_token` from the
1904
+ * previous `pushChanges` / `pullChanges` reply, and the
1905
+ * `entity_type` to scope to a single collection. On the first
1906
+ * call, omit `checkpoint_token` to receive the full backfill.
1907
+ */
1908
+ pullChanges: (since: PullChangesCursor) => Promise<PullChangesResult>;
1909
+ };
1910
+ /**
1911
+ * Automatic offline mutation queue (opt-in).
1912
+ *
1913
+ * Once enabled, a collection write (`insert` / `update` / `delete`) or a
1914
+ * `track` that fails because the device is offline is buffered to a durable
1915
+ * local outbox instead of throwing — then replayed in order the moment
1916
+ * connectivity returns. Replays reuse a per-op idempotency key so a write
1917
+ * that actually reached the server before the connection dropped is never
1918
+ * applied twice.
1919
+ *
1920
+ * This is distinct from `Amba.sync` (manual change replay): `offline` is
1921
+ * the automatic, fire-and-forget safety net for ordinary writes.
1922
+ *
1923
+ * @example
1924
+ * Amba.offline.enable(); // call once after configure()
1925
+ * await Amba.collections.insert("todos", { title: "Buy milk" }); // queues if offline
1926
+ * // ...reconnect...
1927
+ * console.log(await Amba.offline.pendingCount()); // drains automatically
1928
+ */
1929
+ offline: {
1930
+ /**
1931
+ * Turn the outbox on and start auto-flushing on reconnect. Idempotent —
1932
+ * a repeat call detaches the prior `online` listener before re-attaching,
1933
+ * so it never stacks listeners. Immediately attempts a flush in case ops
1934
+ * were persisted in a prior session. Returns a disable function that
1935
+ * detaches the reconnect listener (queued ops are NOT dropped — call
1936
+ * `clear()` for that).
1937
+ *
1938
+ * The disabler is tied to THIS activation: once a later `enable()` (or a
1939
+ * `configure()`) supersedes it, calling it is a no-op — so a stale disabler
1940
+ * can never turn a freshly re-enabled outbox off.
1941
+ */
1942
+ enable: () => (() => void);
1943
+ /** Whether the outbox is currently enabled. */
1944
+ isEnabled: () => boolean;
1945
+ /** Count of writes awaiting replay. */
1946
+ pendingCount: () => Promise<number>;
1947
+ /** Snapshot the pending ops (a copy — safe to inspect). */
1948
+ pending: () => Promise<OutboxOp[]>;
1949
+ /**
1950
+ * Force a replay attempt now (also runs automatically on reconnect).
1951
+ * Resolves with `{ flushed, dropped, remaining }` (dropped = ops the server rejected deterministically on replay, removed so they cannot wedge the queue).
1952
+ */
1953
+ flush: () => Promise<FlushResult>;
1954
+ /** Drop every queued write without replaying. */
1955
+ clear: () => Promise<void>;
1956
+ };
1957
+ /** Push notifications. */
1958
+ push: {
1959
+ register: (token: string, platform: PushPlatform, bundleId?: string) => Promise<PushToken>;
1960
+ unregister: (token: string) => Promise<void>;
1961
+ getTokens: () => Promise<PushToken[]>;
1962
+ subscribe: (topic: string) => Promise<void>;
1963
+ unsubscribe: (topic: string) => Promise<void>;
1964
+ /**
1965
+ * Auto re-register the device's Web Push subscription when the browser
1966
+ * rotates it.
1967
+ *
1968
+ * The browser can silently roll a push subscription (key rollover,
1969
+ * permission re-grant); when it does, the service worker fires
1970
+ * `pushsubscriptionchange` and the old endpoint stops delivering. Call
1971
+ * this from your service worker (where that event fires) and the SDK
1972
+ * re-registers the fresh endpoint through the idempotent
1973
+ * {@link register} path automatically.
1974
+ *
1975
+ * No-op (returns a no-op detacher) in runtimes without service-worker /
1976
+ * push support, so it's safe to call unconditionally. Returns an
1977
+ * unsubscribe function.
1978
+ *
1979
+ * @example
1980
+ * // in your service worker
1981
+ * Amba.push.enableAutoRotation();
1982
+ */
1983
+ enableAutoRotation: () => (() => void);
1984
+ };
1985
+ /**
1986
+ * On-device local notifications — `scheduleLocal` / `cancelLocal` /
1987
+ * `listScheduledLocal`. Fire from the device without a server
1988
+ * round-trip (reminders, streak nudges), distinct from remote push
1989
+ * (`Amba.push.*`). Browser scheduling is partial: the OS-backed path
1990
+ * needs the experimental Notification Triggers API + an active Service
1991
+ * Worker (Chromium today); without it the SDK falls back to an in-page
1992
+ * timer and reports `scheduledVia: "timer"` so the degradation is
1993
+ * visible — never a silent no-op. See `./local-notifications.ts`.
1994
+ */
1995
+ notifications: {
1996
+ scheduleLocal(input: ScheduleLocalNotificationInput): Promise<LocalNotificationHandle & {
1997
+ scheduledVia: "os-trigger" | "timer";
1998
+ }>;
1999
+ cancelLocal(id: string): Promise<void>;
2000
+ listScheduledLocal(): Promise<ScheduledLocalNotification[]>;
2001
+ };
2002
+ /** Subscription entitlements — feature gates resolved server-side. */
2003
+ entitlements: {
2004
+ list: () => Promise<UserEntitlement[]>;
2005
+ has: (name: string) => Promise<boolean>;
2006
+ /**
2007
+ * Restore purchases — re-sync the user's owned entitlements from the
2008
+ * server and return the active set. Required for App Store review (3.1.1);
2009
+ * wire it to a "Restore Purchases" button. Rejects (access unchanged) if
2010
+ * the server can't reach the validation engine — show a retry message.
2011
+ */
2012
+ restore: () => Promise<RestoreResult>;
2013
+ };
2014
+ /**
2015
+ * Subscription offerings — the packages your paywall renders, provider-
2016
+ * neutral. Pass an `offeringId` to fetch one offering; omit it for all.
2017
+ */
2018
+ offerings: (offeringId?: string) => Promise<OfferingsResult>;
2019
+ /** LLM proxy. */
2020
+ ai: {
2021
+ anthropic: {
2022
+ messages: {
2023
+ create: (request: AiMessageRequest) => Promise<AiMessageResponse>;
2024
+ /**
2025
+ * Stream the response token-by-token instead of waiting for the full
2026
+ * buffered reply. Returns an async iterable of vendor-neutral events:
2027
+ * zero or more `{type:'delta', text}` as tokens arrive, then exactly
2028
+ * one `{type:'done', usage, model, stopReason}` when the stream ends.
2029
+ *
2030
+ * ```ts
2031
+ * let answer = "";
2032
+ * for await (const ev of Amba.ai.anthropic.messages.stream({ prompt_slug: "chat" })) {
2033
+ * if (ev.type === "delta") answer += ev.text;
2034
+ * else console.log("tokens:", ev.usage.output_tokens);
2035
+ * }
2036
+ * ```
2037
+ *
2038
+ * Pass `{ onChunk }` to also receive each delta via callback. Throws
2039
+ * an `AmbaApiError` on a transport failure or a non-2xx response.
2040
+ */
2041
+ stream: (request: AiMessageRequest, options?: AiStreamOptions) => AsyncGenerator<AiStreamEvent, void, unknown>;
2042
+ };
2043
+ };
2044
+ openai: {
2045
+ chat: {
2046
+ completions: {
2047
+ /**
2048
+ * Stream an OpenAI-routed prompt token-by-token. Same event shape
2049
+ * as `Amba.ai.anthropic.messages.stream` — the vendor split is just
2050
+ * a stable wire path; the server resolves the real provider from the
2051
+ * prompt's registered configuration.
2052
+ */
2053
+ stream: (request: AiMessageRequest, options?: AiStreamOptions) => AsyncGenerator<AiStreamEvent, void, unknown>;
2054
+ };
2055
+ };
2056
+ };
2057
+ };
2058
+ /**
2059
+ * Invoke a deployed function by name, with the project's client key (and
2060
+ * the live session token, if any) injected automatically.
2061
+ *
2062
+ * This is the authenticated way to call your functions — prefer it over
2063
+ * hand-rolling `fetch` against a function URL, which leaves the call
2064
+ * unauthenticated (a footgun when the function holds secrets).
2065
+ *
2066
+ * The return value is unchanged (the parsed body, typed `T`). To
2067
+ * correlate a run with server logs, read the trace id additively:
2068
+ * pass `options.onTraceId` to receive the `X-Amba-Request-Id` on every
2069
+ * response, and on failure read `error.traceId` off the thrown
2070
+ * `AmbaApiError`. Then `functions logs --request-id <traceId>` pulls the
2071
+ * matching server log.
2072
+ *
2073
+ * @example
2074
+ * const result = await Amba.functions.invoke<{ ok: boolean }>(
2075
+ * 'send_welcome_email',
2076
+ * { to: 'user@example.com' },
2077
+ * { onTraceId: (id) => console.log('trace', id) },
2078
+ * );
2079
+ *
2080
+ * @example
2081
+ * try {
2082
+ * await Amba.functions.invoke('charge', { amount: 100 });
2083
+ * } catch (err) {
2084
+ * if (err instanceof AmbaApiError) {
2085
+ * console.error(`failed (trace ${err.traceId}): ${err.message}`);
2086
+ * }
2087
+ * }
2088
+ *
2089
+ * @param name The deployed function name.
2090
+ * @param body JSON-serializable request body (omitted for GET).
2091
+ * @param options Method, extra headers, and an `onTraceId` callback.
2092
+ * @returns The parsed JSON response (or the raw text if not JSON).
2093
+ * @throws AmbaApiError on a network failure or a non-2xx response. When
2094
+ * the function / router returns the standard `{ error: { code, message } }`
2095
+ * envelope, those surface as the thrown error's `code` + `message`; the
2096
+ * server trace id (when present) is attached as `error.traceId`.
2097
+ */
2098
+ functions: {
2099
+ invoke: <T = unknown>(name: string, body?: unknown, options?: InvokeOptions) => Promise<T>;
2100
+ };
2101
+ /** Remote config bundle. */
2102
+ config: {
2103
+ fetch: () => Promise<ConfigBundle>;
2104
+ };
2105
+ /** Server-evaluated feature flags. */
2106
+ flags: {
2107
+ fetch: () => Promise<FlagAssignment[]>;
2108
+ /**
2109
+ * Single-flag lookup (SDK 4.0). Wraps `GET /v1/client/flags/{key}`.
2110
+ * Returns `null` for unknown / disabled keys; other errors
2111
+ * (auth, rate-limit, network) propagate.
2112
+ */
2113
+ get: (key: string) => Promise<FlagAssignment | null>;
2114
+ };
2115
+ /**
2116
+ * Wire-verify primitive. Call once after `configure()` to confirm
2117
+ * the SDK is talking to the right project with the right key.
2118
+ *
2119
+ * The response is entirely server-decided — none of the fields are
2120
+ * derived from request parameters — so it's trustworthy as a setup
2121
+ * check. Common usage:
2122
+ *
2123
+ * ```ts
2124
+ * const ping = await Amba.diagnostics.ping();
2125
+ * if (!ping.ok || ping.server_project_id !== expectedProjectId) {
2126
+ * throw new Error(`amba misconfigured: ${JSON.stringify(ping)}`);
2127
+ * }
2128
+ * ```
2129
+ */
2130
+ diagnostics: {
2131
+ ping: () => Promise<PingResult>;
2132
+ };
2133
+ /** Achievement definitions + per-user progress. */
2134
+ achievements: {
2135
+ getAll: () => Promise<Achievement[]>;
2136
+ getProgress: () => Promise<AchievementProgress[]>;
2137
+ };
2138
+ /** Time-bounded challenges with rewards. */
2139
+ challenges: {
2140
+ getActive: () => Promise<Challenge[]>;
2141
+ get: (id: string) => Promise<Challenge>;
2142
+ getProgress: (id: string) => Promise<ChallengeProgress>;
2143
+ claim: (id: string) => Promise<ChallengeProgress>;
2144
+ };
2145
+ /** Virtual currency balances + ledger. */
2146
+ currencies: {
2147
+ getBalance: () => Promise<CurrencyBalance[]>;
2148
+ getTransactions: (currencyKey: string) => Promise<CurrencyTransaction[]>;
2149
+ };
2150
+ /** User inventory items + purchase/consume. */
2151
+ inventory: {
2152
+ getItems: () => Promise<InventoryItem[]>;
2153
+ getItem: (id: string) => Promise<InventoryItem>;
2154
+ purchase: (request: PurchaseRequest) => Promise<InventoryItem>;
2155
+ consume: (request: ConsumeRequest) => Promise<InventoryItem>;
2156
+ };
2157
+ /** Score rankings. */
2158
+ leaderboards: {
2159
+ get: (key: string) => Promise<Leaderboard>;
2160
+ getEntries: (key: string, limit?: number) => Promise<LeaderboardEntry[]>;
2161
+ getMyRank: (key: string) => Promise<LeaderboardEntry>;
2162
+ };
2163
+ /**
2164
+ * Tiered weekly leaderboard cohorts (SDK 4.0). Each user is bucketed
2165
+ * into a small cohort (~25 members) within their league tier; the
2166
+ * top scorers each week promote and the bottom scorers demote.
2167
+ */
2168
+ leagues: {
2169
+ /**
2170
+ * `GET /v1/client/leagues/me` — the caller's active-cohort
2171
+ * standing plus live rank/score and member count.
2172
+ */
2173
+ me: () => Promise<LeagueMembership>;
2174
+ /**
2175
+ * `GET /v1/client/leagues/me/cohort` — the caller's cohort with
2176
+ * every member's anonymised standing (display name, score, rank).
2177
+ * Notably no user ids — the server strips them so a leaderboard
2178
+ * UI can render the cohort without exposing identities.
2179
+ */
2180
+ cohort: () => Promise<LeagueCohortView>;
2181
+ };
2182
+ /** Purchase storefronts. */
2183
+ stores: {
2184
+ list: () => Promise<Store[]>;
2185
+ getPurchaseOptions: (storeKey: string) => Promise<PurchaseOption[]>;
2186
+ purchase: (storeKey: string, purchaseOptionId: string, receipt: unknown) => Promise<PurchaseResult>;
2187
+ };
2188
+ /** Experience points + level system. */
2189
+ xp: {
2190
+ getBalance: () => Promise<XpBalance>;
2191
+ getHistory: (limit?: number) => Promise<XpTransaction[]>;
2192
+ claim: (grantKey: string) => Promise<XpTransaction>;
2193
+ };
2194
+ /** Daily-engagement streaks. */
2195
+ streaks: {
2196
+ getAll: () => Promise<Streak[]>;
2197
+ qualify: (streakKey: string) => Promise<Streak>;
2198
+ };
2199
+ /**
2200
+ * Live gamification updates for the signed-in user. Call `.subscribe(cb)` to
2201
+ * receive xp / level / achievement / streak changes the instant they happen,
2202
+ * instead of polling `xp.getBalance()` / `streaks.getAll()` on a timer.
2203
+ *
2204
+ * @example
2205
+ * const unsub = Amba.gamification.subscribe((change) => {
2206
+ * // change.kind: "xp" | "level" | "achievement" | "streak"
2207
+ * if (change.kind === "level") showLevelUp(change.data.level);
2208
+ * });
2209
+ * // later: unsub();
2210
+ *
2211
+ * You only ever receive your own gamification changes — never another user's.
2212
+ * Requires an authenticated session.
2213
+ */
2214
+ readonly gamification: GamificationSubscription;
2215
+ /** Activity feeds. */
2216
+ feeds: {
2217
+ getActivity: (feed?: string, cursor?: string) => Promise<FeedResponse>;
2218
+ };
2219
+ /** Friend graph + block list. */
2220
+ friends: {
2221
+ getList: () => Promise<Friendship[]>;
2222
+ getFriends: () => Promise<Friendship[]>;
2223
+ /**
2224
+ * Send a friend request to `userId` (SDK 4.0). Returns the
2225
+ * resulting `Friendship` row in `pending` state.
2226
+ */
2227
+ sendRequest: (userId: string) => Promise<Friendship>;
2228
+ /**
2229
+ * Accept a pending incoming friend request (SDK 4.0). Pass the
2230
+ * friendship id from `getList()`.
2231
+ */
2232
+ acceptRequest: (friendshipId: string) => Promise<Friendship>;
2233
+ /**
2234
+ * Decline a pending incoming friend request (SDK 4.0). Removes
2235
+ * the pending row; idempotent.
2236
+ */
2237
+ declineRequest: (friendshipId: string) => Promise<void>;
2238
+ blockUser: (userId: string) => Promise<Friendship>;
2239
+ unblockUser: (userId: string) => Promise<void>;
2240
+ removeBlock: (friendshipId: string) => Promise<void>;
2241
+ /**
2242
+ * Unfriend by the other user's id (SDK 4.0). Wraps
2243
+ * `DELETE /v1/client/friends/by-user/{userId}`. Server preserves
2244
+ * blocked rows; use `unblockUser` to clear those. 404 propagates
2245
+ * as `AmbaApiError`.
2246
+ */
2247
+ removeFriend: (userId: string) => Promise<void>;
2248
+ };
2249
+ /** Multi-user groups. */
2250
+ groups: {
2251
+ create: (params: GroupCreate) => Promise<Group>;
2252
+ get: (id: string) => Promise<Group>;
2253
+ update: (id: string, patch: GroupUpdate) => Promise<Group>;
2254
+ delete: (id: string) => Promise<void>;
2255
+ getMembers: (id: string) => Promise<GroupMember[]>;
2256
+ join: (id: string) => Promise<GroupMember>;
2257
+ leave: (id: string) => Promise<void>;
2258
+ invite: (id: string, userId: string) => Promise<GroupMember>;
2259
+ };
2260
+ /**
2261
+ * User-to-user direct messaging.
2262
+ *
2263
+ * Paths mirror the API exactly — see `apps/api/src/routes/client/messaging.ts`:
2264
+ *
2265
+ * | SDK method | API route |
2266
+ * | --------------------- | -------------------------------------------------------- |
2267
+ * | `createConversation` | POST `/v1/client/messaging/conversations` |
2268
+ * | `getConversations` | GET `/v1/client/messaging/conversations` |
2269
+ * | `listConversations` | (alias for `getConversations`) |
2270
+ * | `sendMessage` | POST `/v1/client/messaging/conversations/:id/messages` |
2271
+ * | `listMessages` | GET `/v1/client/messaging/conversations/:id/messages` |
2272
+ * | `markRead` | POST `/v1/client/messaging/conversations/:id/read` |
2273
+ *
2274
+ * Task #62 corrected `sendMessage`'s path (it previously POSTed to
2275
+ * `/messaging/messages`, a route that doesn't exist on the API), and
2276
+ * added the four missing helpers. The previous `getMessage(id)` helper
2277
+ * has been removed — there is no GET-by-id route on the server; the
2278
+ * shape consumers actually want is `listMessages(conversationId, opts)`.
2279
+ */
2280
+ messaging: {
2281
+ /**
2282
+ * `POST /v1/client/messaging/conversations` — create (or fetch the
2283
+ * existing direct) conversation. For `type: "direct"` with exactly
2284
+ * one other participant, the server returns the existing row if
2285
+ * one already exists between these two users — calls are
2286
+ * idempotent.
2287
+ */
2288
+ createConversation: (request: CreateConversationRequest) => Promise<Conversation>;
2289
+ /**
2290
+ * `GET /v1/client/messaging/conversations` — list the caller's
2291
+ * conversations. Canonical 4.0 name; the legacy `getConversations`
2292
+ * / `listConversations` are removed in 4.0 per the spec.
2293
+ */
2294
+ conversations: () => Promise<Conversation[]>;
2295
+ /**
2296
+ * `POST /v1/client/messaging/conversations/:conversationId/messages`
2297
+ * — send a message into an existing conversation. The
2298
+ * `conversation_id` argument is a path parameter (not a body
2299
+ * field) — the previous SDK shape mistakenly placed it in the
2300
+ * body, which the API ignored because the route was wrong anyway.
2301
+ */
2302
+ sendMessage: (conversationId: string, request: SendMessageRequest) => Promise<Message>;
2303
+ /**
2304
+ * `GET /v1/client/messaging/conversations/:conversationId/messages`
2305
+ * — list messages in a conversation. Pagination uses `?limit=&offset=`
2306
+ * (the server's `parsePagination` clamps both; defaults to 50 / 0).
2307
+ */
2308
+ listMessages: (conversationId: string, opts?: ListMessagesOptions) => Promise<Message[]>;
2309
+ /**
2310
+ * `GET /v1/client/messaging/conversations/:conversationId/messages/:messageId`
2311
+ * — fetch a single message by id. Returns `null` when the message
2312
+ * doesn't exist or the caller can't see it. (SDK 4.0: Phase A
2313
+ * shipped the missing `amba_messaging_get_message` core symbol the
2314
+ * native SDKs were already trying to call.)
2315
+ */
2316
+ getMessage: (conversationId: string, messageId: string) => Promise<Message | null>;
2317
+ /**
2318
+ * `POST /v1/client/messaging/conversations/:conversationId/participants`
2319
+ * — add a participant to an existing conversation. The caller must
2320
+ * already be a participant (the server returns `403
2321
+ * NOT_A_PARTICIPANT` otherwise) — this is the peer-invite path.
2322
+ * Idempotent: re-adding an existing member returns the existing
2323
+ * participant row, not an error. Enables dynamic membership growth
2324
+ * (e.g. a circle whose members qualify over time). Server-side
2325
+ * membership management (admin/matchmaker) uses the admin
2326
+ * add-participant endpoint instead.
2327
+ */
2328
+ addParticipant: (conversationId: string, userId: string) => Promise<ConversationParticipant>;
2329
+ /**
2330
+ * `POST /v1/client/messaging/conversations/:conversationId/read`
2331
+ * — bump the caller's `last_read_at` for the conversation. Returns
2332
+ * the updated participant row.
2333
+ */
2334
+ markRead: (conversationId: string) => Promise<MarkReadResult>;
2335
+ /**
2336
+ * Live message subscriptions (SDK 4.0). Returns a builder — call
2337
+ * `.subscribe(cb)` to receive new messages in a conversation live
2338
+ * instead of polling `listMessages`. `subscribe` returns an
2339
+ * unsubscribe handle.
2340
+ *
2341
+ * @example
2342
+ * const unsub = Amba.messaging
2343
+ * .conversation(conversationId)
2344
+ * .subscribe((change) => {
2345
+ * // change.op === "INSERT"; change.row is the new message
2346
+ * appendMessageToThread(change.row);
2347
+ * });
2348
+ * // later: unsub();
2349
+ *
2350
+ * Delivery is scoped to conversation participants — you receive a
2351
+ * conversation's messages only if your session is a member of it.
2352
+ * Requires an authenticated session.
2353
+ */
2354
+ conversation: <T = Message>(conversationId: string) => ConversationSubscription<T>;
2355
+ };
2356
+ /** Report users / content for moderation. */
2357
+ moderation: {
2358
+ reportUser: (request: ReportRequest) => Promise<Report>;
2359
+ reportContent: (request: ReportRequest) => Promise<Report>;
2360
+ getReportStatus: (id: string) => Promise<Report>;
2361
+ };
2362
+ /** User reviews of catalog items or other users. */
2363
+ reviews: {
2364
+ list: (targetType: string, targetId: string) => Promise<Review[]>;
2365
+ create: (params: ReviewCreate) => Promise<Review>;
2366
+ update: (id: string, patch: ReviewUpdate) => Promise<Review>;
2367
+ delete: (id: string) => Promise<void>;
2368
+ };
2369
+ /** RBAC roles + permission checks. */
2370
+ roles: {
2371
+ getMyRoles: () => Promise<Role[]>;
2372
+ hasPermission: (permission: string) => Promise<boolean>;
2373
+ };
2374
+ /** Referral codes + claim flow. */
2375
+ referrals: {
2376
+ getReferralCode: () => Promise<ReferralCode>;
2377
+ claimReferral: (code: string) => Promise<ReferralClaim>;
2378
+ create: (code?: string, maxUses?: number) => Promise<ReferralCode>;
2379
+ };
2380
+ /** Read-only product catalog. */
2381
+ catalog: {
2382
+ list: () => Promise<CatalogItem[]>;
2383
+ /**
2384
+ * Fetch a single catalog item by id (SDK 4.0).
2385
+ * `GET /v1/client/catalog/:id`.
2386
+ */
2387
+ get: (id: string) => Promise<CatalogItem>;
2388
+ };
2389
+ /** Developer-defined content items (daily quotes, lessons, etc.). */
2390
+ content: {
2391
+ /**
2392
+ * Get today's published item for `channel`. Defaults to `"default"`
2393
+ * so single-channel apps can call `Amba.content.today()` bare.
2394
+ * (Canonical 4.0 name; legacy `getToday` is removed.)
2395
+ */
2396
+ today: (channel?: string) => Promise<ContentItem | null>;
2397
+ /**
2398
+ * Paginated library list for `channel`. `options.limit` caps page size
2399
+ * (server default applies when omitted); `options.cursor` is the
2400
+ * opaque token returned by a prior response's `next_cursor`.
2401
+ * (Canonical 4.0 name; legacy `getLibrary` is removed.)
2402
+ */
2403
+ library: (channel?: string, options?: {
2404
+ limit?: number;
2405
+ cursor?: string;
2406
+ }) => Promise<ContentItem[]>;
2407
+ item: (id: string) => Promise<ContentItem>;
2408
+ updateItem: (id: string, state: unknown) => Promise<ContentItem>;
2409
+ createItem: (channel: string, item: unknown) => Promise<ContentItem>;
2410
+ };
2411
+ /** Server-managed deep link records. */
2412
+ deepLinks: {
2413
+ get: (shortCode: string) => Promise<DeepLink>;
2414
+ create: (params: DeepLinkCreate) => Promise<DeepLink>;
2415
+ };
2416
+ /** Step-by-step user onboarding flow. */
2417
+ onboarding: {
2418
+ getStatus: () => Promise<OnboardingStatus>;
2419
+ nextStep: (payload: unknown) => Promise<OnboardingStatus>;
2420
+ skipStep: () => Promise<OnboardingStatus>;
2421
+ complete: () => Promise<OnboardingStatus>;
2422
+ };
2423
+ };
2424
+ /** Escape hatch — the raw SDK runtime handle, for namespaces not yet wrapped. */
2425
+ declare function getCoreWasm(): AmbaCoreWasm;
2426
+ /**
2427
+ * Package version stamped at build time. Sent in the
2428
+ * `sdk_wrapper_version` header on every customer request.
2429
+ *
2430
+ * **Must match `package.json#version` exactly** — the
2431
+ * `verify-sdk-version-coherence` GH action gates every publish on the
2432
+ * pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
2433
+ */
2434
+ declare const SDK_VERSION = "4.0.7";
2435
+ //#endregion
2436
+ 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 };
2437
+ //# sourceMappingURL=index.browser.d.ts.map