foundation-sdk 0.1.14 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,89 +1,194 @@
1
1
  # foundation-sdk
2
2
 
3
- SDK for building applications that embed within the Foundation container.
3
+ SDK for building applications on the Foundation platform. Works in two modes:
4
4
 
5
- > **Future Migration:** This package will be renamed to `@foundation/iframe-sdk` when the npm organization is set up.
5
+ - **Iframe mode** app embeds inside the Foundation container, communicates via postMessage
6
+ - **Standalone mode** — app runs independently, communicates directly with the API via HTTP
7
+
8
+ Same API surface in both modes. Your code doesn't change.
6
9
 
7
10
  ## Installation
8
11
 
12
+ ### npm / yarn / pnpm
13
+
9
14
  ```bash
10
15
  npm install foundation-sdk
11
16
  ```
12
17
 
13
- Vue is an optional peer dependency - only required if using the `/vue` entry point:
18
+ Vue is an optional peer dependency only needed if using the `/vue` entry point:
14
19
 
15
20
  ```bash
16
21
  npm install foundation-sdk vue
17
22
  ```
18
23
 
24
+ ### CDN / Script Tag
25
+
26
+ For apps that don't use a bundler, load the SDK directly via a script tag. This exposes `window.FoundationSDK`.
27
+
28
+ ```html
29
+ <script src="https://unpkg.com/foundation-sdk/dist/foundation-sdk.browser.js"></script>
30
+ <script>
31
+ const sdk = FoundationSDK.createFoundationClient()
32
+
33
+ sdk.ready.then(function() {
34
+ console.log('SDK ready, user:', sdk.auth.user)
35
+ })
36
+ </script>
37
+ ```
38
+
19
39
  ## Quick Start
20
40
 
21
- ### Vanilla TypeScript/JavaScript
41
+ ### Iframe Mode (embedded in container)
22
42
 
23
43
  ```typescript
24
44
  import { createFoundationClient } from 'foundation-sdk'
25
45
 
26
46
  const sdk = createFoundationClient()
27
-
28
- // Wait for SDK to be ready (receives initial state from container)
29
47
  await sdk.ready
30
48
 
31
- // Access current user
32
- console.log(sdk.auth.user)
33
-
34
- // Fetch data
35
49
  const { items } = await sdk.db.list('projects', { limit: 10 })
36
-
37
- // Show a toast notification
38
50
  await sdk.ui.toast('Hello from embedded app!', 'success')
39
51
  ```
40
52
 
53
+ ### Standalone Mode (your own app)
54
+
55
+ ```typescript
56
+ import { createFoundationClient } from 'foundation-sdk'
57
+ import { createAuth0Client } from '@auth0/auth0-spa-js'
58
+
59
+ // Set up your auth provider
60
+ const auth0 = await createAuth0Client({
61
+ domain: 'your-domain.auth0.com',
62
+ clientId: 'your-client-id',
63
+ authorizationParams: { audience: 'your-api' }
64
+ })
65
+
66
+ // Create the SDK with your config
67
+ const sdk = createFoundationClient({
68
+ configUrl: 'https://api.your-app.com',
69
+ tenantId: 'your-tenant-id',
70
+ appId: 'your-app-id',
71
+ auth: auth0 // Auth0 client satisfies MinimalAuthClient
72
+ })
73
+
74
+ await sdk.ready
75
+
76
+ const { items } = await sdk.db.list('projects', { limit: 10 })
77
+ ```
78
+
79
+ You own auth completely — provider choice, login pages, token refresh. The SDK just needs an auth client that can provide tokens.
80
+
41
81
  ### Vue 3
42
82
 
43
83
  ```typescript
84
+ // Iframe mode
44
85
  import { useFoundation } from 'foundation-sdk/vue'
45
-
46
- // In your component setup
47
86
  const { isReady, user, isDark, db, ui } = useFoundation()
87
+ ```
48
88
 
