@layers/amba 1.0.1 → 4.0.2

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,348 @@
1
+ # Infrastructure
2
+
3
+ The plumbing that sits behind every other surface: custom database tables (Collections — schema-first, per-tenant), serverless functions (run server-side code without standing up a backend), analytics (events + sessions), AI prompts (managed LLM templates, callable from the SDK with per-tenant keys), secrets, runtime configs, feature flags, third-party integrations (RevenueCat / Superwall / Resend / Stripe / etc.), media (file storage + CDN), and sites (static asset hosting at `*.app.amba.host`).
4
+
5
+ If gamification, economy, and social are the playable surface, **infrastructure is what you build a custom product on top of**. Anything that doesn't fit the canned surfaces lands here.
6
+
7
+ ## MCP tools
8
+
9
+ ### Collections (typed tables)
10
+
11
+ A collection is a schema-first table inside the project's isolated tenant database. You describe the columns, the server creates the table and any indexes. Rows are scoped to the signed-in `app_user` automatically (server-enforced auto row-level isolation) for SDK clients — admin tools bypass this.
12
+
13
+ | Tool | Purpose | Example args |
14
+ | --- | --- | --- |
15
+ | `amba_collections_create` / `amba_create_collection` | Create a typed collection. | `{ project_id, name: "todos", columns: [{ name: "title", type: "text", nullable: false }, { name: "done", type: "boolean", nullable: false, default: false }, { name: "due_at", type: "timestamptz", nullable: true }] }` |
16
+ | `amba_collections_list` / `amba_list_collections` | List collections in this project. | `{ project_id }` |
17
+ | `amba_collections_get` / `amba_get_collection` | Read one collection's schema. | `{ project_id, collection_name: "todos" }` |
18
+ | `amba_collections_alter` / `amba_alter_collection` | Add / drop columns, add / drop indexes. | `{ project_id, collection_name: "todos", add_columns: [{ name: "priority", type: "int", nullable: true }] }` |
19
+ | `amba_collections_delete` / `amba_delete_collection` | Drop the table (destructive). | `{ project_id, collection_name }` |
20
+ | `amba_admin_insert_row` | Insert a row as the developer (bypasses user-scope). Useful for seed data. | `{ project_id, collection: "todos", row: { title: "Sample todo", done: false } }` |
21
+ | `amba_admin_list_rows` | Read rows as the developer (bypasses user-scope — sees every user's rows). | `{ project_id, collection: "todos", limit: 100 }` |
22
+ | `amba_client_insert_row` | Insert as an end-user. Requires `api_key` + `session_token`. | `{ project_id, api_key, session_token, collection: "todos", row: {...} }` |
23
+ | `amba_client_list_rows` | Read as an end-user (auto-RLS-scoped). | `{ project_id, api_key, session_token, collection: "todos" }` |
24
+ | `amba_client_get_row` | Get one row by id (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |
25
+ | `amba_client_update_row` | Update one row (end-user). | `{ project_id, api_key, session_token, collection, row_id, patch: {...} }` |
26
+ | `amba_client_delete_row` | Delete one row (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |
27
+ | `amba_client_count_rows` | Count rows matching a filter (end-user). | `{ project_id, api_key, session_token, collection, filter: {...} }` |
28
+ | `amba_client_find_rows` | Filter / sort / paginate rows (end-user). | `{ project_id, api_key, session_token, collection, filter: {...}, order_by: [...], limit: 50 }` |
29
+ | `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ project_id, api_key, session_token, collection, vector_column: "embedding", query_vector: [...], k: 10 }` |
30
+
31
+ Column types: `text`, `int`, `bigint`, `float`, `boolean`, `timestamptz`, `date`, `json`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings).
32
+
33
+ ### Functions (serverless code)
34
+
35
+ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.
36
+
37
+ | Tool | Purpose | Example args |
38
+ | --- | --- | --- |
39
+ | `amba_functions_deploy` / `amba_deploy_function` | Deploy a function from source (TypeScript / JavaScript / Python). | `{ project_id, name: "send_welcome_email", runtime: "node22", source: "export default async (req) => { ... }", trigger: { type: "http" } }` |
40
+ | `amba_functions_list` / `amba_list_functions` | List functions. | `{ project_id }` |
41
+ | `amba_functions_get` / `amba_get_function` | Read function metadata. | `{ project_id, function_id }` |
42
+ | `amba_functions_get_logs` / `amba_get_function_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |
43
+ | `amba_functions_delete` / `amba_delete_function` | Delete a function. | `{ project_id, function_id }` |
44
+ | `amba_functions_schedule` / `amba_schedule_function` | Attach a cron schedule. | `{ project_id, function_id, cron: "0 9 * * *", timezone: "America/Los_Angeles" }` |
45
+ | `amba_functions_pause_schedule` / `amba_pause_function_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |
46
+ | `amba_functions_resume_schedule` / `amba_resume_function_schedule` | Resume. | `{ project_id, function_id }` |
47
+ | `amba_functions_trigger_schedule` / `amba_trigger_function_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |
48
+
49
+ ### AI prompts
50
+
51
+ Managed LLM templates: stored prompt with model + system message + variables, callable by name from the SDK. The actual LLM call is rewritten per-tenant — the customer's API keys (Anthropic / OpenAI) live in the tenant secrets, never on the device.
52
+
53
+ | Tool | Purpose | Example args |
54
+ | --- | --- | --- |
55
+ | `amba_ai_prompts_create` / `amba_create_ai_prompt` | Create a prompt template. | `{ project_id, key: "summarize", model: "claude-opus-4-5", system: "Summarize the user's text in 2 sentences.", variables: ["text"] }` |
56
+ | `amba_ai_prompts_list` / `amba_list_ai_prompts` | List prompts. | `{ project_id }` |
57
+ | `amba_ai_prompts_get` / `amba_get_ai_prompt` | Read one prompt. | `{ project_id, key }` |
58
+ | `amba_ai_prompts_update` / `amba_update_ai_prompt` | Edit a prompt (model swap, system message change). | `{ project_id, key, system: "..." }` |
59
+ | `amba_ai_prompts_invoke` / `amba_invoke_ai_prompt` | Invoke a prompt server-side (no client involvement — admin testing). | `{ project_id, key, variables: { text: "..." } }` |
60
+ | `amba_ai_prompts_delete` / `amba_delete_ai_prompt` | Delete. | `{ project_id, key }` |
61
+
62
+ ### Analytics + events + sessions
63
+
64
+ | Tool | Purpose | Example args |
65
+ | --- | --- | --- |
66
+ | `amba_analytics_get` / `amba_get_analytics` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: "7d" }` |
67
+ | `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: "2026-05-19T00:00:00Z" }` |
68
+ | `amba_events_count` | Count events matching a filter. | `{ project_id, event: "workout_completed", since: "2026-05-19T00:00:00Z" }` |
69
+ | `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |
70
+ | `amba_sessions_analytics` | Session-level metrics (avg duration, screens/session). | `{ project_id, period: "7d" }` |
71
+ | `amba_users_list_events` / `amba_users_events_export` | Per-user event history / export to CSV. | `{ project_id, user_id }` |
72
+ | `amba_users_export` | Export the full user list (CSV / JSON). | `{ project_id, format: "csv" }` |
73
+
74
+ ### Secrets + configs + integrations
75
+
76
+ | Tool | Purpose | Example args |
77
+ | --- | --- | --- |
78
+ | `amba_secrets_set` / `amba_set_secret` | Set a tenant secret (encrypted at rest). Use for third-party API keys called from functions. | `{ project_id, name: "OPENAI_API_KEY", value: "sk-..." }` |
79
+ | `amba_secrets_get` / `amba_get_secret` | Read a secret (returns `"<redacted>"` unless explicitly requested). | `{ project_id, name }` |
80
+ | `amba_secrets_list` / `amba_list_secrets` | List secret names. | `{ project_id }` |
81
+ | `amba_secrets_delete` / `amba_delete_secret` | Delete. | `{ project_id, name }` |
82
+ | `amba_configs_create` / `amba_create_config` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: "primary_color", value: "#ff0066", segment_id: null }` |
83
+ | `amba_configs_list` / `amba_list_configs` | List configs. | `{ project_id }` |
84
+ | `amba_configs_update` / `amba_update_config` | Edit. | `{ project_id, config_id, value: "..." }` |
85
+ | `amba_configs_delete` / `amba_delete_config` | Delete. | `{ project_id, config_id }` |
86
+ | `amba_integrations_list` | List third-party integrations configured for this project. | `{ project_id }` |
87
+ | `amba_integrations_configure` / `amba_configure_integration` | Configure a provider. | `{ project_id, provider: "revenuecat", config: { webhook_secret: "...", default_offering: "..." } }` |
88
+ | `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |
89
+ | `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: "..." } }` |
90
+ | `amba_integrations_test` / `amba_test_integration` | Send a test event to a configured provider. | `{ project_id, provider }` |
91
+
92
+ ### Media (file storage + CDN)
93
+
94
+ | Tool | Purpose | Example args |
95
+ | --- | --- | --- |
96
+ | `amba_media_upload` / `amba_upload_media` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: "logo.png", content_type: "image/png", data: "<base64>" }` |
97
+ | `amba_media_list` / `amba_list_media` | List files. | `{ project_id, folder: "/", limit: 100 }` |
98
+ | `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |
99
+ | `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: "/uploads/avatars" }` |
100
+ | `amba_media_list_folders` | List folders. | `{ project_id }` |
101
+ | `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |
102
+
103
+ ### Sites (static asset hosting)
104
+
105
+ | Tool | Purpose | Example args |
106
+ | --- | --- | --- |
107
+ | `amba_sites_deploy` / `amba_deploy_site` | Deploy a static site bundle (zip / tar). | `{ project_id, name: "marketing", bundle: "<base64>", index: "index.html" }` |
108
+ | `amba_sites_list` / `amba_list_sites` | List sites. | `{ project_id }` |
109
+ | `amba_sites_get` / `amba_get_site` | Read a site. | `{ project_id, site_id }` |
110
+ | `amba_sites_add_domain` / `amba_add_site_domain` | Attach a custom domain. | `{ project_id, site_id, domain: "marketing.example.com" }` |
111
+ | `amba_sites_list_domains` / `amba_list_site_domains` | List domains on a site. | `{ project_id, site_id }` |
112
+ | `amba_sites_remove_domain` / `amba_remove_site_domain` | Detach a domain. | `{ project_id, site_id, domain }` |
113
+ | `amba_sites_delete` / `amba_delete_site` | Delete a site. | `{ project_id, site_id }` |
114
+
115
+ ## SDK init per stack
116
+
117
+ `Amba.configure(...)` runs first. The infrastructure surfaces — collections, AI, config, flags, events — are SDK-side reads; the snippets below show what the client calls look like.
118
+
119
+ ### Expo
120
+
121
+ ```tsx
122
+ import { Amba } from '@layers/amba-expo';
123
+
124
+ // Collections — typed table, user-scoped reads + writes
125
+ type Todo = { id: string; title: string; done: boolean; created_at: string };
126
+
127
+ const { data: todos } = await Amba.collections.find<Todo>('todos', {
128
+ filter: Amba.collections.where.eq('done', false),
129
+ order: [{ column: 'created_at', direction: 'desc' }],
130
+ limit: 50,
131
+ });
132
+
133
+ const newTodo = await Amba.collections.insert('todos', {
134
+ title: 'Ship the app',
135
+ done: false,
136
+ });
137
+
138
+ await Amba.collections.update('todos', newTodo.id, { done: true });
139
+ await Amba.collections.delete('todos', newTodo.id);
140
+
141
+ // AI — call a managed prompt
142
+ const response = await Amba.ai.anthropic.messages.create({
143
+ prompt_key: 'summarize',
144
+ variables: { text: 'A long article about backend services …' },
145
+ });
146
+ // response.content — the model's reply
147
+
148
+ // Track an analytics event (also drives XP rules / achievements / etc.)
149
+ await Amba.events.track('button_clicked', { button: 'cta' });
150
+
151
+ // Read runtime config
152
+ const config = await Amba.config.fetch();
153
+ // config.values.primary_color — '#ff0066'
154
+
155
+ // Read a feature flag
156
+ const showBeta = await Amba.flags.get('beta_feature');
157
+ // or load them all at app start
158
+ const allFlags = await Amba.flags.fetch();
159
+
160
+ // Diagnostics — wire-verify
161
+ const ping = await Amba.diagnostics.ping();
162
+ if (!ping.ok) {
163
+ console.error('Amba misconfigured:', ping);
164
+ }
165
+ ```
166
+
167
+ ### React Native (bare)
168
+
169
+ ```tsx
170
+ import { Amba } from '@layers/amba-react-native';
171
+
172
+ const { data: todos } = await Amba.collections.find('todos', { limit: 50 });
173
+ await Amba.collections.insert('todos', { title: 'hi', done: false });
174
+ const config = await Amba.config.fetch();
175
+ const showBeta = await Amba.flags.get('beta_feature');
176
+ await Amba.events.track('app_opened');
177
+ ```
178
+
179
+ ### Web
180
+
181
+ ```ts
182
+ import { Amba } from '@layers/amba-web';
183
+
184
+ const { data: todos } = await Amba.collections.find('todos', {
185
+ filter: Amba.collections.where.eq('done', false),
186
+ limit: 50,
187
+ });
188
+
189
+ await Amba.collections.insert('todos', { title: 'Ship', done: false });
190
+ await Amba.events.track('page_view', { path: location.pathname });
191
+ ```
192
+
193
+ With `@layers/amba-react`:
194
+
195
+ ```tsx
196
+ import { useCollection, useFlag } from '@layers/amba-react';
197
+
198
+ function TodoList() {
199
+ const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');
200
+ const showArchive = useFlag('archive_todos');
201
+ if (loading) return <Spinner />;
202
+ return (
203
+ <ul>
204
+ {todos?.map(t => <li key={t.id}>{t.title}</li>)}
205
+ {showArchive && <ArchiveButton onArchive={refetch} />}
206
+ </ul>
207
+ );
208
+ }
209
+ ```
210
+
211
+ ### iOS (Swift)
212
+
213
+ ```swift
214
+ import Amba
215
+
216
+ struct Todo: Codable {
217
+ let id: String
218
+ let title: String
219
+ let done: Bool
220
+ }
221
+
222
+ let response = try await Amba.collections.find("todos", as: Todo.self)
223
+ let todos = response.data
224
+
225
+ _ = try await Amba.collections.insert("todos", row: ["title": "Ship", "done": false])
226
+
227
+ let config = try await Amba.config.fetch()
228
+ let showBeta = try await Amba.flags.get(name: "beta_feature")
229
+ try await Amba.events.track("app_opened", properties: ["source": "deep_link"])
230
+
231
+ // AI
232
+ let reply = try await Amba.ai.anthropic.messages.create(
233
+ promptKey: "summarize",
234
+ variables: ["text": "A long article..."]
235
+ )
236
+ ```
237
+
238
+ ### Android (Kotlin)
239
+
240
+ ```kotlin
241
+ data class Todo(val id: String, val title: String, val done: Boolean)
242
+
243
+ val todos = Amba.collections.find<Todo>("todos")
244
+ Amba.collections.insert("todos", mapOf("title" to "Ship", "done" to false))
245
+
246
+ val config = Amba.config.fetch()
247
+ val showBeta = Amba.flags.get("beta_feature")
248
+ Amba.events.track("app_opened", mapOf("source" to "deep_link"))
249
+
250
+ val reply = Amba.ai.anthropic.messages.create(
251
+ promptKey = "summarize",
252
+ variables = mapOf("text" to "A long article…")
253
+ )
254
+ ```
255
+
256
+ ### Flutter
257
+
258
+ ```dart
259
+ import 'package:amba/amba.dart';
260
+
261
+ final response = await Amba.collections.find('todos', limit: 50);
262
+ final todos = response.data;
263
+
264
+ await Amba.collections.insert('todos', {'title': 'Ship', 'done': false});
265
+
266
+ final config = await Amba.config.fetch();
267
+ final showBeta = await Amba.flags.get('beta_feature');
268
+ await Amba.events.track('app_opened', {'source': 'deep_link'});
269
+
270
+ final reply = await Amba.ai.anthropic.messages.create(
271
+ promptKey: 'summarize',
272
+ variables: {'text': 'A long article…'},
273
+ );
274
+ ```
275
+
276
+ ## Common follow-ups
277
+
278
+ Batch.
279
+
280
+ 1. **Custom data tables (collections):** any domain-specific tables to create?
281
+ - Yes — I'll list them. (Then for each: name + columns + types.)
282
+ - No, just use the canned Amba surfaces (auth, push, gamification, etc.)
283
+ - Auto-create from the existing code's models — read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.
284
+
285
+ 2. **Custom backend logic (functions):** any server-side code to deploy?
286
+ - Yes — describe what it should do. (Then offer to scaffold a function template and deploy.)
287
+ - No
288
+
289
+ 3. **AI features:** want managed LLM prompts?
290
+ - Yes — what's the use case? (summarize, translate, classify, generate, custom)
291
+ - No
292
+
293
+ 4. **Analytics:** which tracker do you want?
294
+ - Only Amba's built-in events (recommended — already wired)
295
+ - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)
296
+ - None — disable event tracking entirely (rarely useful — events also drive XP / achievements / streaks; disabling cripples gamification)
297
+
298
+ 5. **Third-party integrations to set up:**
299
+ - [ ] RevenueCat (IAP / subscriptions on iOS + Android)
300
+ - [ ] Superwall (paywall A/B)
301
+ - [ ] Resend (transactional email)
302
+ - [ ] Stripe (web payments / subscriptions)
303
+ - [ ] Mixpanel / PostHog / Segment (analytics forwarding)
304
+ - [ ] OpenAI / Anthropic (LLM keys — required for `Amba.ai.*` calls)
305
+
306
+ 6. **Feature flags:** seed any starter flags?
307
+ - Yes — wire `beta_feature` (off by default) so I can ship the wiring before the feature exists
308
+ - No
309
+
310
+ 7. **Static site:** want a marketing page hosted under your tenant subdomain?
311
+ - Yes — scaffold and deploy a 1-page index
312
+ - No
313
+
314
+ ## Re-run behavior
315
+
316
+ 1. `.amba/wired.json`:
317
+
318
+ ```json
319
+ {
320
+ "surfaces": {
321
+ "infrastructure": {
322
+ "collections": ["todos", "favorites"],
323
+ "functions": ["send_welcome_email"],
324
+ "ai_prompts": ["summarize"],
325
+ "integrations": ["revenuecat", "anthropic"],
326
+ "flags": ["beta_feature"],
327
+ "configs": ["primary_color", "max_uploads_per_day"]
328
+ }
329
+ }
330
+ }
331
+ ```
332
+
333
+ 2. Before creating:
334
+ - `amba_collections_list` — match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.
335
+ - `amba_functions_list` — match on `name`. Collisions: ask to redeploy (with the new source) or skip.
336
+ - `amba_ai_prompts_list` — match on `key`. Same.
337
+ - `amba_integrations_list` — match on `provider`. Same.
338
+ - `amba_configs_list` — match on `key`. Same.
339
+
340
+ 3. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** — this drops the underlying table and every row in it across every user of the tenant.
341
+
342
+ 4. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source — but log the deploy in `wired.json.surfaces.infrastructure.function_deploys` with a timestamp so the user can audit.
343
+
344
+ 5. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace) to avoid clobbering fields the user filled in via the console.
345
+
346
+ 6. Secrets: don't list secret values in chat output, even on read. Just confirm "OPENAI_API_KEY is set" / "not set" — the actual value stays in the tenant's secret store.
347
+
348
+ 7. Update `wired.json` to append.