foundation-sdk 0.1.14 → 0.2.0
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 +316 -144
- package/dist/index.cjs +2 -293
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +210 -0
- package/dist/index.d.ts +210 -3
- package/dist/index.js +2 -293
- package/dist/index.js.map +1 -1
- package/package.json +21 -30
- package/dist/client.d.ts +0 -4
- package/dist/client.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -228
- package/dist/types.d.ts.map +0 -1
- package/dist/vue.cjs +0 -91
- package/dist/vue.cjs.map +0 -1
- package/dist/vue.d.ts +0 -35
- package/dist/vue.d.ts.map +0 -1
- package/dist/vue.js +0 -91
- package/dist/vue.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,89 +1,194 @@
|
|
|
1
1
|
# foundation-sdk
|
|
2
2
|
|
|
3
|
-
SDK for building applications
|
|
3
|
+
SDK for building applications on the Foundation platform. Works in two modes:
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
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
|
-
###
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
//
|
|
57
|
-
const {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
101
|
-
readonly isAuthenticated: boolean
|
|
102
|
-
getToken(): Promise<string>
|
|
103
|
-
login(): Promise<void>
|
|
104
|
-
logout(): Promise<void>
|
|
105
|
-
onChange(callback
|
|
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?:
|
|
235
|
-
replace(path: string, options?:
|
|
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
|
-
###
|
|
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
|
|
301
|
-
upload(options:
|
|
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?:
|
|
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
|
-
//
|
|
312
|
-
const
|
|
313
|
-
name: '
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
###
|
|
450
|
+
### Account (`sdk.account`)
|
|
329
451
|
|
|
330
|
-
|
|
452
|
+
Manage the current user's account.
|
|
331
453
|
|
|
332
454
|
```typescript
|
|
333
|
-
interface
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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,
|
|
373
|
-
warn(message: string,
|
|
374
|
-
error(message: string,
|
|
375
|
-
event(
|
|
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
|
-
//
|
|
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
|
-
|
|
427
|
-
// event.
|
|
428
|
-
// event.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
731
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for protocol details, security model, and message flow diagrams.
|
|
560
732
|
|
|
561
733
|
## License
|
|
562
734
|
|