49
- // Reactive state is automatically updated
50
- watch(isReady, (ready) => {
51
- if (ready) {
52
- console.log('SDK ready, user:', user.value)
53
- }
89
+ ```typescript
90
+ // Standalone mode — configure once at app startup
91
+ import { configureFoundation, useFoundation } from 'foundation-sdk/vue'
92
+
93
+ configureFoundation({
94
+ configUrl: 'https://api.your-app.com',
95
+ tenantId: 'your-tenant-id',
96
+ appId: 'your-app-id',
97
+ auth: auth0Client
54
98
  })
55
99
 
56
- // Use services directly
57
- const { items } = await db.list('projects', { limit: 10 })
58
- await ui.toast('Hello!', 'success')
100
+ // Then useFoundation() in any component as normal
101
+ const { isReady, db, ui } = useFoundation()
59
102
  ```
60
103
 
104
+ ## How It Works
105
+
106
+ ### Iframe Mode
107
+
108
+ The SDK communicates with the Foundation container via `postMessage`. On initialization, a three-step handshake establishes a secure session:
109
+
110
+ 1. SDK sends `SDK_HELLO` to the container (retries with exponential backoff)
111
+ 2. Container responds with `SDK_READY` containing initial state and a session nonce
112
+ 3. SDK sends `SDK_ACK` to confirm — container flushes any buffered events
113
+
114
+ All subsequent messages include the session nonce for authentication. The container validates every message against the nonce and the iframe's origin.
115
+
116
+ ### Standalone Mode
117
+
118
+ The SDK makes HTTP calls directly to the Foundation API. On initialization:
119
+
120
+ 1. SDK calls `auth.getTokenSilently()` to get a JWT
121
+ 2. JWT is parsed to extract API URLs (`apiBaseUrl`, `accountBaseUrl`)
122
+ 3. SDK fetches config from `{configUrl}/api/v1/config/init`
123
+ 4. `sdk.ready` resolves — all services are available
124
+
125
+ Every request includes `Authorization`, `X-Foundation-Mvp-Application-Id`, and `X-Foundation-Mvp-Tenant-Id` headers. Tokens are refreshed automatically via the auth client on each request.
126
+
127
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for protocol details.
128
+
129
+ ---
130
+
61
131
  ## API Reference
62
132
 
63
133
  ### Initialization
64
134
 
65
- #### `createFoundationClient(): FoundationClient`
135
+ #### `createFoundationClient(): FoundationClient` (iframe mode)
66
136
 
67
- Creates or returns the singleton SDK client instance.
137
+ Creates or returns the singleton SDK client instance. Immediately begins the handshake with the container.
68
138
 
69
139
  ```typescript
70
140
  const sdk = createFoundationClient()
141
+ await sdk.ready
142
+ ```
71
143
 
72
- // Check if ready
73
- if (sdk.isReady) {
74
- // SDK has received initial state
75
- }
144
+ If the container does not respond after ~6 seconds of retries, `sdk.ready` rejects with an error.
76
145
 
77
- // Wait for ready
146
+ #### `createFoundationClient(options: StandaloneOptions): FoundationClient` (standalone mode)
147
+
148
+ Creates a standalone SDK client that communicates directly with the API via HTTP.
149
+
150
+ ```typescript
151
+ import { createFoundationClient } from 'foundation-sdk'
152
+ import type { StandaloneOptions, MinimalAuthClient } from 'foundation-sdk'
153
+
154
+ const sdk = createFoundationClient({
155
+ configUrl: 'https://api.your-app.com', // base URL for /api/v1/config/init
156
+ tenantId: 'your-tenant-id',
157
+ appId: 'your-app-id',
158
+ auth: yourAuthClient, // implements MinimalAuthClient
159
+ version: '1.0.0' // optional, defaults to '0.0.0'
160
+ })
78
161
  await sdk.ready
162
+ ```
79
163
 
80
- // Access available entity definitions
81
- console.log(sdk.entities) // EntityDefinition[]
164
+ The `auth` parameter must implement `MinimalAuthClient`:
165
+
166
+ ```typescript
167
+ interface MinimalAuthClient {
168
+ loginWithRedirect(options?: Record<string, unknown>): Promise<void>
169
+ logout(options?: Record<string, unknown>): void | Promise<void>
170
+ getUser(): Promise<{ id: string; email: string; name?: string; picture?: string } | undefined>
171
+ getTokenSilently(options?: Record<string, unknown>): Promise<string>
172
+ isAuthenticated(): Promise<boolean>
173
+ handleRedirectCallback(url?: string): Promise<unknown>
174
+ }
82
175
  ```
83
176
 
177
+ Auth0 (`@auth0/auth0-spa-js`) and Cognito client instances satisfy this interface natively.
178
+
179
+ **Standalone mode differences:**
180
+ - `sdk.router` — no-op (manage your own routing)
181
+ - `sdk.ui.toast` — logs to console
182
+ - `sdk.ui.confirm` — uses `window.confirm()`
183
+ - `sdk.ui.loading` / `sdk.ui.banner` — no-ops
184
+ - `sdk.theme` — manages local state (persisted to localStorage)
185
+ - `sdk.integration.connect()` — throws (requires platform UI)
186
+ - `sdk.log` — outputs to console
187
+ - `sdk.onEntityChange` — does not receive WebSocket push events
188
+
84
189
  #### `resetFoundationClient(): void`
85
190
 
86
- Resets the singleton instance. Useful for testing or re-initialization.
191
+ Resets the singleton instance, cleans up transport and all state. Useful for testing or re-initialization.
87
192
 
88
193
  ```typescript
89
194
  import { resetFoundationClient } from 'foundation-sdk'
@@ -97,17 +202,15 @@ resetFoundationClient()
97
202
 
98
203
  ```typescript
99
204
  interface AuthService {
100
- readonly user: User | null // Current user
101
- readonly isAuthenticated: boolean // Is user logged in
102
- getToken(): Promise<string> // Get auth token
103
- login(): Promise<void> // Trigger login flow
104
- logout(): Promise<void> // Trigger logout
105
- onChange(callback): () => void // Subscribe to auth changes
205
+ readonly user: User | null
206
+ readonly isAuthenticated: boolean
207
+ getToken(): Promise<string>
208
+ login(): Promise<void>
209
+ logout(): Promise<void>
210
+ onChange(callback: (user: User | null) => void): () => void
106
211
  }
107
212
  ```
108
213
 
109
- **Example:**
110
-
111
214
  ```typescript
112
215
  // Get current user
113
216
  const user = sdk.auth.user
@@ -126,12 +229,15 @@ const unsubscribe = sdk.auth.onChange((user) => {
126
229
 
127
230
  ### Database (`sdk.db`)
128
231
 
232
+ All database operations are scoped to entities defined in the app configuration. The container rejects requests for entities or methods not in the app's entity definitions.
233
+
129
234
  ```typescript
130
235
  interface DbService {
131
236
  list<T>(entity: string, options?: ListOptions): Promise<{ items: T[]; nextCursor?: string }>
132
237
  get<T>(entity: string, id: string): Promise<T>
133
238
  create<T>(entity: string, data: Partial<T>): Promise<T>
134
239
  update<T>(entity: string, id: string, updates: Partial<T>): Promise<T>
240
+ save<T>(entity: string, data: Partial<T>): Promise<T>
135
241
  delete(entity: string, id: string): Promise<void>
136
242
  }
137
243
 
@@ -144,8 +250,6 @@ interface ListOptions {
144
250
  }
145
251
  ```
146
252
 
147
- **Example:**
148
-
149
253
  ```typescript
150
254
  // List with filters and pagination
151
255
  const { items, nextCursor } = await sdk.db.list('projects', {
@@ -169,6 +273,9 @@ const updated = await sdk.db.update('projects', 'project-123', {
169
273
  name: 'Updated Name'
170
274
  })
171
275
 
276
+ // Save (create or update — tries update first, falls back to create)
277
+ await sdk.db.save('projects', { key: 'settings', value: '...' })
278
+
172
279
  // Delete
173
280
  await sdk.db.delete('projects', 'project-123')
174
281
  ```
@@ -192,8 +299,6 @@ interface UIService {
192
299
  }
193
300
  ```
194
301
 
195
- **Example:**
196
-
197
302
  ```typescript
198
303
  // Toast notifications
199
304
  await sdk.ui.toast('Operation successful', 'success')
@@ -206,19 +311,18 @@ if (confirmed) {
206
311
  }
207
312
 
208
313
  // Loading overlay
209
- sdk.ui.loading.show('Processing...')
314
+ await sdk.ui.loading.show('Processing...')
210
315
  try {
211
316
  await doSomething()
212
317
  } finally {
213
- sdk.ui.loading.hide()
318
+ await sdk.ui.loading.hide()
214
319
  }
215
320
 
216
- // Banners
321
+ // Banners (persistent messages at the top of the container)
217
322
  const { bannerId } = await sdk.ui.banner.show('New feature available!', {
218
323
  type: 'info',
219
324
  dismissible: true
220
325
  })
221
- // Later...
222
326
  await sdk.ui.banner.hide(bannerId)
223
327
  ```
224
328
 
@@ -226,20 +330,20 @@ await sdk.ui.banner.hide(bannerId)
226
330
 
227
331
  ### Router (`sdk.router`)
228
332
 
333
+ The container owns the URL bar. Your app reads and writes the route through the SDK, which proxies to the container's Vue Router.
334
+
229
335
  ```typescript
230
336
  interface RouterService {
231
337
  readonly path: string
232
338
  readonly query: Record<string, string>
233
339
  readonly hash: string
234
- push(path: string, options?: NavOptions): Promise<void>
235
- replace(path: string, options?: NavOptions): Promise<void>
340
+ push(path: string, options?: { query?: Record<string, string>; hash?: string }): Promise<void>
341
+ replace(path: string, options?: { query?: Record<string, string>; hash?: string }): Promise<void>
236
342
  setQuery(params: Record<string, string>, options?: { replace?: boolean }): Promise<void>
237
343
  onChange(callback: (route: RouterState) => void): () => void
238
344
  }
239
345
  ```
240
346
 
241
- **Example:**
242
-
243
347
  ```typescript
244
348
  // Navigate
245
349
  await sdk.router.push('/dashboard')
@@ -248,7 +352,7 @@ await sdk.router.push('/projects', { query: { filter: 'active' } })
248
352
  // Replace (no history entry)
249
353
  await sdk.router.replace('/login')
250
354
 
251
- // Update query params
355
+ // Update query params (null removes a param)
252
356
  await sdk.router.setQuery({ page: '2', sort: 'name' })
253
357
 
254
358
  // Subscribe to route changes
@@ -271,22 +375,14 @@ interface ThemeService {
271
375
  }
272
376
  ```
273
377
 
274
- **Example:**
275
-
276
378
  ```typescript
277
- // Check current theme
278
379
  if (sdk.theme.isDark) {
279
380
  // Apply dark styles
280
381
  }
281
382
 
282
- // Set specific theme mode
283
383
  await sdk.theme.setTheme('dark')
284
- await sdk.theme.setTheme('system')
285
-
286
- // Toggle between light/dark
287
384
  await sdk.theme.toggle()
288
385
 
289
- // Subscribe to theme changes
290
386
  const unsubscribe = sdk.theme.onChange((theme) => {
291
387
  document.body.classList.toggle('dark', theme.isDark)
292
388
  })
@@ -294,100 +390,168 @@ const unsubscribe = sdk.theme.onChange((theme) => {
294
390
 
295
391
  ---
296
392
 
297
- ### Storage (`sdk.storage`)
393
+ ### Files (`sdk.files`)
394
+
395
+ Two approaches for file uploads: `upload()` sends the file through the container (simpler, handles CORS), `initiate()` returns a presigned URL for direct-to-S3 upload (better for large files).
298
396
 
299
397
  ```typescript
300
- interface StorageService {
301
- upload(options: UploadOptions): Promise<{ id: string; url: string; name: string }>
398
+ interface FilesService {
399
+ upload(options: {
400
+ name: string
401
+ contentType: string
402
+ file: ArrayBuffer | Blob | File
403
+ sha256: string
404
+ }): Promise<{ id: string; name: string; status: string; s3UploadComplete: boolean }>
405
+
406
+ initiate(options: {
407
+ name: string
408
+ contentType: string
409
+ contentLength: number
410
+ sha256: string
411
+ metadata?: Record<string, unknown>
412
+ }): Promise<{ id: string; name: string; signedUrl: string; signedData: Record<string, string> }>
413
+
302
414
  get(fileId: string): Promise<FileMetadata>
303
415
  delete(fileId: string): Promise<void>
304
- list(options?: ListOptions): Promise<{ items: FileMetadata[]; nextCursor?: string }>
416
+ list(options?: { limit?: number; cursor?: string }): Promise<{ items: FileMetadata[]; nextCursor?: string }>
305
417
  }
306
418
  ```
307
419
 
308
- **Example:**
309
-
310
420
  ```typescript
311
- // Upload a file (base64 encoded)
312
- const file = await sdk.storage.upload({
313
- name: 'document.pdf',
314
- type: 'application/pdf',
315
- size: fileBuffer.byteLength,
316
- data: btoa(String.fromCharCode(...new Uint8Array(fileBuffer))),
317
- folder: 'documents'
421
+ // Simple upload (file goes through the container)
422
+ const result = await sdk.files.upload({
423
+ name: 'report.pdf',
424
+ contentType: 'application/pdf',
425
+ file: fileInput.files[0],
426
+ sha256: await computeSha256(fileInput.files[0])
318
427
  })
319
428
 
320
- console.log(file.url) // URL to access the file
429
+ // Presigned upload (direct to S3, better for large files)
430
+ const { signedUrl, signedData } = await sdk.files.initiate({
431
+ name: 'large-video.mp4',
432
+ contentType: 'video/mp4',
433
+ contentLength: file.size,
434
+ sha256: await computeSha256(file)
435
+ })
321
436
 
322
- // List files
323
- const { items } = await sdk.storage.list({ folder: 'documents' })
437
+ const formData = new FormData()
438
+ Object.entries(signedData).forEach(([key, value]) => formData.append(key, value))
439
+ formData.append('file', file)
440
+ await fetch(signedUrl, { method: 'POST', body: formData })
441
+
442
+ // List and manage files
443
+ const { items } = await sdk.files.list({ limit: 20 })
444
+ const metadata = await sdk.files.get('file-123')
445
+ await sdk.files.delete('file-123')
324
446
  ```
325
447
 
326
448
  ---
327
449
 
328
- ### Files (`sdk.files`)
450
+ ### Account (`sdk.account`)
329
451
 
330
- For large file uploads using presigned URLs:
452
+ Manage the current user's account.
331
453
 
332
454
  ```typescript
333
- interface FilesService {
334
- initiate(options: InitiateOptions): Promise<{
335
- id: string
336
- name: string
337
- signedUrl: string
338
- signedData: Record<string, string>
339
- }>
340
- get(fileId: string): Promise<FileMetadata>
341
- delete(fileId: string): Promise<void>
342
- list(options?: ListOptions): Promise<{ items: FileMetadata[]; nextCursor?: string }>
455
+ interface AccountService {
456
+ get(): Promise<{ user: User; account: Record<string, unknown> }>
457
+ update(data: Record<string, unknown>): Promise<void>
458
+ usage(): Promise<Record<string, unknown>>
459
+ resendVerification(): Promise<void>
343
460
  }
344
461
  ```
345
462
 
346
- **Example:**
463
+ ```typescript
464
+ // Get account details
465
+ const { user, account } = await sdk.account.get()
466
+
467
+ // Update profile
468
+ await sdk.account.update({ name: 'New Name' })
469
+
470
+ // Check usage/limits
471
+ const usage = await sdk.account.usage()
472
+
473
+ // Resend email verification
474
+ await sdk.account.resendVerification()
475
+ ```
476
+
477
+ ---
478
+
479
+ ### Integrations (`sdk.integration`)
480
+
481
+ Connect to third-party services. The `connect()` method opens the container's integration modal for OAuth flows.
347
482
 
348
483
  ```typescript
349
- // Initiate upload to get presigned URL
350
- const { signedUrl, signedData, id } = await sdk.files.initiate({
351
- name: 'large-video.mp4',
352
- contentType: 'video/mp4',
353
- contentLength: file.size
354
- })
484
+ interface IntegrationService {
485
+ list(): Promise<Integration[]>
486
+ connections(): Promise<IntegrationConfiguration[]>
487
+ status(source: string): Promise<{ connected: boolean; connections: unknown[] }>
488
+ connect(source: string): Promise<{ success: boolean; configuration?: unknown; message?: string }>
489
+ disconnect(source: string, configurationId: string): Promise<void>
490
+ }
491
+ ```
355
492
 
356
- // Upload directly to storage
357
- const formData = new FormData()
358
- Object.entries(signedData).forEach(([key, value]) => {
359
- formData.append(key, value)
360
- })
361
- formData.append('file', file)
493
+ ```typescript
494
+ // List available integrations
495
+ const integrations = await sdk.integration.list()
362
496
 
363
- await fetch(signedUrl, { method: 'POST', body: formData })
497
+ // Check connection status
498
+ const { connected, connections } = await sdk.integration.status('google-calendar')
499
+
500
+ // Connect (opens OAuth modal in container)
501
+ const result = await sdk.integration.connect('google-calendar')
502
+ if (result.success) {
503
+ console.log('Connected!', result.configuration)
504
+ }
505
+
506
+ // List all active connections
507
+ const allConnections = await sdk.integration.connections()
508
+
509
+ // Disconnect
510
+ await sdk.integration.disconnect('google-calendar', connectionId)
511
+ ```
512
+
513
+ ---
514
+
515
+ ### OpenAPI (`sdk.openapi`)
516
+
517
+ Fetch the app's OpenAPI specification.
518
+
519
+ ```typescript
520
+ interface OpenApiService {
521
+ get(): Promise<Record<string, unknown>>
522
+ }
523
+ ```
524
+
525
+ ```typescript
526
+ const spec = await sdk.openapi.get()
364
527
  ```
365
528
 
366
529
  ---
367
530
 
368
531
  ### Logging (`sdk.log`)
369
532
 
533
+ Log messages and analytics events through the container's logging infrastructure.
534
+
370
535
  ```typescript
371
536
  interface LogService {
372
- info(message: string, data?: Record<string, unknown>): void
373
- warn(message: string, data?: Record<string, unknown>): void
374
- error(message: string, data?: Record<string, unknown>): void
375
- event(name: string, properties?: Record<string, unknown>): void
537
+ info(message: string, context?: Record<string, unknown>): void
538
+ warn(message: string, context?: Record<string, unknown>): void
539
+ error(message: string, context?: Record<string, unknown>): void
540
+ event(event: string, data?: Record<string, unknown>): void
376
541
  }
377
542
  ```
378
543
 
379
- **Example:**
380
-
381
544
  ```typescript
382
- // Log messages
383
545
  sdk.log.info('User completed onboarding', { userId: user.id })
384
546
  sdk.log.warn('API rate limit approaching', { remaining: 10 })
385
547
  sdk.log.error('Failed to save', { error: err.message })
386
548
 
387
- // Track analytics events
549
+ // Analytics events
388
550
  sdk.log.event('button_clicked', { buttonId: 'submit', page: 'checkout' })
389
551
  ```
390
552
 
553
+ Log methods are fire-and-forget — they don't return promises and won't throw.
554
+
391
555
  ---
392
556
 
393
557
  ### Config (`sdk.config`)
@@ -400,18 +564,13 @@ interface ConfigService {
400
564
  }
401
565
  ```
402
566
 
403
- **Example:**
404
-
405
567
  ```typescript
406
- // Check feature flags
407
568
  if (sdk.config.features.newDashboard) {
408
569
  // Show new dashboard
409
570
  }
410
571
 
411
- // Get app info
412
572
  console.log(sdk.config.app.environment) // 'production' | 'staging' | 'development'
413
573
 
414
- // Get specific config value
415
574
  const apiUrl = await sdk.config.get('apiBaseUrl')
416
575
  ```
417
576
 
@@ -419,16 +578,14 @@ const apiUrl = await sdk.config.get('apiBaseUrl')
419
578
 
420
579
  ### Entity Changes
421
580
 
422
- Subscribe to real-time entity changes for cache invalidation:
581
+ Subscribe to real-time entity changes pushed from the container via WebSocket. Useful for cache invalidation.
423
582
 
424
583
  ```typescript
425
584
  const unsubscribe = sdk.onEntityChange((event) => {
426
- console.log('Entity changed:', event)
427
- // event.changeType: 'entity.created' | 'entity.updated' | 'entity.deleted'
428
- // event.entityId: 'projects'
429
- // event.id: 'project-123'
585
+ // event.changeType: 'entity.created' | 'entity.modified' | 'entity.deleted'
586
+ // event.entityId: 'projects' (entity type)
587
+ // event.id: 'project-123' (instance id)
430
588
 
431
- // Invalidate your cache
432
589
  if (event.entityId === 'projects') {
433
590
  refetchProjects()
434
591
  }
@@ -439,15 +596,17 @@ const unsubscribe = sdk.onEntityChange((event) => {
439
596
 
440
597
  ## Vue Integration
441
598
 
442
- The SDK provides a Vue 3 composable for reactive state management:
443
-
444
599
  ```typescript
445
600
  import { useFoundation } from 'foundation-sdk/vue'
601
+
602
+ // For standalone mode, call configureFoundation once at app startup:
603
+ import { configureFoundation } from 'foundation-sdk/vue'
604
+ configureFoundation({ configUrl: '...', tenantId: '...', appId: '...', auth: yourAuthClient })
446
605
  ```
447
606
 
448
607
  ### `useFoundation()`
449
608
 
450
- Returns reactive state and direct service access:
609
+ Returns reactive state and direct service access. Entity change subscriptions are automatically cleaned up when the component unmounts. Works identically in both iframe and standalone modes.
451
610
 
452
611
  ```typescript
453
612
  interface UseFoundationReturn {
@@ -473,43 +632,37 @@ interface UseFoundationReturn {
473
632
  router: RouterService
474
633
  config: ConfigService
475
634
  files: FilesService
476
- storage: StorageService
477
635
  log: LogService
478
636
  theme: ThemeService
637
+ account: AccountService
638
+ integration: IntegrationService
639
+ openapi: OpenApiService
479
640
 
480
- // Theme control shortcuts
641
+ // Shortcuts
481
642
  setTheme(mode: 'light' | 'dark' | 'system'): Promise<void>
482
643
  toggleTheme(): Promise<void>
483
-
484
- // Entity change subscription
485
644
  onEntityChange(callback: (event: EntityChangeEvent) => void): () => void
486
645
  }
487
646
  ```
488
647
 
489
- **Example:**
490
-
491
648
  ```vue
492
649
  <script setup lang="ts">
493
650
  import { useFoundation } from 'foundation-sdk/vue'
494
651
  import { watch } from 'vue'
495
652
 
496
- const { isReady, user, isDark, db, ui, toggleTheme } = useFoundation()
653
+ const { isReady, user, isDark, db, ui, toggleTheme, onEntityChange } = useFoundation()
497
654
 
498
- // Reactive state updates automatically
499
655
  watch(user, (newUser) => {
500
656
  console.log('User changed:', newUser?.email)
501
657
  })
502
658
 
503
- // Theme reactivity
504
- watch(isDark, (dark) => {
505
- document.body.classList.toggle('dark-mode', dark)
659
+ // Auto-cleaned on component unmount
660
+ onEntityChange((event) => {
661
+ if (event.entityId === 'tasks') {
662
+ refetchTasks()
663
+ }
506
664
  })
507
665
 
508
- async function loadProjects() {
509
- const { items } = await db.list('projects', { limit: 10 })
510
- return items
511
- }
512
-
513
666
  async function handleClick() {
514
667
  await toggleTheme()
515
668
  await ui.toast('Theme toggled!', 'success')
@@ -525,6 +678,18 @@ async function handleClick() {
525
678
  </template>
526
679
  ```
527
680
 
681
+ ### `resetFoundation()`
682
+
683
+ Resets the Vue composable state and cleans up all subscriptions. Call alongside `resetFoundationClient()`.
684
+
685
+ ```typescript
686
+ import { resetFoundation } from 'foundation-sdk/vue'
687
+ import { resetFoundationClient } from 'foundation-sdk'
688
+
689
+ resetFoundation()
690
+ resetFoundationClient()
691
+ ```
692
+
528
693
  ---
529
694
 
530
695
  ## TypeScript
@@ -544,11 +709,18 @@ import type {
544
709
  RouterService,
545
710
  DbService,
546
711
  UIService,
547
- StorageService,
548
712
  FilesService,
549
713
  FileMetadata,
550
714
  LogService,
551
- ConfigService
715
+ ConfigService,
716
+ AccountService,
717
+ IntegrationService,
718
+ OpenApiService,
719
+ SDKReadyPayload,
720
+ SDKMessage,
721
+ SDKResponse,
722
+ StandaloneOptions,
723
+ MinimalAuthClient
552
724
  } from 'foundation-sdk'
553
725
  ```
554
726
 
@@ -556,7 +728,7 @@ import type {
556
728
 
557
729
  ## Architecture
558
730
 
559
- See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed diagrams of how the SDK communicates with the container.
731
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for protocol details, security model, and message flow diagrams.
560
732
 
561
733
  ## License
562
734