@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/dist/index.js
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
function getBaseUrl(config) {
|
|
3
|
+
if (config.baseUrl) {
|
|
4
|
+
return config.baseUrl;
|
|
5
|
+
}
|
|
6
|
+
if (typeof window === "undefined") {
|
|
7
|
+
return "http://localhost:6926";
|
|
8
|
+
}
|
|
9
|
+
const isLocal = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
10
|
+
return isLocal ? "http://localhost:6926" : "https://api.myco.com";
|
|
11
|
+
}
|
|
12
|
+
function getAuthPortalUrl() {
|
|
13
|
+
if (typeof window === "undefined") {
|
|
14
|
+
return "http://localhost:5173";
|
|
15
|
+
}
|
|
16
|
+
const isLocal = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
17
|
+
return isLocal ? "http://localhost:5173" : "https://myco.com";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/auth.ts
|
|
21
|
+
import { createAuthClient } from "better-auth/react";
|
|
22
|
+
import { jwtClient } from "better-auth/client/plugins";
|
|
23
|
+
var JWT_COOKIE_NAME = "app_jwt";
|
|
24
|
+
function storeJwtCookie(jwt) {
|
|
25
|
+
if (typeof document === "undefined") return;
|
|
26
|
+
const maxAge = 60 * 60 * 24 * 7;
|
|
27
|
+
document.cookie = `${JWT_COOKIE_NAME}=${jwt}; path=/; max-age=${maxAge}; samesite=lax`;
|
|
28
|
+
}
|
|
29
|
+
function clearJwtCookie() {
|
|
30
|
+
if (typeof document === "undefined") return;
|
|
31
|
+
document.cookie = `${JWT_COOKIE_NAME}=; path=/; max-age=0`;
|
|
32
|
+
}
|
|
33
|
+
function getCurrentUrl() {
|
|
34
|
+
if (typeof window === "undefined") return "";
|
|
35
|
+
return window.location.href;
|
|
36
|
+
}
|
|
37
|
+
var jwtCapturePlugin = {
|
|
38
|
+
id: "jwt-capture",
|
|
39
|
+
fetchPlugins: [
|
|
40
|
+
{
|
|
41
|
+
id: "jwt-capture-fetch",
|
|
42
|
+
name: "JWT Capture",
|
|
43
|
+
hooks: {
|
|
44
|
+
onSuccess: (context) => {
|
|
45
|
+
const jwt = context.response.headers.get("set-auth-jwt");
|
|
46
|
+
if (jwt) {
|
|
47
|
+
storeJwtCookie(jwt);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
function createAuth(config) {
|
|
55
|
+
const authClient = createAuthClient({
|
|
56
|
+
baseURL: getBaseUrl(config),
|
|
57
|
+
basePath: "/auth",
|
|
58
|
+
fetchOptions: {
|
|
59
|
+
credentials: "include"
|
|
60
|
+
},
|
|
61
|
+
plugins: [jwtClient(), jwtCapturePlugin]
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
/**
|
|
65
|
+
* Redirect to the federated login page.
|
|
66
|
+
* @param returnTo - URL to return to after login (defaults to current page)
|
|
67
|
+
*/
|
|
68
|
+
login(returnTo) {
|
|
69
|
+
if (typeof window === "undefined") return;
|
|
70
|
+
const authPortalUrl = getAuthPortalUrl();
|
|
71
|
+
const returnUrl = encodeURIComponent(returnTo || getCurrentUrl());
|
|
72
|
+
window.location.href = `${authPortalUrl}/federated/login?return_to=${returnUrl}`;
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Sign out the current user and clear session.
|
|
76
|
+
*/
|
|
77
|
+
async logout() {
|
|
78
|
+
await authClient.signOut();
|
|
79
|
+
clearJwtCookie();
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Get the current session (React hook).
|
|
83
|
+
* Returns { data: session, isPending, error, refetch }
|
|
84
|
+
*/
|
|
85
|
+
useSession: () => authClient.useSession(),
|
|
86
|
+
/**
|
|
87
|
+
* Get session data (non-hook version for use outside React components).
|
|
88
|
+
*/
|
|
89
|
+
getSession: () => authClient.getSession()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/workspaces.ts
|
|
94
|
+
function createWorkspaces(getConfig, setWorkspaceId, apiRequest, ensureWorkspace) {
|
|
95
|
+
return {
|
|
96
|
+
/**
|
|
97
|
+
* Ensure a workspace is set up for the current user.
|
|
98
|
+
* Called automatically during SDK initialization.
|
|
99
|
+
* Call manually after login if redirectOnUnauth was false.
|
|
100
|
+
*/
|
|
101
|
+
ensure: ensureWorkspace,
|
|
102
|
+
/**
|
|
103
|
+
* List all workspaces the current user has access to.
|
|
104
|
+
*/
|
|
105
|
+
async list() {
|
|
106
|
+
return apiRequest("/api/workspaces");
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* Get the current workspace details.
|
|
110
|
+
*/
|
|
111
|
+
async current() {
|
|
112
|
+
const config = getConfig();
|
|
113
|
+
if (!config.workspaceId) {
|
|
114
|
+
throw new Error("No workspace configured. Call workspaces.ensure() first.");
|
|
115
|
+
}
|
|
116
|
+
return apiRequest(`/api/workspaces/${config.workspaceId}`);
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Switch to a different workspace.
|
|
120
|
+
*/
|
|
121
|
+
switch(workspaceId) {
|
|
122
|
+
setWorkspaceId(workspaceId);
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* Create a new workspace.
|
|
126
|
+
*/
|
|
127
|
+
async create(name) {
|
|
128
|
+
return apiRequest("/api/workspaces", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
body: JSON.stringify({ name })
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* Update the current workspace.
|
|
135
|
+
*/
|
|
136
|
+
async update(data) {
|
|
137
|
+
const config = getConfig();
|
|
138
|
+
if (!config.workspaceId) {
|
|
139
|
+
throw new Error("No workspace configured. Call workspaces.ensure() first.");
|
|
140
|
+
}
|
|
141
|
+
return apiRequest(`/api/workspaces/${config.workspaceId}`, {
|
|
142
|
+
method: "PATCH",
|
|
143
|
+
body: JSON.stringify(data)
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* Delete the current workspace.
|
|
148
|
+
*/
|
|
149
|
+
async delete() {
|
|
150
|
+
const config = getConfig();
|
|
151
|
+
if (!config.workspaceId) {
|
|
152
|
+
throw new Error("No workspace configured. Call workspaces.ensure() first.");
|
|
153
|
+
}
|
|
154
|
+
await apiRequest(`/api/workspaces/${config.workspaceId}`, {
|
|
155
|
+
method: "DELETE"
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* Get members of the current workspace.
|
|
160
|
+
*/
|
|
161
|
+
async getMembers() {
|
|
162
|
+
const config = getConfig();
|
|
163
|
+
if (!config.workspaceId) {
|
|
164
|
+
throw new Error("No workspace configured. Call workspaces.ensure() first.");
|
|
165
|
+
}
|
|
166
|
+
return apiRequest(`/api/workspaces/${config.workspaceId}/members`);
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Add a member to the current workspace.
|
|
170
|
+
*/
|
|
171
|
+
async addMember(userId, role = "member") {
|
|
172
|
+
const config = getConfig();
|
|
173
|
+
if (!config.workspaceId) {
|
|
174
|
+
throw new Error("No workspace configured. Call workspaces.ensure() first.");
|
|
175
|
+
}
|
|
176
|
+
return apiRequest(`/api/workspaces/${config.workspaceId}/members`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: JSON.stringify({ userId, role })
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Remove a member from the current workspace.
|
|
183
|
+
*/
|
|
184
|
+
async removeMember(userId) {
|
|
185
|
+
const config = getConfig();
|
|
186
|
+
if (!config.workspaceId) {
|
|
187
|
+
throw new Error("No workspace configured. Call workspaces.ensure() first.");
|
|
188
|
+
}
|
|
189
|
+
await apiRequest(`/api/workspaces/${config.workspaceId}/members/${userId}`, {
|
|
190
|
+
method: "DELETE"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/hooks/records.ts
|
|
197
|
+
import { useState, useCallback } from "react";
|
|
198
|
+
import useSWR from "swr";
|
|
199
|
+
function createRecordsHooks(apiRequest) {
|
|
200
|
+
return {
|
|
201
|
+
useRecords(entitySlug, options) {
|
|
202
|
+
const { initialPage = 1, onPageChange, ...queryOptions } = options ?? {};
|
|
203
|
+
const [page, setPageState] = useState(initialPage);
|
|
204
|
+
const [hasNextPage, setHasNextPage] = useState(false);
|
|
205
|
+
const setPage = useCallback(
|
|
206
|
+
(newPage) => {
|
|
207
|
+
if (newPage < 1) return;
|
|
208
|
+
setPageState(newPage);
|
|
209
|
+
onPageChange?.(newPage);
|
|
210
|
+
},
|
|
211
|
+
[onPageChange]
|
|
212
|
+
);
|
|
213
|
+
const nextPage = useCallback(() => {
|
|
214
|
+
if (hasNextPage) {
|
|
215
|
+
setPage(page + 1);
|
|
216
|
+
}
|
|
217
|
+
}, [hasNextPage, page, setPage]);
|
|
218
|
+
const previousPage = useCallback(() => {
|
|
219
|
+
if (page > 1) {
|
|
220
|
+
setPage(page - 1);
|
|
221
|
+
}
|
|
222
|
+
}, [page, setPage]);
|
|
223
|
+
const swrResult = useSWR(
|
|
224
|
+
entitySlug ? ["records", entitySlug, page, queryOptions] : null,
|
|
225
|
+
async () => {
|
|
226
|
+
const params = new URLSearchParams();
|
|
227
|
+
params.set("page", String(page));
|
|
228
|
+
if (queryOptions?.pageSize !== void 0) {
|
|
229
|
+
params.set("pageSize", String(queryOptions.pageSize));
|
|
230
|
+
}
|
|
231
|
+
if (queryOptions?.sort) {
|
|
232
|
+
params.set("sort", queryOptions.sort);
|
|
233
|
+
}
|
|
234
|
+
if (queryOptions?.order) {
|
|
235
|
+
params.set("order", queryOptions.order);
|
|
236
|
+
}
|
|
237
|
+
if (queryOptions?.filter) {
|
|
238
|
+
params.set("filter", JSON.stringify(queryOptions.filter));
|
|
239
|
+
}
|
|
240
|
+
const queryString = params.toString();
|
|
241
|
+
const path = `/api/entities/${entitySlug}/records?${queryString}`;
|
|
242
|
+
const response = await apiRequest(path);
|
|
243
|
+
setHasNextPage(response.pagination.hasMore);
|
|
244
|
+
return response.records;
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
return {
|
|
248
|
+
...swrResult,
|
|
249
|
+
page,
|
|
250
|
+
hasNextPage,
|
|
251
|
+
hasPreviousPage: page > 1,
|
|
252
|
+
nextPage,
|
|
253
|
+
previousPage,
|
|
254
|
+
setPage
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
useRecord(entitySlug, recordId) {
|
|
258
|
+
return useSWR(
|
|
259
|
+
entitySlug && recordId ? ["record", entitySlug, recordId] : null,
|
|
260
|
+
async () => {
|
|
261
|
+
const response = await apiRequest(
|
|
262
|
+
`/api/entities/${entitySlug}/records/${recordId}`
|
|
263
|
+
);
|
|
264
|
+
return response;
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/data.ts
|
|
272
|
+
function createData(apiRequest) {
|
|
273
|
+
const hooks = createRecordsHooks(apiRequest);
|
|
274
|
+
return {
|
|
275
|
+
...hooks,
|
|
276
|
+
/**
|
|
277
|
+
* Get all entities for the current app.
|
|
278
|
+
*/
|
|
279
|
+
async getEntities() {
|
|
280
|
+
return apiRequest("/api/entities");
|
|
281
|
+
},
|
|
282
|
+
/**
|
|
283
|
+
* Get a single entity by slug.
|
|
284
|
+
*/
|
|
285
|
+
async getEntity(entitySlug) {
|
|
286
|
+
return apiRequest(`/api/entities/${entitySlug}`);
|
|
287
|
+
},
|
|
288
|
+
/**
|
|
289
|
+
* Get records for an entity.
|
|
290
|
+
*/
|
|
291
|
+
async getRecords(entitySlug, options) {
|
|
292
|
+
const params = new URLSearchParams();
|
|
293
|
+
if (options?.page !== void 0) {
|
|
294
|
+
params.set("page", String(options.page));
|
|
295
|
+
}
|
|
296
|
+
if (options?.pageSize !== void 0) {
|
|
297
|
+
params.set("pageSize", String(options.pageSize));
|
|
298
|
+
}
|
|
299
|
+
if (options?.sort) {
|
|
300
|
+
params.set("sort", options.sort);
|
|
301
|
+
}
|
|
302
|
+
if (options?.order) {
|
|
303
|
+
params.set("order", options.order);
|
|
304
|
+
}
|
|
305
|
+
if (options?.filter) {
|
|
306
|
+
params.set("filter", JSON.stringify(options.filter));
|
|
307
|
+
}
|
|
308
|
+
const queryString = params.toString();
|
|
309
|
+
const path = `/api/entities/${entitySlug}/records${queryString ? `?${queryString}` : ""}`;
|
|
310
|
+
return apiRequest(path);
|
|
311
|
+
},
|
|
312
|
+
/**
|
|
313
|
+
* Get a single record by ID.
|
|
314
|
+
*/
|
|
315
|
+
async getRecord(entitySlug, recordId) {
|
|
316
|
+
return apiRequest(`/api/entities/${entitySlug}/records/${recordId}`);
|
|
317
|
+
},
|
|
318
|
+
/**
|
|
319
|
+
* Create a new record.
|
|
320
|
+
*/
|
|
321
|
+
async createRecord(entitySlug, data) {
|
|
322
|
+
return apiRequest(`/api/entities/${entitySlug}/records`, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
body: JSON.stringify({ data })
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
/**
|
|
328
|
+
* Update an existing record.
|
|
329
|
+
*/
|
|
330
|
+
async updateRecord(entitySlug, recordId, data) {
|
|
331
|
+
return apiRequest(`/api/entities/${entitySlug}/records/${recordId}`, {
|
|
332
|
+
method: "PATCH",
|
|
333
|
+
body: JSON.stringify({ data })
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
/**
|
|
337
|
+
* Delete a record.
|
|
338
|
+
*/
|
|
339
|
+
async deleteRecord(entitySlug, recordId) {
|
|
340
|
+
await apiRequest(`/api/entities/${entitySlug}/records/${recordId}`, {
|
|
341
|
+
method: "DELETE"
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
/**
|
|
345
|
+
* Batch get multiple records by IDs.
|
|
346
|
+
*/
|
|
347
|
+
async batchGetRecords(entitySlug, ids) {
|
|
348
|
+
const response = await apiRequest(
|
|
349
|
+
`/api/entities/${entitySlug}/records/batch`,
|
|
350
|
+
{
|
|
351
|
+
method: "POST",
|
|
352
|
+
body: JSON.stringify({ ids })
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
return response.records;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/api-client.ts
|
|
361
|
+
var ApiError = class extends Error {
|
|
362
|
+
constructor(status, body, path) {
|
|
363
|
+
super(`API Error ${status} on ${path}: ${body}`);
|
|
364
|
+
this.status = status;
|
|
365
|
+
this.body = body;
|
|
366
|
+
this.path = path;
|
|
367
|
+
this.name = "ApiError";
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
function createApiClient(getConfig, ready) {
|
|
371
|
+
return async function apiRequest(path, options = {}) {
|
|
372
|
+
await ready;
|
|
373
|
+
const config = getConfig();
|
|
374
|
+
const baseUrl = getBaseUrl(config);
|
|
375
|
+
const headers = new Headers(options.headers);
|
|
376
|
+
if (!headers.has("Content-Type") && options.body) {
|
|
377
|
+
headers.set("Content-Type", "application/json");
|
|
378
|
+
}
|
|
379
|
+
headers.set("Myco-App-Key", config.appKey);
|
|
380
|
+
if (config.workspaceId) {
|
|
381
|
+
headers.set("Myco-Workspace-Id", config.workspaceId);
|
|
382
|
+
}
|
|
383
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
384
|
+
...options,
|
|
385
|
+
headers,
|
|
386
|
+
credentials: "include"
|
|
387
|
+
});
|
|
388
|
+
if (!response.ok) {
|
|
389
|
+
const errorText = await response.text();
|
|
390
|
+
throw new ApiError(response.status, errorText, path);
|
|
391
|
+
}
|
|
392
|
+
const contentType = response.headers.get("Content-Type");
|
|
393
|
+
if (!contentType?.includes("application/json")) {
|
|
394
|
+
return void 0;
|
|
395
|
+
}
|
|
396
|
+
return response.json();
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/index.ts
|
|
401
|
+
var WORKSPACE_STORAGE_KEY = "myco_workspace_id";
|
|
402
|
+
function getStoredWorkspaceId() {
|
|
403
|
+
if (typeof localStorage === "undefined") return null;
|
|
404
|
+
try {
|
|
405
|
+
return localStorage.getItem(WORKSPACE_STORAGE_KEY);
|
|
406
|
+
} catch {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function storeWorkspaceId(workspaceId) {
|
|
411
|
+
if (typeof localStorage === "undefined") return;
|
|
412
|
+
try {
|
|
413
|
+
localStorage.setItem(WORKSPACE_STORAGE_KEY, workspaceId);
|
|
414
|
+
} catch {
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function clearStoredWorkspaceId() {
|
|
418
|
+
if (typeof localStorage === "undefined") return;
|
|
419
|
+
try {
|
|
420
|
+
localStorage.removeItem(WORKSPACE_STORAGE_KEY);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function createMycoSDK(appKey, options = {}) {
|
|
425
|
+
const { redirectOnUnauth = true } = options;
|
|
426
|
+
const initialConfig = {
|
|
427
|
+
appKey,
|
|
428
|
+
workspaceId: void 0,
|
|
429
|
+
baseUrl: options.baseUrl
|
|
430
|
+
};
|
|
431
|
+
let config = { ...initialConfig };
|
|
432
|
+
const getConfig = () => config;
|
|
433
|
+
const setWorkspaceId = (workspaceId) => {
|
|
434
|
+
config = { ...config, workspaceId };
|
|
435
|
+
storeWorkspaceId(workspaceId);
|
|
436
|
+
};
|
|
437
|
+
let readyResolve;
|
|
438
|
+
let readyReject;
|
|
439
|
+
const ready = new Promise((resolve, reject) => {
|
|
440
|
+
readyResolve = resolve;
|
|
441
|
+
readyReject = reject;
|
|
442
|
+
});
|
|
443
|
+
const apiRequest = createApiClient(getConfig, ready);
|
|
444
|
+
const auth = createAuth(config);
|
|
445
|
+
async function joinApp() {
|
|
446
|
+
const baseUrl = getBaseUrl(config);
|
|
447
|
+
const response = await fetch(`${baseUrl}/api/app/join`, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
headers: {
|
|
450
|
+
"Content-Type": "application/json",
|
|
451
|
+
"Myco-App-Key": appKey
|
|
452
|
+
},
|
|
453
|
+
credentials: "include"
|
|
454
|
+
});
|
|
455
|
+
if (response.status === 401) {
|
|
456
|
+
throw new Error("UNAUTHENTICATED");
|
|
457
|
+
}
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
throw new Error(`Join failed: ${response.status}`);
|
|
460
|
+
}
|
|
461
|
+
return response.json();
|
|
462
|
+
}
|
|
463
|
+
async function ensureWorkspace() {
|
|
464
|
+
const storedId = getStoredWorkspaceId();
|
|
465
|
+
if (storedId) {
|
|
466
|
+
try {
|
|
467
|
+
const response2 = await joinApp();
|
|
468
|
+
if (response2.success && response2.workspaceId) {
|
|
469
|
+
setWorkspaceId(response2.workspaceId);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (error instanceof Error && error.message === "UNAUTHENTICATED") {
|
|
474
|
+
clearStoredWorkspaceId();
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
clearStoredWorkspaceId();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const response = await joinApp();
|
|
481
|
+
if (response.success && response.workspaceId) {
|
|
482
|
+
setWorkspaceId(response.workspaceId);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
(async () => {
|
|
486
|
+
try {
|
|
487
|
+
await ensureWorkspace();
|
|
488
|
+
readyResolve();
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (error instanceof Error && error.message === "UNAUTHENTICATED") {
|
|
491
|
+
if (redirectOnUnauth) {
|
|
492
|
+
auth.login();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
readyResolve();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
readyReject(error instanceof Error ? error : new Error(String(error)));
|
|
499
|
+
}
|
|
500
|
+
})();
|
|
501
|
+
const workspaces = createWorkspaces(getConfig, setWorkspaceId, apiRequest, ensureWorkspace);
|
|
502
|
+
return {
|
|
503
|
+
ready,
|
|
504
|
+
auth,
|
|
505
|
+
workspaces,
|
|
506
|
+
data: createData(apiRequest)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
export {
|
|
510
|
+
ApiError,
|
|
511
|
+
createMycoSDK
|
|
512
|
+
};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { MycoSDKInstance } from './index.js';
|
|
4
|
+
import 'better-auth';
|
|
5
|
+
import '@better-fetch/fetch';
|
|
6
|
+
import './types-D2J6kXGR.js';
|
|
7
|
+
import 'swr';
|
|
8
|
+
|
|
9
|
+
interface AuthContextValue {
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
isLoggedIn: boolean;
|
|
12
|
+
userId?: string;
|
|
13
|
+
user?: {
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
login: (returnTo?: string) => void;
|
|
19
|
+
logout: () => Promise<void>;
|
|
20
|
+
refresh: () => void;
|
|
21
|
+
}
|
|
22
|
+
interface ProtectedRouteProps {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
fallback?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Provider that wraps the Myco SDK auth state for React components.
|
|
28
|
+
* Must be placed near the root of your React app.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* import { AuthProvider } from "@myco/sdk/react";
|
|
33
|
+
* import { myco } from "./lib/myco";
|
|
34
|
+
*
|
|
35
|
+
* function App() {
|
|
36
|
+
* return (
|
|
37
|
+
* <AuthProvider myco={myco}>
|
|
38
|
+
* <YourRoutes />
|
|
39
|
+
* </AuthProvider>
|
|
40
|
+
* );
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare function AuthProvider({ myco, children, }: {
|
|
45
|
+
myco: MycoSDKInstance<any>;
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
}): react_jsx_runtime.JSX.Element;
|
|
48
|
+
/**
|
|
49
|
+
* Hook to access auth state and methods.
|
|
50
|
+
* Must be used within an AuthProvider.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* function UserMenu() {
|
|
55
|
+
* const { isLoggedIn, user, logout } = useAuth();
|
|
56
|
+
*
|
|
57
|
+
* if (!isLoggedIn) return <button onClick={() => login()}>Login</button>;
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <div>
|
|
61
|
+
* <span>{user?.name}</span>
|
|
62
|
+
* <button onClick={logout}>Logout</button>
|
|
63
|
+
* </div>
|
|
64
|
+
* );
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare function useAuth(): AuthContextValue;
|
|
69
|
+
/**
|
|
70
|
+
* Hook to access the Myco SDK instance.
|
|
71
|
+
* Must be used within an AuthProvider.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* function MyComponent() {
|
|
76
|
+
* const myco = useMyco<EntityTypes>();
|
|
77
|
+
* const records = await myco.data.getRecords('property');
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function useMyco<T extends Record<string, unknown>>(): MycoSDKInstance<T>;
|
|
82
|
+
/**
|
|
83
|
+
* Component that protects its children from unauthenticated access.
|
|
84
|
+
* Redirects to Myco login if user is not authenticated.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // In a router
|
|
89
|
+
* <Route
|
|
90
|
+
* path="/dashboard"
|
|
91
|
+
* element={
|
|
92
|
+
* <ProtectedRoute fallback={<LoadingSpinner />}>
|
|
93
|
+
* <Dashboard />
|
|
94
|
+
* </ProtectedRoute>
|
|
95
|
+
* }
|
|
96
|
+
* />
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare function ProtectedRoute({ children, fallback }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
|
|
100
|
+
|
|
101
|
+
export { AuthProvider, ProtectedRoute, useAuth, useMyco };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/react.tsx
|
|
2
|
+
import { createContext, useContext, useEffect } from "react";
|
|
3
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
|
+
var AuthContext = createContext(null);
|
|
5
|
+
var MycoContext = createContext(null);
|
|
6
|
+
function AuthProvider({
|
|
7
|
+
myco,
|
|
8
|
+
children
|
|
9
|
+
}) {
|
|
10
|
+
const { data: session, isPending, refetch } = myco.auth.useSession();
|
|
11
|
+
const value = {
|
|
12
|
+
isLoading: isPending,
|
|
13
|
+
isLoggedIn: !!session?.user,
|
|
14
|
+
userId: session?.user?.id,
|
|
15
|
+
user: session?.user,
|
|
16
|
+
login: (returnTo) => myco.auth.login(returnTo),
|
|
17
|
+
logout: () => myco.auth.logout(),
|
|
18
|
+
refresh: refetch
|
|
19
|
+
};
|
|
20
|
+
return /* @__PURE__ */ jsx(MycoContext.Provider, { value: myco, children: /* @__PURE__ */ jsx(AuthContext.Provider, { value, children }) });
|
|
21
|
+
}
|
|
22
|
+
function useAuth() {
|
|
23
|
+
const context = useContext(AuthContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error("useAuth must be used within an AuthProvider");
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
function useMyco() {
|
|
30
|
+
const context = useContext(MycoContext);
|
|
31
|
+
if (!context) {
|
|
32
|
+
throw new Error("useMyco must be used within an AuthProvider");
|
|
33
|
+
}
|
|
34
|
+
return context;
|
|
35
|
+
}
|
|
36
|
+
function ProtectedRoute({ children, fallback = null }) {
|
|
37
|
+
const { isLoading, isLoggedIn, login } = useAuth();
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!isLoading && !isLoggedIn) {
|
|
40
|
+
login(window.location.href);
|
|
41
|
+
}
|
|
42
|
+
}, [isLoading, isLoggedIn, login]);
|
|
43
|
+
if (isLoading) {
|
|
44
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
45
|
+
}
|
|
46
|
+
return isLoggedIn ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
AuthProvider,
|
|
50
|
+
ProtectedRoute,
|
|
51
|
+
useAuth,
|
|
52
|
+
useMyco
|
|
53
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { V as VerifiedUser, W as Workspace, a as WorkspaceMember, E as Entity, G as GetRecordsOptions, P as PaginatedResponse, b as EntityRecord, S as ServerEnv } from './types-D2J6kXGR.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-side SDK instance
|
|
5
|
+
*/
|
|
6
|
+
interface ServerSDK {
|
|
7
|
+
/**
|
|
8
|
+
* Make an authenticated API request to the backend.
|
|
9
|
+
* Automatically includes JWT from cookie and app headers.
|
|
10
|
+
*/
|
|
11
|
+
fetch<T>(path: string, options?: RequestInit): Promise<T>;
|
|
12
|
+
auth: {
|
|
13
|
+
getVerifiedUser: () => Promise<{
|
|
14
|
+
user: VerifiedUser | null;
|
|
15
|
+
jwt: string | null;
|
|
16
|
+
}>;
|
|
17
|
+
getLoginUrl: (returnTo: string) => string;
|
|
18
|
+
};
|
|
19
|
+
workspaces: {
|
|
20
|
+
list: () => Promise<Workspace[]>;
|
|
21
|
+
current: () => Promise<Workspace>;
|
|
22
|
+
create: (name: string) => Promise<Workspace>;
|
|
23
|
+
update: (data: {
|
|
24
|
+
name?: string;
|
|
25
|
+
}) => Promise<Workspace>;
|
|
26
|
+
delete: () => Promise<void>;
|
|
27
|
+
getMembers: () => Promise<WorkspaceMember[]>;
|
|
28
|
+
addMember: (userId: string, role?: "admin" | "member") => Promise<WorkspaceMember>;
|
|
29
|
+
removeMember: (userId: string) => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
data: {
|
|
32
|
+
getEntities: () => Promise<Entity[]>;
|
|
33
|
+
getEntity: (entitySlug: string) => Promise<Entity>;
|
|
34
|
+
getRecords: (entitySlug: string, options?: GetRecordsOptions) => Promise<PaginatedResponse<EntityRecord>>;
|
|
35
|
+
getRecord: (entitySlug: string, recordId: string) => Promise<EntityRecord>;
|
|
36
|
+
createRecord: (entitySlug: string, data: Record<string, unknown>) => Promise<EntityRecord>;
|
|
37
|
+
updateRecord: (entitySlug: string, recordId: string, data: Record<string, unknown>) => Promise<EntityRecord>;
|
|
38
|
+
deleteRecord: (entitySlug: string, recordId: string) => Promise<void>;
|
|
39
|
+
batchGetRecords: (recordIds: string[]) => Promise<EntityRecord[]>;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a server-side SDK instance.
|
|
44
|
+
* Use this in loaders and actions.
|
|
45
|
+
*/
|
|
46
|
+
declare function createServerSDK(env: ServerEnv, request: Request): ServerSDK;
|
|
47
|
+
|
|
48
|
+
export { ServerEnv, type ServerSDK, VerifiedUser, createServerSDK };
|