@myco-dev/sdk 0.1.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 +526 -0
- package/dist/index.d.ts +253 -0
- package/dist/index.js +512 -0
- package/dist/react.d.ts +101 -0
- package/dist/react.js +53 -0
- package/dist/server.d.ts +48 -0
- package/dist/server.js +220 -0
- package/dist/types-BOqnIVpz.d.ts +76 -0
- package/dist/types-CT1s2ZrW.d.ts +89 -0
- package/dist/types-D2J6kXGR.d.ts +78 -0
- package/dist/types-pm2LlwkU.d.ts +89 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# @myco/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for building apps on the Myco platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @myco/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer Dependencies
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react better-auth swr
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createMycoSDK } from "@myco/sdk";
|
|
21
|
+
|
|
22
|
+
// Optional: import generated types for type-safe entity access
|
|
23
|
+
import type { EntityTypes } from "./types/myco.generated";
|
|
24
|
+
|
|
25
|
+
const myco = createMycoSDK<EntityTypes>("your-app-key");
|
|
26
|
+
|
|
27
|
+
// Fetch records - automatically waits for SDK to be ready
|
|
28
|
+
const { records } = await myco.data.getRecords("property");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Client SDK
|
|
32
|
+
|
|
33
|
+
### Initialization
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { createMycoSDK } from "@myco/sdk";
|
|
37
|
+
|
|
38
|
+
const myco = createMycoSDK<EntityTypes>("your-app-key", {
|
|
39
|
+
// Optional: override the base URL
|
|
40
|
+
baseUrl: "https://api.myco.com",
|
|
41
|
+
// Optional: disable auto-redirect on unauthenticated (default: true)
|
|
42
|
+
redirectOnUnauth: false,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Optional: await ready if you need to know when initialization completes
|
|
46
|
+
// All API calls automatically wait for ready internally
|
|
47
|
+
await myco.ready;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The SDK automatically:
|
|
51
|
+
- Detects local vs production environment
|
|
52
|
+
- Handles workspace initialization via the `/api/app/join` endpoint
|
|
53
|
+
- Persists workspace ID in localStorage
|
|
54
|
+
- Redirects to login if unauthenticated (configurable)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### Auth Namespace
|
|
59
|
+
|
|
60
|
+
#### `myco.auth.login(returnTo?)`
|
|
61
|
+
|
|
62
|
+
Redirects to the federated login page.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Redirect to login, return to current page after
|
|
66
|
+
myco.auth.login();
|
|
67
|
+
|
|
68
|
+
// Redirect to login, return to specific URL after
|
|
69
|
+
myco.auth.login("/dashboard");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### `myco.auth.logout()`
|
|
73
|
+
|
|
74
|
+
Signs out the current user and clears the session.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
await myco.auth.logout();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### `myco.auth.useSession()`
|
|
81
|
+
|
|
82
|
+
React hook for session state.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
function MyComponent() {
|
|
86
|
+
const { data: session, isPending, error } = myco.auth.useSession();
|
|
87
|
+
|
|
88
|
+
if (isPending) return <div>Loading...</div>;
|
|
89
|
+
if (!session) return <div>Not logged in</div>;
|
|
90
|
+
|
|
91
|
+
return <div>Hello, {session.user.name}</div>;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### `myco.auth.getSession()`
|
|
96
|
+
|
|
97
|
+
Get session data outside of React components.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const session = await myco.auth.getSession();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Workspaces Namespace
|
|
106
|
+
|
|
107
|
+
#### `myco.workspaces.ensure()`
|
|
108
|
+
|
|
109
|
+
Ensures a workspace is set up. Called automatically during initialization.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Call manually after login if redirectOnUnauth was false
|
|
113
|
+
await myco.workspaces.ensure();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### `myco.workspaces.list()`
|
|
117
|
+
|
|
118
|
+
List all workspaces the user has access to.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const workspaces = await myco.workspaces.list();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### `myco.workspaces.current()`
|
|
125
|
+
|
|
126
|
+
Get the current workspace details.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const workspace = await myco.workspaces.current();
|
|
130
|
+
console.log(workspace.name);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `myco.workspaces.switch(workspaceId)`
|
|
134
|
+
|
|
135
|
+
Switch to a different workspace.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
myco.workspaces.switch("ws_abc123");
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### `myco.workspaces.create(name)`
|
|
142
|
+
|
|
143
|
+
Create a new workspace.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const workspace = await myco.workspaces.create("My New Workspace");
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `myco.workspaces.update(data)`
|
|
150
|
+
|
|
151
|
+
Update the current workspace.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
await myco.workspaces.update({ name: "Renamed Workspace" });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `myco.workspaces.delete()`
|
|
158
|
+
|
|
159
|
+
Delete the current workspace.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
await myco.workspaces.delete();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### `myco.workspaces.getMembers()`
|
|
166
|
+
|
|
167
|
+
Get members of the current workspace.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const members = await myco.workspaces.getMembers();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `myco.workspaces.addMember(userId, role?)`
|
|
174
|
+
|
|
175
|
+
Add a member to the current workspace.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
await myco.workspaces.addMember("user_123", "admin");
|
|
179
|
+
await myco.workspaces.addMember("user_456"); // defaults to "member"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### `myco.workspaces.removeMember(userId)`
|
|
183
|
+
|
|
184
|
+
Remove a member from the current workspace.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
await myco.workspaces.removeMember("user_123");
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Data Namespace
|
|
193
|
+
|
|
194
|
+
#### Entities
|
|
195
|
+
|
|
196
|
+
##### `myco.data.getEntities()`
|
|
197
|
+
|
|
198
|
+
Get all entities for the current app.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const entities = await myco.data.getEntities();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
##### `myco.data.getEntity(entitySlug)`
|
|
205
|
+
|
|
206
|
+
Get a single entity by slug.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const entity = await myco.data.getEntity("property");
|
|
210
|
+
console.log(entity.fields);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Records (Async Methods)
|
|
214
|
+
|
|
215
|
+
##### `myco.data.getRecords(entitySlug, options?)`
|
|
216
|
+
|
|
217
|
+
Get paginated records for an entity.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const { records, pagination } = await myco.data.getRecords("property", {
|
|
221
|
+
page: 1,
|
|
222
|
+
pageSize: 20,
|
|
223
|
+
sort: "createdAt",
|
|
224
|
+
order: "desc",
|
|
225
|
+
filter: { status: "active" },
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
##### `myco.data.getRecord(entitySlug, recordId)`
|
|
230
|
+
|
|
231
|
+
Get a single record by ID.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const record = await myco.data.getRecord("property", "rec_123");
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
##### `myco.data.createRecord(entitySlug, data)`
|
|
238
|
+
|
|
239
|
+
Create a new record.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const record = await myco.data.createRecord("property", {
|
|
243
|
+
title: "Beach House",
|
|
244
|
+
price: 500000,
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
##### `myco.data.updateRecord(entitySlug, recordId, data)`
|
|
249
|
+
|
|
250
|
+
Update an existing record.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const record = await myco.data.updateRecord("property", "rec_123", {
|
|
254
|
+
price: 550000,
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
##### `myco.data.deleteRecord(entitySlug, recordId)`
|
|
259
|
+
|
|
260
|
+
Delete a record.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
await myco.data.deleteRecord("property", "rec_123");
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
##### `myco.data.batchGetRecords(entitySlug, ids)`
|
|
267
|
+
|
|
268
|
+
Batch get multiple records by IDs.
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const records = await myco.data.batchGetRecords("property", [
|
|
272
|
+
"rec_123",
|
|
273
|
+
"rec_456",
|
|
274
|
+
]);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Records (React Hooks)
|
|
278
|
+
|
|
279
|
+
##### `myco.data.useRecords(entitySlug, options?)`
|
|
280
|
+
|
|
281
|
+
SWR hook for fetching records with built-in pagination.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
function PropertyList() {
|
|
285
|
+
const {
|
|
286
|
+
data: properties,
|
|
287
|
+
isLoading,
|
|
288
|
+
error,
|
|
289
|
+
// Pagination
|
|
290
|
+
page,
|
|
291
|
+
hasNextPage,
|
|
292
|
+
hasPreviousPage,
|
|
293
|
+
nextPage,
|
|
294
|
+
previousPage,
|
|
295
|
+
setPage,
|
|
296
|
+
} = myco.data.useRecords("property", {
|
|
297
|
+
pageSize: 10,
|
|
298
|
+
initialPage: 1,
|
|
299
|
+
sort: "createdAt",
|
|
300
|
+
order: "desc",
|
|
301
|
+
filter: { status: "active" },
|
|
302
|
+
// Called when page changes (for URL sync, analytics, etc.)
|
|
303
|
+
onPageChange: (page) => {
|
|
304
|
+
router.push(`?page=${page}`);
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (isLoading) return <div>Loading...</div>;
|
|
309
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<div>
|
|
313
|
+
{properties?.map((property) => (
|
|
314
|
+
<div key={property.id}>{property.data.title}</div>
|
|
315
|
+
))}
|
|
316
|
+
<button onClick={previousPage} disabled={!hasPreviousPage}>
|
|
317
|
+
Previous
|
|
318
|
+
</button>
|
|
319
|
+
<span>Page {page}</span>
|
|
320
|
+
<button onClick={nextPage} disabled={!hasNextPage}>
|
|
321
|
+
Next
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Options:**
|
|
329
|
+
|
|
330
|
+
| Option | Type | Description |
|
|
331
|
+
|--------|------|-------------|
|
|
332
|
+
| `initialPage` | `number` | Starting page (default: 1) |
|
|
333
|
+
| `pageSize` | `number` | Records per page |
|
|
334
|
+
| `sort` | `string` | Field to sort by |
|
|
335
|
+
| `order` | `"asc" \| "desc"` | Sort direction |
|
|
336
|
+
| `filter` | `object` | Filter criteria |
|
|
337
|
+
| `onPageChange` | `(page: number) => void` | Callback when page changes |
|
|
338
|
+
|
|
339
|
+
**Return value extends SWR response with:**
|
|
340
|
+
|
|
341
|
+
| Property | Type | Description |
|
|
342
|
+
|----------|------|-------------|
|
|
343
|
+
| `page` | `number` | Current page number |
|
|
344
|
+
| `hasNextPage` | `boolean` | Whether there's a next page |
|
|
345
|
+
| `hasPreviousPage` | `boolean` | Whether there's a previous page |
|
|
346
|
+
| `nextPage` | `() => void` | Go to next page |
|
|
347
|
+
| `previousPage` | `() => void` | Go to previous page |
|
|
348
|
+
| `setPage` | `(page: number) => void` | Jump to specific page |
|
|
349
|
+
|
|
350
|
+
##### `myco.data.useRecord(entitySlug, recordId)`
|
|
351
|
+
|
|
352
|
+
SWR hook for fetching a single record.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
function PropertyDetail({ id }: { id: string }) {
|
|
356
|
+
const { data: property, isLoading } = myco.data.useRecord("property", id);
|
|
357
|
+
|
|
358
|
+
if (isLoading) return <div>Loading...</div>;
|
|
359
|
+
|
|
360
|
+
return <div>{property?.data.title}</div>;
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Supports conditional fetching by passing `null` or `undefined`:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// Only fetch when selectedId is set
|
|
368
|
+
const { data } = myco.data.useRecord("property", selectedId ?? null);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Server SDK
|
|
374
|
+
|
|
375
|
+
For server-side rendering and API routes (e.g., Remix loaders/actions).
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { createServerSDK } from "@myco/sdk/server";
|
|
379
|
+
|
|
380
|
+
export async function loader({ request }: LoaderArgs) {
|
|
381
|
+
const myco = createServerSDK(
|
|
382
|
+
{ MYCO_APP_KEY: process.env.MYCO_APP_KEY! },
|
|
383
|
+
request
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const { user } = await myco.auth.getVerifiedUser();
|
|
387
|
+
|
|
388
|
+
if (!user) {
|
|
389
|
+
return redirect(myco.auth.getLoginUrl(request.url));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const { records } = await myco.data.getRecords("property");
|
|
393
|
+
return json({ records });
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Server Auth
|
|
398
|
+
|
|
399
|
+
#### `myco.auth.getVerifiedUser()`
|
|
400
|
+
|
|
401
|
+
Verify the JWT from cookies and return the user.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
const { user, jwt } = await myco.auth.getVerifiedUser();
|
|
405
|
+
if (!user) {
|
|
406
|
+
// Not authenticated
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### `myco.auth.getLoginUrl(returnTo)`
|
|
411
|
+
|
|
412
|
+
Get the federated login URL.
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const loginUrl = myco.auth.getLoginUrl("https://myapp.com/dashboard");
|
|
416
|
+
return redirect(loginUrl);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Server Workspaces
|
|
420
|
+
|
|
421
|
+
Same API as client SDK:
|
|
422
|
+
- `myco.workspaces.list()`
|
|
423
|
+
- `myco.workspaces.current()`
|
|
424
|
+
- `myco.workspaces.create(name)`
|
|
425
|
+
- `myco.workspaces.update(data)`
|
|
426
|
+
- `myco.workspaces.delete()`
|
|
427
|
+
- `myco.workspaces.getMembers()`
|
|
428
|
+
- `myco.workspaces.addMember(userId, role?)`
|
|
429
|
+
- `myco.workspaces.removeMember(userId)`
|
|
430
|
+
|
|
431
|
+
### Server Data
|
|
432
|
+
|
|
433
|
+
Same API as client SDK (async methods only, no hooks):
|
|
434
|
+
- `myco.data.getEntities()`
|
|
435
|
+
- `myco.data.getEntity(entitySlug)`
|
|
436
|
+
- `myco.data.getRecords(entitySlug, options?)`
|
|
437
|
+
- `myco.data.getRecord(entitySlug, recordId)`
|
|
438
|
+
- `myco.data.createRecord(entitySlug, data)`
|
|
439
|
+
- `myco.data.updateRecord(entitySlug, recordId, data)`
|
|
440
|
+
- `myco.data.deleteRecord(entitySlug, recordId)`
|
|
441
|
+
- `myco.data.batchGetRecords(recordIds)`
|
|
442
|
+
|
|
443
|
+
### Server Fetch
|
|
444
|
+
|
|
445
|
+
For custom API calls:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
const result = await myco.fetch<MyType>("/api/custom-endpoint", {
|
|
449
|
+
method: "POST",
|
|
450
|
+
body: JSON.stringify({ data }),
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Error Handling
|
|
457
|
+
|
|
458
|
+
The SDK exports an `ApiError` class for handling API errors:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { ApiError } from "@myco/sdk";
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
await myco.data.getRecord("property", "invalid-id");
|
|
465
|
+
} catch (error) {
|
|
466
|
+
if (error instanceof ApiError) {
|
|
467
|
+
console.log(error.status); // HTTP status code
|
|
468
|
+
console.log(error.body); // Response body
|
|
469
|
+
console.log(error.path); // Request path
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## TypeScript
|
|
477
|
+
|
|
478
|
+
### Generated Types
|
|
479
|
+
|
|
480
|
+
For type-safe entity access, generate types from your Myco schema:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// types/myco.generated.ts
|
|
484
|
+
export interface EntityTypes {
|
|
485
|
+
property: {
|
|
486
|
+
title: string;
|
|
487
|
+
price: number;
|
|
488
|
+
bedrooms: number;
|
|
489
|
+
status: "active" | "sold";
|
|
490
|
+
};
|
|
491
|
+
agent: {
|
|
492
|
+
name: string;
|
|
493
|
+
email: string;
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Then pass the type to `createMycoSDK`:
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
import type { EntityTypes } from "./types/myco.generated";
|
|
502
|
+
|
|
503
|
+
const myco = createMycoSDK<EntityTypes>("your-app-key");
|
|
504
|
+
|
|
505
|
+
// Now fully typed!
|
|
506
|
+
const { records } = await myco.data.getRecords("property");
|
|
507
|
+
// records is TypedEntityRecord<{ title: string; price: number; ... }>[]
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Exported Types
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import type {
|
|
514
|
+
MycoSDKConfig,
|
|
515
|
+
JoinResponse,
|
|
516
|
+
VerifiedUser,
|
|
517
|
+
Workspace,
|
|
518
|
+
WorkspaceMember,
|
|
519
|
+
Entity,
|
|
520
|
+
EntityField,
|
|
521
|
+
EntityRecord,
|
|
522
|
+
PaginatedResponse,
|
|
523
|
+
GetRecordsOptions,
|
|
524
|
+
TypedEntityRecord,
|
|
525
|
+
} from "@myco/sdk";
|
|
526
|
+
```
|