@makemind/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/dist/index.js ADDED
@@ -0,0 +1,4131 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ApiClient: () => ApiClient,
34
+ ErrorCodes: () => ErrorCodes,
35
+ ExpertChat: () => ExpertChat,
36
+ ExpertDoorError: () => ExpertDoorError,
37
+ MAKEMINDAI: () => MAKEMINDAI,
38
+ MAKEMINDApps: () => MAKEMINDApps,
39
+ MAKEMINDAuth: () => MAKEMINDAuth,
40
+ MAKEMINDClient: () => MAKEMINDClient,
41
+ MAKEMINDData: () => MAKEMINDData,
42
+ MAKEMINDError: () => MAKEMINDError,
43
+ MAKEMINDExperts: () => MAKEMINDExperts,
44
+ MAKEMINDLogic: () => MAKEMINDLogic,
45
+ MAKEMINDMarketplace: () => MAKEMINDMarketplace,
46
+ MAKEMINDMessaging: () => MAKEMINDMessaging,
47
+ MAKEMINDSearch: () => MAKEMINDSearch,
48
+ MAKEMINDSettlement: () => MAKEMINDSettlement,
49
+ MAKEMINDStorage: () => MAKEMINDStorage,
50
+ MAKEMINDTenant: () => MAKEMINDTenant,
51
+ getMAKEMIND: () => getMAKEMIND,
52
+ initializeMAKEMIND: () => initializeMAKEMIND,
53
+ mountExpertChat: () => mountExpertChat
54
+ });
55
+ module.exports = __toCommonJS(index_exports);
56
+
57
+ // src/utils/errors.ts
58
+ var MAKEMINDError = class _MAKEMINDError extends Error {
59
+ constructor(code, message, details) {
60
+ super(message);
61
+ this.name = "MAKEMINDError";
62
+ this.code = code;
63
+ this.details = details;
64
+ }
65
+ static fromApiError(error) {
66
+ const err = error;
67
+ const code = err.code || "UNKNOWN_ERROR";
68
+ const message = err.message || "An unknown error occurred";
69
+ return new _MAKEMINDError(code, message, err.details);
70
+ }
71
+ static fromHttpStatus(status, body) {
72
+ const statusMapping = {
73
+ 400: ErrorCodes.VALIDATION_ERROR,
74
+ 401: ErrorCodes.UNAUTHENTICATED,
75
+ 403: ErrorCodes.PERMISSION_DENIED,
76
+ 404: ErrorCodes.NOT_FOUND,
77
+ 409: ErrorCodes.ALREADY_EXISTS,
78
+ 429: ErrorCodes.QUOTA_EXCEEDED,
79
+ 500: ErrorCodes.INTERNAL_ERROR,
80
+ 503: ErrorCodes.SERVICE_UNAVAILABLE
81
+ };
82
+ const code = statusMapping[status] || "UNKNOWN_ERROR";
83
+ const bodyObj = body;
84
+ const message = bodyObj?.message || `HTTP error ${status}`;
85
+ return new _MAKEMINDError(code, message, bodyObj?.details);
86
+ }
87
+ toJSON() {
88
+ return {
89
+ code: this.code,
90
+ message: this.message,
91
+ details: this.details
92
+ };
93
+ }
94
+ };
95
+ var ErrorCodes = {
96
+ // Auth errors
97
+ UNAUTHENTICATED: "UNAUTHENTICATED",
98
+ PERMISSION_DENIED: "PERMISSION_DENIED",
99
+ INVALID_TOKEN: "INVALID_TOKEN",
100
+ // Data errors
101
+ NOT_FOUND: "NOT_FOUND",
102
+ ALREADY_EXISTS: "ALREADY_EXISTS",
103
+ VALIDATION_ERROR: "VALIDATION_ERROR",
104
+ // Tenant errors
105
+ TENANT_NOT_FOUND: "TENANT_NOT_FOUND",
106
+ TENANT_SUSPENDED: "TENANT_SUSPENDED",
107
+ NOT_A_MEMBER: "NOT_A_MEMBER",
108
+ // Quota errors
109
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
110
+ // Storage errors
111
+ STORAGE_ERROR: "STORAGE_ERROR",
112
+ // System errors
113
+ INTERNAL_ERROR: "INTERNAL_ERROR",
114
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
115
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
116
+ };
117
+
118
+ // src/services/auth.ts
119
+ var STORAGE_KEYS = {
120
+ AUTH_TOKEN: "makemindAuthToken",
121
+ REFRESH_TOKEN: "makemindRefreshToken",
122
+ USER_DATA: "makemindUserData",
123
+ MEMBERSHIPS: "makemindMemberships"
124
+ };
125
+ var MAKEMINDAuth = class {
126
+ constructor(config) {
127
+ this._currentUser = null;
128
+ this._memberships = [];
129
+ this.refreshToken = null;
130
+ this.authStateCallbacks = /* @__PURE__ */ new Set();
131
+ this.apiClient = config.apiClient;
132
+ this.applicationId = config.applicationId;
133
+ this.onAuthStateChange = config.onAuthStateChange;
134
+ this.setAuthToken = config.setAuthToken;
135
+ this.restoreSession();
136
+ }
137
+ restoreSession() {
138
+ if (typeof window === "undefined" || typeof localStorage === "undefined") {
139
+ return;
140
+ }
141
+ try {
142
+ const storedToken = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
143
+ const storedRefreshToken = localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
144
+ if (storedRefreshToken) localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
145
+ const storedUserData = localStorage.getItem(STORAGE_KEYS.USER_DATA);
146
+ const storedMemberships = localStorage.getItem(STORAGE_KEYS.MEMBERSHIPS);
147
+ if (storedToken && storedUserData) {
148
+ this._currentUser = JSON.parse(storedUserData);
149
+ this.refreshToken = storedRefreshToken;
150
+ this._memberships = storedMemberships ? JSON.parse(storedMemberships) : [];
151
+ this.setAuthToken(storedToken);
152
+ this.onAuthStateChange(this._currentUser);
153
+ }
154
+ } catch {
155
+ this.clearStoredSession();
156
+ }
157
+ }
158
+ persistSession(user, token, refreshToken, memberships) {
159
+ if (typeof window === "undefined" || typeof localStorage === "undefined") {
160
+ return;
161
+ }
162
+ try {
163
+ localStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, token);
164
+ localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
165
+ localStorage.setItem(STORAGE_KEYS.USER_DATA, JSON.stringify(user));
166
+ if (memberships) {
167
+ localStorage.setItem(STORAGE_KEYS.MEMBERSHIPS, JSON.stringify(memberships));
168
+ }
169
+ } catch {
170
+ }
171
+ }
172
+ clearStoredSession() {
173
+ if (typeof window === "undefined" || typeof localStorage === "undefined") {
174
+ return;
175
+ }
176
+ try {
177
+ localStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
178
+ localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
179
+ localStorage.removeItem(STORAGE_KEYS.USER_DATA);
180
+ localStorage.removeItem(STORAGE_KEYS.MEMBERSHIPS);
181
+ } catch {
182
+ }
183
+ }
184
+ get currentUser() {
185
+ return this._currentUser;
186
+ }
187
+ get memberships() {
188
+ return this._memberships;
189
+ }
190
+ /**
191
+ * Set the session directly from an externally-obtained token — e.g. a Firebase
192
+ * ID token from Google sign-in, which the gateway accepts as a Bearer. Mirrors
193
+ * the post-authentication state update of signInWithEmail (set token, persist
194
+ * to localStorage, notify auth-state listeners) without a /auth/signin
195
+ * round-trip. Also used by the console's token-refresh bridge to swap in a
196
+ * freshly-refreshed Firebase token. Existing memberships are preserved when
197
+ * `memberships` is omitted.
198
+ */
199
+ setSession(user, token, refreshToken, memberships) {
200
+ this._currentUser = user;
201
+ this.refreshToken = refreshToken;
202
+ if (memberships) {
203
+ this._memberships = memberships;
204
+ }
205
+ this.setAuthToken(token);
206
+ this.persistSession(user, token, refreshToken, memberships ?? this._memberships);
207
+ this.onAuthStateChange(user);
208
+ this.notifyAuthStateCallbacks(user);
209
+ }
210
+ /**
211
+ * Load the caller's tenant memberships into the session.
212
+ *
213
+ * `signInWithEmail` gets them in its response, but a session established from
214
+ * an external credential (a Firebase ID token from a Google popup) has no
215
+ * such response — it arrives with claims only. Claims carry the *current*
216
+ * tenant, not the list, and a platform admin's claims carry no tenant roles
217
+ * at all, so a console that gates its navigation on `admin`/`owner` shows an
218
+ * almost empty menu until this runs.
219
+ *
220
+ * @return {Promise<TenantMembership[]>} The loaded memberships.
221
+ */
222
+ async loadMemberships() {
223
+ const res = await this.apiClient.dispatch(
224
+ "getUserTenants",
225
+ {}
226
+ );
227
+ const memberships = (res?.tenants ?? []).map((t) => ({
228
+ tenantId: String(t.id ?? ""),
229
+ tenantName: String(t.name ?? t.id ?? ""),
230
+ tenantDisplayName: String(t.displayName ?? t.name ?? t.id ?? ""),
231
+ roles: Array.isArray(t.roles) ? t.roles : [],
232
+ // The op returns the tenant's own status; a membership on a live tenant
233
+ // is what a consumer means by active.
234
+ status: String(t.status ?? "active")
235
+ }));
236
+ this._memberships = memberships;
237
+ if (this._currentUser) {
238
+ localStorage.setItem(STORAGE_KEYS.MEMBERSHIPS, JSON.stringify(memberships));
239
+ }
240
+ return memberships;
241
+ }
242
+ async signInWithEmail(email, password) {
243
+ try {
244
+ const response = await this.apiClient.post("/auth/signin", {
245
+ email,
246
+ password,
247
+ applicationId: this.applicationId
248
+ }, { skipAuth: true });
249
+ this._currentUser = response.user;
250
+ this.refreshToken = response.refreshToken ?? null;
251
+ this._memberships = response.memberships || [];
252
+ this.setAuthToken(response.token);
253
+ this.persistSession(response.user, response.token, response.refreshToken, response.memberships);
254
+ this.onAuthStateChange(response.user);
255
+ this.notifyAuthStateCallbacks(response.user);
256
+ return response.user;
257
+ } catch (error) {
258
+ if (error instanceof MAKEMINDError) {
259
+ throw error;
260
+ }
261
+ throw new MAKEMINDError("AUTH_ERROR", "Failed to sign in");
262
+ }
263
+ }
264
+ // Alias for Firebase compatibility
265
+ async signInWithEmailAndPassword(email, password) {
266
+ return this.signInWithEmail(email, password);
267
+ }
268
+ async signUp(email, password, options) {
269
+ try {
270
+ const response = await this.apiClient.post("/auth/signup", {
271
+ email,
272
+ password,
273
+ displayName: options?.displayName,
274
+ phoneNumber: options?.phoneNumber,
275
+ applicationId: this.applicationId
276
+ }, { skipAuth: true });
277
+ this._currentUser = response.user;
278
+ this.refreshToken = response.refreshToken ?? null;
279
+ this._memberships = response.memberships || [];
280
+ this.setAuthToken(response.token);
281
+ this.persistSession(response.user, response.token, response.refreshToken, response.memberships);
282
+ this.onAuthStateChange(response.user);
283
+ this.notifyAuthStateCallbacks(response.user);
284
+ return response.user;
285
+ } catch (error) {
286
+ if (error instanceof MAKEMINDError) {
287
+ throw error;
288
+ }
289
+ throw new MAKEMINDError("AUTH_ERROR", "Failed to sign up");
290
+ }
291
+ }
292
+ async signOut() {
293
+ try {
294
+ await this.apiClient.post("/auth/signout", {});
295
+ } catch {
296
+ } finally {
297
+ this._currentUser = null;
298
+ this._memberships = [];
299
+ this.refreshToken = null;
300
+ this.setAuthToken(null);
301
+ this.clearStoredSession();
302
+ this.onAuthStateChange(null);
303
+ this.notifyAuthStateCallbacks(null);
304
+ }
305
+ }
306
+ async sendPasswordResetEmail(email) {
307
+ await this.apiClient.post("/auth/password/reset", {
308
+ email,
309
+ applicationId: this.applicationId
310
+ }, { skipAuth: true });
311
+ }
312
+ onAuthStateChanged(callback) {
313
+ this.authStateCallbacks.add(callback);
314
+ callback(this._currentUser);
315
+ return () => {
316
+ this.authStateCallbacks.delete(callback);
317
+ };
318
+ }
319
+ notifyAuthStateCallbacks(user) {
320
+ this.authStateCallbacks.forEach((callback) => {
321
+ try {
322
+ callback(user);
323
+ } catch {
324
+ }
325
+ });
326
+ }
327
+ async refreshAuthToken() {
328
+ try {
329
+ const response = await this.apiClient.post(
330
+ "/auth/refresh",
331
+ this.refreshToken ? { refreshToken: this.refreshToken } : {},
332
+ { skipAuth: true }
333
+ );
334
+ this.refreshToken = response.refreshToken ?? null;
335
+ this.setAuthToken(response.token);
336
+ if (response.memberships) {
337
+ this._memberships = response.memberships;
338
+ }
339
+ if (response.user) {
340
+ this._currentUser = response.user;
341
+ this.onAuthStateChange(response.user);
342
+ this.notifyAuthStateCallbacks(response.user);
343
+ this.persistSession(response.user, response.token, response.refreshToken, response.memberships);
344
+ } else if (this._currentUser) {
345
+ this.persistSession(this._currentUser, response.token, response.refreshToken, response.memberships);
346
+ }
347
+ } catch {
348
+ await this.signOut();
349
+ throw new MAKEMINDError("UNAUTHENTICATED", "Session expired");
350
+ }
351
+ }
352
+ async switchTenant(tenantId) {
353
+ await this.apiClient.dispatch(
354
+ "switchTenant",
355
+ { tenantId }
356
+ );
357
+ await this.refreshAuthToken();
358
+ if (!this._currentUser) {
359
+ throw new MAKEMINDError("UNAUTHENTICATED", "Session lost after tenant switch");
360
+ }
361
+ return this._currentUser;
362
+ }
363
+ // Returns tenants the authenticated user belongs to.
364
+ // Backend dispatch 'authGetUserTenants' → { tenants: Record<string, unknown>[] }
365
+ async getUserMemberships() {
366
+ const response = await this.apiClient.dispatch(
367
+ "authGetUserTenants"
368
+ );
369
+ return (response.tenants || []).map((t) => ({
370
+ tenantId: t["id"],
371
+ tenantName: t["displayName"] ?? t["name"],
372
+ roles: t["roles"] ?? [],
373
+ status: t["status"] ?? "active"
374
+ }));
375
+ }
376
+ // Permission helpers
377
+ hasScope(scope) {
378
+ if (!this._currentUser) return false;
379
+ return this._currentUser.scopes.includes(scope) || this._currentUser.scopes.includes("*");
380
+ }
381
+ hasRole(role) {
382
+ if (!this._currentUser) return false;
383
+ return this._currentUser.roles.includes(role);
384
+ }
385
+ hasAnyRole(roles) {
386
+ if (!this._currentUser) return false;
387
+ return roles.some((role) => this._currentUser.roles.includes(role));
388
+ }
389
+ hasAllScopes(scopes) {
390
+ if (!this._currentUser) return false;
391
+ if (this._currentUser.scopes.includes("*")) return true;
392
+ return scopes.every((scope) => this._currentUser.scopes.includes(scope));
393
+ }
394
+ /**
395
+ * Get the current ID token for use with external services
396
+ */
397
+ async getIdToken(forceRefresh = false) {
398
+ if (!this._currentUser) {
399
+ return null;
400
+ }
401
+ if (forceRefresh) {
402
+ await this.refreshAuthToken();
403
+ }
404
+ if (typeof localStorage !== "undefined") {
405
+ return localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
406
+ }
407
+ return null;
408
+ }
409
+ /**
410
+ * Update the current user's password.
411
+ * Backend dispatch 'updatePassword' → data { currentPassword, newPassword }.
412
+ */
413
+ async updatePassword(currentPassword, newPassword) {
414
+ if (!this._currentUser) {
415
+ throw new MAKEMINDError("UNAUTHENTICATED", "No user signed in");
416
+ }
417
+ await this.apiClient.dispatch("updatePassword", { currentPassword, newPassword });
418
+ }
419
+ /**
420
+ * Send email verification.
421
+ * Backend dispatch 'resendVerificationEmail' — uid resolved from auth context, no payload needed.
422
+ */
423
+ async sendEmailVerification() {
424
+ if (!this._currentUser) {
425
+ throw new MAKEMINDError("UNAUTHENTICATED", "No user signed in");
426
+ }
427
+ await this.apiClient.dispatch("resendVerificationEmail");
428
+ }
429
+ /**
430
+ * Verify email with code
431
+ */
432
+ async verifyEmail(code) {
433
+ await this.apiClient.post("/auth/verify-email", {
434
+ code,
435
+ applicationId: this.applicationId
436
+ }, { skipAuth: true });
437
+ }
438
+ /**
439
+ * Confirm password reset with code
440
+ */
441
+ async confirmPasswordReset(code, newPassword) {
442
+ await this.apiClient.post("/auth/confirm-reset", {
443
+ code,
444
+ newPassword,
445
+ applicationId: this.applicationId
446
+ }, { skipAuth: true });
447
+ }
448
+ /**
449
+ * OAuth redirect-based sign-in is NOT supported by this backend.
450
+ * The backend has no /oauth/initiate or /oauth/callback routes.
451
+ * Use signInWithOAuthToken() with a provider-issued ID token instead,
452
+ * or use the Firebase Client SDK directly for OAuth redirect flows.
453
+ */
454
+ async signInWithOAuth(_provider) {
455
+ throw new MAKEMINDError(
456
+ "NOT_SUPPORTED",
457
+ "OAuth redirect flow is not supported. Use signInWithOAuthToken() with a provider ID token, or the Firebase Client SDK."
458
+ );
459
+ }
460
+ /**
461
+ * OAuth redirect callback handling is NOT supported by this backend.
462
+ * Use signInWithOAuthToken() with a provider-issued ID token instead,
463
+ * or use the Firebase Client SDK directly for OAuth redirect flows.
464
+ */
465
+ async handleOAuthCallback(_code, _state) {
466
+ throw new MAKEMINDError(
467
+ "NOT_SUPPORTED",
468
+ "OAuth redirect callback is not supported. Use signInWithOAuthToken() with a provider ID token, or the Firebase Client SDK."
469
+ );
470
+ }
471
+ /**
472
+ * Sign in with OAuth ID token directly (for native apps)
473
+ * Use this when you have already obtained the ID token from the OAuth provider
474
+ */
475
+ async signInWithOAuthToken(provider, idToken) {
476
+ try {
477
+ const response = await this.apiClient.post("/auth/oauth/token", {
478
+ provider,
479
+ idToken,
480
+ applicationId: this.applicationId
481
+ }, { skipAuth: true });
482
+ this._currentUser = response.user;
483
+ this.refreshToken = response.refreshToken ?? null;
484
+ this._memberships = response.memberships || [];
485
+ this.setAuthToken(response.token);
486
+ this.persistSession(response.user, response.token, response.refreshToken, response.memberships);
487
+ this.onAuthStateChange(response.user);
488
+ this.notifyAuthStateCallbacks(response.user);
489
+ return response.user;
490
+ } catch (error) {
491
+ if (error instanceof MAKEMINDError) {
492
+ throw error;
493
+ }
494
+ throw new MAKEMINDError("AUTH_ERROR", "OAuth token authentication failed");
495
+ }
496
+ }
497
+ /**
498
+ * OAuth redirect-based account linking is NOT supported by this backend.
499
+ * The backend has no /oauth/link/initiate route.
500
+ * Use the Firebase Client SDK linkWithRedirect() or linkWithPopup() instead.
501
+ */
502
+ async linkWithOAuth(_provider) {
503
+ throw new MAKEMINDError(
504
+ "NOT_SUPPORTED",
505
+ "OAuth redirect link flow is not supported. Use the Firebase Client SDK linkWithRedirect() or linkWithPopup()."
506
+ );
507
+ }
508
+ /**
509
+ * OAuth redirect link callback is NOT supported by this backend.
510
+ * The backend has no /oauth/link/callback route.
511
+ * Use the Firebase Client SDK for OAuth redirect link flows.
512
+ */
513
+ async handleOAuthLinkCallback(_code, _state) {
514
+ throw new MAKEMINDError(
515
+ "NOT_SUPPORTED",
516
+ "OAuth redirect link callback is not supported. Use the Firebase Client SDK for OAuth link flows."
517
+ );
518
+ }
519
+ /**
520
+ * Unlink OAuth provider from current account
521
+ */
522
+ async unlinkOAuth(provider) {
523
+ if (!this._currentUser) {
524
+ throw new MAKEMINDError("UNAUTHENTICATED", "No user signed in");
525
+ }
526
+ await this.apiClient.post("/auth/oauth/unlink", {
527
+ provider
528
+ });
529
+ }
530
+ /**
531
+ * Get linked OAuth providers for current user
532
+ */
533
+ async getLinkedProviders() {
534
+ if (!this._currentUser) {
535
+ throw new MAKEMINDError("UNAUTHENTICATED", "No user signed in");
536
+ }
537
+ const response = await this.apiClient.get("/auth/oauth/providers");
538
+ return response.providers;
539
+ }
540
+ // Update the current user in the session (e.g., after tenant creation)
541
+ updateCurrentUser(userData, membership) {
542
+ const user = {
543
+ uid: userData.uid,
544
+ email: userData.email,
545
+ displayName: userData.displayName || null,
546
+ photoURL: this._currentUser?.photoURL || null,
547
+ tenantId: userData.tenantId,
548
+ roles: userData.roles,
549
+ scopes: userData.scopes
550
+ };
551
+ this._currentUser = user;
552
+ this.onAuthStateChange(user);
553
+ this.notifyAuthStateCallbacks(user);
554
+ if (membership) {
555
+ const existingIndex = this._memberships.findIndex((m) => m.tenantId === membership.tenantId);
556
+ if (existingIndex >= 0) {
557
+ this._memberships[existingIndex] = membership;
558
+ } else {
559
+ this._memberships.push(membership);
560
+ }
561
+ }
562
+ const storedToken = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN) : null;
563
+ if (storedToken) {
564
+ this.persistSession(user, storedToken, this.refreshToken ?? "", this._memberships);
565
+ }
566
+ }
567
+ };
568
+
569
+ // src/services/data.ts
570
+ var MAKEMINDData = class {
571
+ constructor(config) {
572
+ this.subscriptions = /* @__PURE__ */ new Map();
573
+ this.apiClient = config.apiClient;
574
+ this.applicationId = config.applicationId;
575
+ this.getTenantId = config.tenantId;
576
+ }
577
+ get tenantId() {
578
+ const id = this.getTenantId();
579
+ if (!id) {
580
+ throw new MAKEMINDError("UNAUTHENTICATED", "No tenant context set");
581
+ }
582
+ return id;
583
+ }
584
+ async query(options) {
585
+ const filters = options.where?.map(({ field, op, value }) => ({
586
+ field,
587
+ operator: op,
588
+ value
589
+ }));
590
+ const res = await this.apiClient.dispatch("dataQuery", {
591
+ collection: options.collection,
592
+ // The service applies field-level conditions from `filters` (it adds the
593
+ // `data.` prefix automatically); `where` is reserved for $and/$or logic.
594
+ filters,
595
+ sort: options.orderBy,
596
+ limit: options.limit,
597
+ startAfter: options.startAfter
598
+ });
599
+ return {
600
+ data: res.results ?? [],
601
+ metadata: { hasMore: res.hasMore ?? false, lastDocId: res.nextCursor }
602
+ };
603
+ }
604
+ async get(options) {
605
+ try {
606
+ const response = await this.apiClient.get(
607
+ `/data/${options.collection}/${options.documentId}`
608
+ );
609
+ return response;
610
+ } catch (error) {
611
+ if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
612
+ return null;
613
+ }
614
+ throw error;
615
+ }
616
+ }
617
+ async create(options) {
618
+ const response = await this.apiClient.post(`/data/${options.collection}`, {
619
+ ...options.data,
620
+ documentId: options.documentId
621
+ });
622
+ return response;
623
+ }
624
+ async update(options) {
625
+ await this.apiClient.patch(`/data/${options.collection}/${options.documentId}`, options.data);
626
+ }
627
+ async delete(options) {
628
+ await this.apiClient.delete(`/data/${options.collection}/${options.documentId}`);
629
+ }
630
+ onSnapshot(options, callback) {
631
+ const subscriptionId = `doc:${options.collection}:${options.documentId}`;
632
+ this.createSubscription(
633
+ `/data/subscribe/${options.collection}/${options.documentId}`,
634
+ subscriptionId,
635
+ callback
636
+ );
637
+ return () => {
638
+ this.closeSubscription(subscriptionId);
639
+ };
640
+ }
641
+ onCollectionSnapshot(options, callback) {
642
+ const subscriptionId = `collection:${options.collection}:${JSON.stringify(options)}`;
643
+ this.createCollectionSubscription(options, subscriptionId, callback);
644
+ return () => {
645
+ this.closeSubscription(subscriptionId);
646
+ };
647
+ }
648
+ createSubscription(endpoint, subscriptionId, callback) {
649
+ this.closeSubscription(subscriptionId);
650
+ if (typeof EventSource !== "undefined") {
651
+ const url = `${this.apiClient.getBaseUrl()}${endpoint}`;
652
+ const eventSource = new EventSource(url, { withCredentials: true });
653
+ eventSource.onmessage = (event) => {
654
+ try {
655
+ const message = JSON.parse(event.data);
656
+ if (message.type === "data") {
657
+ callback(message.data);
658
+ }
659
+ } catch {
660
+ }
661
+ };
662
+ eventSource.onerror = () => {
663
+ };
664
+ this.subscriptions.set(subscriptionId, eventSource);
665
+ } else {
666
+ this.pollForUpdates(endpoint, subscriptionId, callback);
667
+ }
668
+ }
669
+ createCollectionSubscription(options, subscriptionId, callback) {
670
+ this.closeSubscription(subscriptionId);
671
+ const poll = async () => {
672
+ try {
673
+ const result = await this.query(options);
674
+ callback(result.data);
675
+ } catch {
676
+ }
677
+ };
678
+ poll();
679
+ const intervalId = setInterval(poll, 5e3);
680
+ this.subscriptions.set(subscriptionId, {
681
+ close: () => clearInterval(intervalId)
682
+ });
683
+ }
684
+ pollForUpdates(endpoint, subscriptionId, callback) {
685
+ const poll = async () => {
686
+ try {
687
+ const data = await this.apiClient.get(endpoint);
688
+ callback(data);
689
+ } catch {
690
+ }
691
+ };
692
+ poll();
693
+ const intervalId = setInterval(poll, 5e3);
694
+ this.subscriptions.set(subscriptionId, {
695
+ close: () => clearInterval(intervalId)
696
+ });
697
+ }
698
+ closeSubscription(subscriptionId) {
699
+ const subscription = this.subscriptions.get(subscriptionId);
700
+ if (subscription) {
701
+ if (subscription instanceof EventSource) {
702
+ subscription.close();
703
+ } else if (typeof subscription.close === "function") {
704
+ subscription.close();
705
+ }
706
+ this.subscriptions.delete(subscriptionId);
707
+ }
708
+ }
709
+ // collectionGroupQuery: no backend route or onCall handler exists for cross-
710
+ // collection group queries. REST /data/collection-group-query is not registered
711
+ // and no `dataCollectionGroupQuery` onCall is exported from data-platform.
712
+ // Callers must use `query()` against a specific collection. This method is
713
+ // intentionally left as a not-implemented guard so TS callers compile and get
714
+ // a clear runtime error rather than a silent 404 or wrong-handler response.
715
+ async collectionGroupQuery(_options) {
716
+ throw new MAKEMINDError(
717
+ "NOT_IMPLEMENTED",
718
+ "collectionGroupQuery is not yet supported by the backend. Use query() against a specific collection."
719
+ );
720
+ }
721
+ // Convenience methods with tenant isolation
722
+ async queryTenant(collectionName, additionalConditions, orderByOptions, limitCount) {
723
+ const conditions = [
724
+ ...additionalConditions || []
725
+ ];
726
+ return this.query({
727
+ collection: collectionName,
728
+ where: conditions,
729
+ orderBy: orderByOptions,
730
+ limit: limitCount
731
+ });
732
+ }
733
+ // Batch operations
734
+ //
735
+ // All three batch methods route through the `dataBatchWrite` onCall handler
736
+ // (dispatch op `dataBatchWrite`). The backend BatchWriteInput shape is:
737
+ // { operations: [{ type, collection?, id?, data? }] }
738
+ // There are no separate REST PATCH /batch or POST /batch-delete routes.
739
+ async batchCreate(collection, documents) {
740
+ const operations = documents.map(({ data, documentId }) => ({
741
+ type: "create",
742
+ collection,
743
+ ...documentId ? { id: documentId } : {},
744
+ data
745
+ }));
746
+ const response = await this.apiClient.dispatch(
747
+ "dataBatchWrite",
748
+ { operations }
749
+ );
750
+ return response.results.map((r) => ({ id: r.id, data: {} }));
751
+ }
752
+ async batchUpdate(collection, updates) {
753
+ const operations = updates.map(({ documentId, data }) => ({
754
+ type: "update",
755
+ id: documentId,
756
+ data
757
+ }));
758
+ await this.apiClient.dispatch("dataBatchWrite", { operations });
759
+ }
760
+ async batchDelete(collection, documentIds) {
761
+ const operations = documentIds.map((id) => ({
762
+ type: "delete",
763
+ id
764
+ }));
765
+ await this.apiClient.dispatch("dataBatchWrite", { operations });
766
+ }
767
+ // Export/Import operations
768
+ //
769
+ // Backend uses async job pattern via onCall handlers:
770
+ // dataExportCollection → returns { exportId, status }
771
+ // dataImportCollection → returns { importId, status } (sourcePath required)
772
+ // dataGetExportStatus → returns full ExportJob by { exportId }
773
+ // dataGetImportStatus → returns full ImportJob by { importId }
774
+ //
775
+ // There are no flat REST routes /data/export or /data/import; the collection-
776
+ // scoped synchronous REST routes (POST /:collection/export, POST /:collection/import)
777
+ // exist for simple inline operations but do not produce jobIds. All job-based
778
+ // export/import goes through dispatch.
779
+ //
780
+ // Import requires the caller to upload the file to Cloud Storage first (under
781
+ // imports/<tenantId>/ or uploads/<tenantId>/) and pass the GCS path as
782
+ // `sourcePath`. The `data` parameter accepted here must be a GCS path string;
783
+ // array-of-records import is not supported by the onCall handler.
784
+ async exportCollection(options) {
785
+ const res = await this.apiClient.dispatch(
786
+ "dataExportCollection",
787
+ {
788
+ collection: options.collection,
789
+ format: options.format,
790
+ filter: options.filter
791
+ }
792
+ );
793
+ return { jobId: res.exportId, status: res.status };
794
+ }
795
+ async importCollection(options) {
796
+ const res = await this.apiClient.dispatch(
797
+ "dataImportCollection",
798
+ {
799
+ collection: options.collection,
800
+ sourcePath: options.sourcePath,
801
+ format: options.format,
802
+ upsert: options.upsert
803
+ }
804
+ );
805
+ return { jobId: res.importId, status: res.status };
806
+ }
807
+ async getExportStatus(jobId) {
808
+ const res = await this.apiClient.dispatch("dataGetExportStatus", { exportId: jobId });
809
+ return {
810
+ jobId: res.id,
811
+ status: res.status,
812
+ documentCount: res.documentCount,
813
+ downloadUrl: res.downloadUrl,
814
+ error: res.error,
815
+ completedAt: res.completedAt
816
+ };
817
+ }
818
+ async getImportStatus(jobId) {
819
+ const res = await this.apiClient.dispatch("dataGetImportStatus", { importId: jobId });
820
+ return {
821
+ jobId: res.id,
822
+ status: res.status,
823
+ totalRecords: res.totalRecords,
824
+ processedRecords: res.processedRecords,
825
+ skippedRecords: res.skippedRecords,
826
+ failedRecords: res.failedRecords,
827
+ error: res.error,
828
+ completedAt: res.completedAt
829
+ };
830
+ }
831
+ // Aggregation operations
832
+ //
833
+ // Backend onCall: `dataAggregate` — accepts AggregateInput:
834
+ // { collection, filters: [{ field, operator, value }], aggregations }
835
+ // No REST route /data/aggregate exists.
836
+ async aggregate(options) {
837
+ const filters = options.filters?.map(({ field, op, value }) => ({
838
+ field,
839
+ operator: op,
840
+ value
841
+ }));
842
+ return this.apiClient.dispatch("dataAggregate", {
843
+ collection: options.collection,
844
+ filters,
845
+ aggregations: options.aggregations
846
+ });
847
+ }
848
+ // Full-text search
849
+ //
850
+ // Backend onCall: `dataSearch` — accepts:
851
+ // { collection, query, fields, limit? }
852
+ // No REST route /data/search exists.
853
+ async search(options) {
854
+ return this.apiClient.dispatch("dataSearch", {
855
+ collection: options.collection,
856
+ query: options.query,
857
+ fields: options.fields,
858
+ limit: options.limit
859
+ });
860
+ }
861
+ };
862
+
863
+ // src/services/logic.ts
864
+ var MAKEMINDLogic = class {
865
+ constructor(config) {
866
+ this.apiClient = config.apiClient;
867
+ this.getTenantId = config.tenantId;
868
+ }
869
+ // Generic transition for any workflow
870
+ async transition(options) {
871
+ const response = await this.apiClient.post("/logic/transition", {
872
+ ...options,
873
+ tenantId: this.getTenantId()
874
+ });
875
+ return response;
876
+ }
877
+ // Call a logic function directly
878
+ async call(functionName, input) {
879
+ const response = await this.apiClient.post("/logic/call", {
880
+ functionName,
881
+ input,
882
+ tenantId: this.getTenantId()
883
+ });
884
+ return response;
885
+ }
886
+ // Execute a flow
887
+ async executeFlow(flowId, input, options) {
888
+ const response = await this.apiClient.post("/logic/execute", {
889
+ flowId,
890
+ input,
891
+ idempotencyKey: options?.idempotencyKey,
892
+ tenantId: this.getTenantId()
893
+ });
894
+ return response;
895
+ }
896
+ // Get execution status
897
+ async getExecution(executionId) {
898
+ return this.apiClient.get(`/logic/executions/${executionId}`);
899
+ }
900
+ // List executions
901
+ async listExecutions(options) {
902
+ const params = new URLSearchParams();
903
+ if (options?.flowId) params.append("flowId", options.flowId);
904
+ if (options?.status) params.append("status", options.status);
905
+ if (options?.limit) params.append("limit", String(options.limit));
906
+ if (options?.startAfter) params.append("startAfter", options.startAfter);
907
+ return this.apiClient.get(`/logic/executions?${params.toString()}`);
908
+ }
909
+ // Send signal to a waiting workflow
910
+ async sendSignal(executionId, signalName, payload) {
911
+ return this.apiClient.post(`/logic/executions/${executionId}/signal`, {
912
+ signalName,
913
+ payload
914
+ });
915
+ }
916
+ /**
917
+ * Cancel a running workflow execution
918
+ */
919
+ async cancelWorkflow(executionId, reason) {
920
+ return this.apiClient.post(`/logic/executions/${executionId}/cancel`, {
921
+ reason
922
+ });
923
+ }
924
+ /**
925
+ * Retry a failed workflow execution
926
+ */
927
+ async retryWorkflow(executionId) {
928
+ return this.apiClient.post(`/logic/executions/${executionId}/retry`, {});
929
+ }
930
+ /**
931
+ * Pause a running workflow execution
932
+ */
933
+ async pauseWorkflow(executionId) {
934
+ return this.apiClient.post(`/logic/executions/${executionId}/pause`, {});
935
+ }
936
+ /**
937
+ * Resume a paused workflow execution
938
+ */
939
+ async resumeWorkflow(executionId) {
940
+ return this.apiClient.post(`/logic/executions/${executionId}/resume`, {});
941
+ }
942
+ // ECO (Engineering Change Order) workflow helpers
943
+ async ecoTransition(ecoId, action, data) {
944
+ return this.transition({
945
+ workflowName: "eco",
946
+ documentId: ecoId,
947
+ action,
948
+ data
949
+ });
950
+ }
951
+ // NCR (Non-Conformance Report) workflow helpers
952
+ async ncrTransition(issueId, action, data) {
953
+ return this.transition({
954
+ workflowName: "ncr",
955
+ documentId: issueId,
956
+ action,
957
+ data
958
+ });
959
+ }
960
+ // Bid workflow helpers
961
+ async bidTransition(requestId, bidId, action, data) {
962
+ return this.transition({
963
+ workflowName: "bid",
964
+ documentId: `${requestId}:${bidId}`,
965
+ action,
966
+ data
967
+ });
968
+ }
969
+ // Gate approval
970
+ async gateApproval(productId, gate, action, comment) {
971
+ return this.transition({
972
+ workflowName: "gate",
973
+ documentId: productId,
974
+ action: `${gate}_${action}`,
975
+ data: comment ? { comment } : void 0
976
+ });
977
+ }
978
+ // Document access with watermark
979
+ async accessDocument(documentId, action) {
980
+ return this.apiClient.post("/logic/document-access", {
981
+ documentId,
982
+ action,
983
+ tenantId: this.getTenantId()
984
+ });
985
+ }
986
+ // Flow management
987
+ async createFlow(flow) {
988
+ return this.apiClient.post("/logic/flows", {
989
+ ...flow,
990
+ tenantId: this.getTenantId()
991
+ });
992
+ }
993
+ async updateFlow(flowId, updates) {
994
+ await this.apiClient.patch(`/logic/flows/${flowId}`, updates);
995
+ }
996
+ async publishFlow(flowId) {
997
+ await this.apiClient.post(`/logic/flows/${flowId}/publish`, {});
998
+ }
999
+ async archiveFlow(flowId) {
1000
+ await this.apiClient.post(`/logic/flows/${flowId}/archive`, {});
1001
+ }
1002
+ /**
1003
+ * A page of the tenant's flows.
1004
+ *
1005
+ * Took no arguments and returned a bare array, so a caller wanting one
1006
+ * state — published flows, to offer as event handlers — pulled every state
1007
+ * and filtered locally. The server has always been able to filter; asking
1008
+ * it to means the page is spent on rows the caller wants.
1009
+ *
1010
+ * `hasMore` is returned rather than implied. A full page and a truncated
1011
+ * one are the same array, and a caller that cannot tell them apart quietly
1012
+ * drops whatever did not fit.
1013
+ */
1014
+ async listFlows(params = {}) {
1015
+ const query = new URLSearchParams();
1016
+ if (params.status) query.set("status", params.status);
1017
+ if (params.mode) query.set("mode", params.mode);
1018
+ if (params.limit !== void 0) query.set("limit", String(params.limit));
1019
+ if (params.cursor) query.set("cursor", params.cursor);
1020
+ const suffix = query.toString();
1021
+ const response = await this.apiClient.get(`/logic/flows${suffix ? `?${suffix}` : ""}`);
1022
+ return {
1023
+ flows: response.flows,
1024
+ hasMore: response.hasMore === true,
1025
+ nextCursor: response.nextCursor
1026
+ };
1027
+ }
1028
+ async getFlow(flowId) {
1029
+ return this.apiClient.get(`/logic/flows/${flowId}`);
1030
+ }
1031
+ async testFlow(flowId, testInput) {
1032
+ return this.apiClient.post(`/logic/flows/${flowId}/test`, {
1033
+ input: testInput
1034
+ });
1035
+ }
1036
+ async cloneFlow(flowId, newName) {
1037
+ return this.apiClient.post(`/logic/flows/${flowId}/clone`, {
1038
+ name: newName
1039
+ });
1040
+ }
1041
+ // Variables management
1042
+ async listVariables() {
1043
+ const response = await this.apiClient.get("/logic/variables");
1044
+ return response.variables;
1045
+ }
1046
+ async getVariable(variableId) {
1047
+ return this.apiClient.get(`/logic/variables/${variableId}`);
1048
+ }
1049
+ async createVariable(data) {
1050
+ return this.apiClient.post("/logic/variables", {
1051
+ ...data,
1052
+ tenantId: this.getTenantId()
1053
+ });
1054
+ }
1055
+ async updateVariable(variableId, data) {
1056
+ await this.apiClient.patch(`/logic/variables/${variableId}`, data);
1057
+ }
1058
+ async deleteVariable(variableId) {
1059
+ await this.apiClient.delete(`/logic/variables/${variableId}`);
1060
+ }
1061
+ // Templates management
1062
+ async listTemplates() {
1063
+ const response = await this.apiClient.get("/logic/templates");
1064
+ return response.templates;
1065
+ }
1066
+ async getTemplate(templateId) {
1067
+ return this.apiClient.get(`/logic/templates/${templateId}`);
1068
+ }
1069
+ async createFlowFromTemplate(templateId, data) {
1070
+ return this.apiClient.post(`/logic/templates/${templateId}/instantiate`, {
1071
+ ...data,
1072
+ tenantId: this.getTenantId()
1073
+ });
1074
+ }
1075
+ // Webhooks management
1076
+ async listWebhooks() {
1077
+ const response = await this.apiClient.get("/logic/webhooks");
1078
+ return response.webhooks;
1079
+ }
1080
+ async createWebhook(data) {
1081
+ return this.apiClient.post("/logic/webhooks", {
1082
+ ...data,
1083
+ tenantId: this.getTenantId()
1084
+ });
1085
+ }
1086
+ async deleteWebhook(webhookId) {
1087
+ await this.apiClient.delete(`/logic/webhooks/${webhookId}`);
1088
+ }
1089
+ async rotateWebhookSecret(webhookId) {
1090
+ return this.apiClient.post(`/logic/webhooks/${webhookId}/rotate-secret`, {});
1091
+ }
1092
+ // Approvals management
1093
+ async listPendingApprovals() {
1094
+ const response = await this.apiClient.get("/logic/approvals/pending");
1095
+ return response.approvals;
1096
+ }
1097
+ async approveRequest(approvalId, comment) {
1098
+ await this.apiClient.post(`/logic/approvals/${approvalId}/approve`, { comment });
1099
+ }
1100
+ async rejectRequest(approvalId, reason) {
1101
+ await this.apiClient.post(`/logic/approvals/${approvalId}/reject`, { reason });
1102
+ }
1103
+ // Data Triggers management
1104
+ async listDataTriggers() {
1105
+ const response = await this.apiClient.get("/logic/data-triggers");
1106
+ return response.triggers;
1107
+ }
1108
+ async createDataTrigger(data) {
1109
+ return this.apiClient.post("/logic/data-triggers", {
1110
+ ...data,
1111
+ tenantId: this.getTenantId()
1112
+ });
1113
+ }
1114
+ async deleteDataTrigger(triggerId) {
1115
+ await this.apiClient.delete(`/logic/data-triggers/${triggerId}`);
1116
+ }
1117
+ async toggleDataTrigger(triggerId, isActive) {
1118
+ await this.apiClient.patch(`/logic/data-triggers/${triggerId}`, { isActive });
1119
+ }
1120
+ // AI Assistant
1121
+ async generateFlowFromPrompt(prompt) {
1122
+ return this.apiClient.post("/logic/ai/generate", {
1123
+ prompt,
1124
+ tenantId: this.getTenantId()
1125
+ });
1126
+ }
1127
+ async explainFlow(flowId) {
1128
+ return this.apiClient.post(`/logic/flows/${flowId}/explain`, {});
1129
+ }
1130
+ };
1131
+
1132
+ // src/services/tenant.ts
1133
+ var MAKEMINDTenant = class {
1134
+ constructor(config) {
1135
+ this.apiClient = config.apiClient;
1136
+ this.getTenantId = config.tenantId;
1137
+ this.onTenantCreated = config.onTenantCreated;
1138
+ }
1139
+ get tenantId() {
1140
+ const id = this.getTenantId();
1141
+ if (!id) {
1142
+ throw new MAKEMINDError("UNAUTHENTICATED", "No tenant context set");
1143
+ }
1144
+ return id;
1145
+ }
1146
+ // Create a new tenant via API Gateway
1147
+ async createTenant(data) {
1148
+ const requestBody = {
1149
+ name: data.name,
1150
+ displayName: data.settings?.displayName || data.name,
1151
+ type: data.settings?.type || "company",
1152
+ mode: data.mode,
1153
+ settings: data.settings
1154
+ };
1155
+ const response = await this.apiClient.post("/tenants", requestBody);
1156
+ if (this.onTenantCreated) {
1157
+ await this.onTenantCreated(response.tenantId, response.customToken);
1158
+ }
1159
+ return {
1160
+ id: response.tenantId,
1161
+ name: data.name,
1162
+ workspaceId: response.workspaceId,
1163
+ status: response.status,
1164
+ user: response.user,
1165
+ customToken: response.customToken
1166
+ };
1167
+ }
1168
+ // Get current tenant
1169
+ async getCurrent() {
1170
+ try {
1171
+ const response = await this.apiClient.get(`/tenants/${this.tenantId}`);
1172
+ return response;
1173
+ } catch (error) {
1174
+ if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
1175
+ return null;
1176
+ }
1177
+ throw error;
1178
+ }
1179
+ }
1180
+ // Get tenant by ID
1181
+ async getTenant(tenantId) {
1182
+ try {
1183
+ const response = await this.apiClient.get(`/tenants/${tenantId}`);
1184
+ return response;
1185
+ } catch (error) {
1186
+ if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
1187
+ return null;
1188
+ }
1189
+ throw error;
1190
+ }
1191
+ }
1192
+ // Stream tenant changes (polling-based for API)
1193
+ onTenantSnapshot(callback) {
1194
+ let isSubscribed = true;
1195
+ const poll = async () => {
1196
+ if (!isSubscribed) return;
1197
+ try {
1198
+ const tenant = await this.getCurrent();
1199
+ callback(tenant);
1200
+ } catch {
1201
+ }
1202
+ if (isSubscribed) {
1203
+ setTimeout(poll, 1e4);
1204
+ }
1205
+ };
1206
+ poll();
1207
+ return () => {
1208
+ isSubscribed = false;
1209
+ };
1210
+ }
1211
+ // Update tenant settings.
1212
+ // Backend allowedFields: displayName, settings, tags, metadata, canHostSubtenants, maxSubtenants.
1213
+ // The field 'name' is NOT accepted by the backend PATCH /tenants/:tenantId handler.
1214
+ async updateTenant(updates) {
1215
+ await this.apiClient.patch(`/tenants/${this.tenantId}`, updates);
1216
+ }
1217
+ // Member operations
1218
+ // List tenant members
1219
+ async listMembers() {
1220
+ const response = await this.apiClient.get(
1221
+ `/tenants/${this.tenantId}/members`
1222
+ );
1223
+ return response.members;
1224
+ }
1225
+ // Invite a member.
1226
+ // Backend body: { email, roles: string[] } — uses 'roles' array, not singular 'role'.
1227
+ async inviteMember(data) {
1228
+ await this.apiClient.post(`/tenants/${this.tenantId}/members/invite`, {
1229
+ email: data.email,
1230
+ roles: data.roles
1231
+ });
1232
+ }
1233
+ // Update member roles.
1234
+ // Backend body: { roles: string[] } — uses 'roles' array, not singular 'role'.
1235
+ async updateMember(userId, updates) {
1236
+ await this.apiClient.patch(`/tenants/${this.tenantId}/members/${userId}`, {
1237
+ roles: updates.roles
1238
+ });
1239
+ }
1240
+ // Remove a member
1241
+ async removeMember(userId) {
1242
+ await this.apiClient.delete(`/tenants/${this.tenantId}/members/${userId}`);
1243
+ }
1244
+ // Workspace operations
1245
+ // List workspaces
1246
+ async listWorkspaces() {
1247
+ const response = await this.apiClient.dispatch(
1248
+ "listWorkspaces",
1249
+ {}
1250
+ );
1251
+ return response.workspaces;
1252
+ }
1253
+ // Create workspace via gateway dispatch (createWorkspace onCall).
1254
+ // Backend data: { name, displayName, settings?, type? } → returns { workspaceId: string }.
1255
+ async createWorkspace(data) {
1256
+ return this.apiClient.dispatch("createWorkspace", {
1257
+ name: data.name,
1258
+ displayName: data.displayName,
1259
+ settings: data.settings,
1260
+ type: data.type
1261
+ });
1262
+ }
1263
+ // Update workspace via gateway dispatch (updateWorkspace onCall).
1264
+ // Backend data: { workspaceId, displayName?, settings? } → returns { success: boolean }.
1265
+ async updateWorkspace(workspaceId, updates) {
1266
+ return this.apiClient.dispatch("updateWorkspace", {
1267
+ workspaceId,
1268
+ displayName: updates.displayName,
1269
+ settings: updates.settings
1270
+ });
1271
+ }
1272
+ // Delete workspace via gateway dispatch (deleteWorkspace onCall).
1273
+ // Backend data: { workspaceId } → returns { success: boolean }.
1274
+ async deleteWorkspace(workspaceId) {
1275
+ return this.apiClient.dispatch("deleteWorkspace", { workspaceId });
1276
+ }
1277
+ };
1278
+
1279
+ // src/services/messaging.ts
1280
+ var MAKEMINDMessaging = class {
1281
+ constructor(config) {
1282
+ this.apiClient = config.apiClient;
1283
+ this.applicationId = config.applicationId;
1284
+ this.getTenantId = config.tenantId;
1285
+ this.getUserId = config.userId;
1286
+ this.firebaseApp = config.firebaseApp;
1287
+ this.vapidKey = config.vapidKey;
1288
+ }
1289
+ get tenantId() {
1290
+ const id = this.getTenantId();
1291
+ if (!id) {
1292
+ throw new MAKEMINDError("UNAUTHENTICATED", "No tenant context set");
1293
+ }
1294
+ return id;
1295
+ }
1296
+ get userId() {
1297
+ const id = this.getUserId();
1298
+ if (!id) {
1299
+ throw new MAKEMINDError("UNAUTHENTICATED", "No user context set");
1300
+ }
1301
+ return id;
1302
+ }
1303
+ async getMessaging() {
1304
+ if (!this.messaging) {
1305
+ if (!this.firebaseApp) {
1306
+ throw new MAKEMINDError("CONFIGURATION_ERROR", "Firebase app not configured for push notifications");
1307
+ }
1308
+ const { getMessaging } = await import("firebase/messaging");
1309
+ this.messaging = getMessaging(this.firebaseApp);
1310
+ }
1311
+ return this.messaging;
1312
+ }
1313
+ generateDeviceId() {
1314
+ return `device_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1315
+ }
1316
+ /**
1317
+ * Send a message using a template via the sendBatchMessages onCall.
1318
+ * Maps SDK SendMessageOptions to the batch service: one call per channel.
1319
+ */
1320
+ async send(options) {
1321
+ const channels = options.channels || ["push", "email"];
1322
+ const recipients = options.recipients.map((userId) => ({ userId }));
1323
+ let firstMessageId = "";
1324
+ for (const channelType of channels) {
1325
+ const response = await this.apiClient.dispatch(
1326
+ "messaging-sendBatchMessages",
1327
+ {
1328
+ channelType,
1329
+ templateId: options.template,
1330
+ recipients,
1331
+ params: options.data
1332
+ }
1333
+ );
1334
+ if (!firstMessageId && response.results.length > 0) {
1335
+ firstMessageId = response.results[0].messageId;
1336
+ }
1337
+ }
1338
+ return { messageId: firstMessageId };
1339
+ }
1340
+ /**
1341
+ * Send notification to specific users across channels.
1342
+ * Uses sendBatchMessages onCall (one dispatch per channel).
1343
+ */
1344
+ async notify(userIds, notification) {
1345
+ const channels = notification.channels || ["push"];
1346
+ const recipients = userIds.map((userId) => ({ userId }));
1347
+ let firstMessageId = "";
1348
+ for (const channelType of channels) {
1349
+ const response = await this.apiClient.dispatch(
1350
+ "messaging-sendBatchMessages",
1351
+ {
1352
+ channelType,
1353
+ recipients,
1354
+ title: notification.title,
1355
+ body: notification.body,
1356
+ priority: "normal"
1357
+ }
1358
+ );
1359
+ if (!firstMessageId && response.results.length > 0) {
1360
+ firstMessageId = response.results[0].messageId;
1361
+ }
1362
+ }
1363
+ return { messageId: firstMessageId };
1364
+ }
1365
+ /**
1366
+ * Register device for push notifications.
1367
+ * SDK internally handles token acquisition (Firebase FCM).
1368
+ * Uses the REST /messaging/devices route (already correct).
1369
+ */
1370
+ async registerDevice(options) {
1371
+ const { platform, deviceId } = options;
1372
+ const finalDeviceId = deviceId || this.generateDeviceId();
1373
+ const token = await this.acquirePushToken();
1374
+ await this.apiClient.post("/messaging/devices", {
1375
+ token,
1376
+ platform
1377
+ });
1378
+ this.currentDeviceId = finalDeviceId;
1379
+ return { deviceId: finalDeviceId };
1380
+ }
1381
+ /**
1382
+ * Internal: Acquire push token from the platform's push service.
1383
+ * Currently uses Firebase FCM, abstracted for future cloud-agnostic support.
1384
+ */
1385
+ async acquirePushToken() {
1386
+ if (!this.vapidKey) {
1387
+ throw new MAKEMINDError("CONFIGURATION_ERROR", "VAPID key not configured for push notifications");
1388
+ }
1389
+ try {
1390
+ const messaging = await this.getMessaging();
1391
+ const { getToken } = await import("firebase/messaging");
1392
+ const token = await getToken(messaging, { vapidKey: this.vapidKey });
1393
+ if (!token) {
1394
+ throw new MAKEMINDError("MESSAGING_ERROR", "Failed to acquire push token");
1395
+ }
1396
+ return token;
1397
+ } catch (error) {
1398
+ if (error instanceof MAKEMINDError) throw error;
1399
+ throw new MAKEMINDError("MESSAGING_ERROR", "Failed to acquire push token: " + (error instanceof Error ? error.message : "Unknown error"));
1400
+ }
1401
+ }
1402
+ /**
1403
+ * Unregister device from push notifications.
1404
+ * Uses the REST DELETE /messaging/devices/:token route.
1405
+ * The route key is the raw FCM token (server base64-encodes it to deviceId).
1406
+ */
1407
+ async unregisterDevice(deviceId) {
1408
+ await this.apiClient.delete(`/messaging/devices/${deviceId}`);
1409
+ if (this.currentDeviceId === deviceId) {
1410
+ this.currentDeviceId = void 0;
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Listen for foreground push messages.
1415
+ */
1416
+ onPushMessage(callback) {
1417
+ let attached = null;
1418
+ let cancelled = false;
1419
+ void (async () => {
1420
+ const messaging = await this.getMessaging();
1421
+ const { onMessage } = await import("firebase/messaging");
1422
+ if (cancelled) return;
1423
+ attached = onMessage(messaging, (payload) => {
1424
+ callback({
1425
+ messageId: payload.messageId || "",
1426
+ title: payload.notification?.title,
1427
+ body: payload.notification?.body || "",
1428
+ data: payload.data
1429
+ });
1430
+ });
1431
+ })();
1432
+ return () => {
1433
+ cancelled = true;
1434
+ attached?.();
1435
+ attached = null;
1436
+ };
1437
+ }
1438
+ /**
1439
+ * Get in-app notifications for the current user.
1440
+ * Dispatch op: messaging-getInAppNotifications
1441
+ * Service contract: { userId?, unreadOnly?, limit?, cursor? }
1442
+ * Response: { notifications, hasMore, nextCursor }
1443
+ */
1444
+ async getNotifications(options) {
1445
+ const response = await this.apiClient.dispatch(
1446
+ "messaging-getInAppNotifications",
1447
+ {
1448
+ unreadOnly: options?.unreadOnly ?? false,
1449
+ limit: options?.limit ?? 50,
1450
+ cursor: options?.cursor
1451
+ }
1452
+ );
1453
+ return {
1454
+ notifications: response.notifications,
1455
+ nextCursor: response.nextCursor ?? void 0
1456
+ };
1457
+ }
1458
+ /**
1459
+ * Get in-app notifications stream (polling-based).
1460
+ */
1461
+ onNotifications(callback, options) {
1462
+ let isSubscribed = true;
1463
+ const poll = async () => {
1464
+ if (!isSubscribed) return;
1465
+ try {
1466
+ const result = await this.getNotifications({
1467
+ unreadOnly: options?.unreadOnly,
1468
+ limit: options?.limitCount || 50
1469
+ });
1470
+ callback(result.notifications);
1471
+ } catch {
1472
+ }
1473
+ if (isSubscribed) {
1474
+ setTimeout(poll, 5e3);
1475
+ }
1476
+ };
1477
+ poll();
1478
+ return () => {
1479
+ isSubscribed = false;
1480
+ };
1481
+ }
1482
+ /**
1483
+ * Get unread notification count.
1484
+ * Dispatch op: messaging-getUnreadNotificationCount (the verified onCall service).
1485
+ * Response: { userId, unreadCount }
1486
+ */
1487
+ async getUnreadCount() {
1488
+ const response = await this.apiClient.dispatch(
1489
+ "messaging-getUnreadNotificationCount",
1490
+ {}
1491
+ );
1492
+ return response.unreadCount;
1493
+ }
1494
+ /**
1495
+ * Get unread notification count stream (polling-based).
1496
+ */
1497
+ onUnreadCount(callback) {
1498
+ let isSubscribed = true;
1499
+ const poll = async () => {
1500
+ if (!isSubscribed) return;
1501
+ try {
1502
+ const count = await this.getUnreadCount();
1503
+ callback(count);
1504
+ } catch {
1505
+ }
1506
+ if (isSubscribed) {
1507
+ setTimeout(poll, 1e4);
1508
+ }
1509
+ };
1510
+ poll();
1511
+ return () => {
1512
+ isSubscribed = false;
1513
+ };
1514
+ }
1515
+ /**
1516
+ * Mark a notification as read.
1517
+ * Dispatch op: messaging-markNotificationRead
1518
+ * Service contract: { notificationId } or { notificationIds }
1519
+ */
1520
+ async markAsRead(notificationId) {
1521
+ await this.apiClient.dispatch(
1522
+ "messaging-markNotificationRead",
1523
+ { notificationId }
1524
+ );
1525
+ }
1526
+ /**
1527
+ * Mark all notifications as read for the current user.
1528
+ * Dispatch op: messaging-markAllNotificationsRead (new onCall — no existing match).
1529
+ * Implemented in backend send.ts + service/send.ts below.
1530
+ */
1531
+ async markAllAsRead() {
1532
+ await this.apiClient.dispatch(
1533
+ "messaging-markAllNotificationsRead",
1534
+ {}
1535
+ );
1536
+ }
1537
+ // PLM-specific notification helpers
1538
+ /**
1539
+ * Notify ECO reviewers.
1540
+ */
1541
+ async notifyEcoReviewers(eco, reviewerIds) {
1542
+ return this.send({
1543
+ template: "ecoReviewRequest",
1544
+ recipients: reviewerIds,
1545
+ data: {
1546
+ ecoNumber: eco.requestNumber,
1547
+ title: eco.title,
1548
+ priority: eco.priority,
1549
+ productCode: eco.productCode
1550
+ },
1551
+ channels: ["push", "email"]
1552
+ });
1553
+ }
1554
+ /**
1555
+ * Notify quality issue critical.
1556
+ */
1557
+ async notifyQualityCritical(issue, recipientIds) {
1558
+ return this.send({
1559
+ template: "qualityIssueCritical",
1560
+ recipients: recipientIds,
1561
+ data: {
1562
+ issueNumber: issue.issueNumber,
1563
+ title: issue.title,
1564
+ severity: issue.severity
1565
+ },
1566
+ channels: ["push", "email", "sms"]
1567
+ });
1568
+ }
1569
+ /**
1570
+ * Notify bid received.
1571
+ */
1572
+ async notifyBidReceived(request, bidder, ownerId) {
1573
+ return this.send({
1574
+ template: "bidReceived",
1575
+ recipients: [ownerId],
1576
+ data: {
1577
+ requestNumber: request.requestNumber,
1578
+ title: request.title,
1579
+ bidderName: bidder.name
1580
+ },
1581
+ channels: ["push", "email"]
1582
+ });
1583
+ }
1584
+ // Template Management
1585
+ /**
1586
+ * List available message templates.
1587
+ * Dispatch op: messaging-msgGetTemplates (the verified onCall service).
1588
+ * Service contract: { channelType? }
1589
+ * Response: { templates }
1590
+ */
1591
+ async listTemplates(options) {
1592
+ const response = await this.apiClient.dispatch(
1593
+ "messaging-msgGetTemplates",
1594
+ options?.channel ? { channelType: options.channel } : {}
1595
+ );
1596
+ return response.templates;
1597
+ }
1598
+ /**
1599
+ * Get a specific template by ID.
1600
+ * Dispatch op: messaging-msgGetTemplate
1601
+ * Service contract: { templateId }
1602
+ * Response: template document (FirebaseFirestore.DocumentData)
1603
+ */
1604
+ async getTemplate(templateId) {
1605
+ const response = await this.apiClient.dispatch(
1606
+ "messaging-msgGetTemplate",
1607
+ { templateId }
1608
+ );
1609
+ return response;
1610
+ }
1611
+ /**
1612
+ * Create a new message template.
1613
+ * Dispatch op: messaging-msgCreateTemplate
1614
+ * Service contract: { name, channelType, subject?, body, bodyHtml?, language?, variables?, description? }
1615
+ * Response: { templateId }
1616
+ * Note: backend returns { templateId }; SDK surface returns full MessageTemplate.
1617
+ * We re-fetch the created template to satisfy the return type.
1618
+ */
1619
+ async createTemplate(template) {
1620
+ const response = await this.apiClient.dispatch(
1621
+ "messaging-msgCreateTemplate",
1622
+ {
1623
+ name: template.name,
1624
+ channelType: template.channel,
1625
+ subject: template.subject,
1626
+ body: template.body,
1627
+ variables: template.variables,
1628
+ language: template.language
1629
+ }
1630
+ );
1631
+ return this.getTemplate(response.templateId);
1632
+ }
1633
+ /**
1634
+ * Update an existing template.
1635
+ * Dispatch op: messaging-msgUpdateTemplate
1636
+ * Service contract: { templateId, subject?, body?, bodyHtml?, language?, variables?, description? }
1637
+ * Response: { success }
1638
+ * We re-fetch the updated template to satisfy the return type.
1639
+ */
1640
+ async updateTemplate(templateId, updates) {
1641
+ await this.apiClient.dispatch(
1642
+ "messaging-msgUpdateTemplate",
1643
+ {
1644
+ templateId,
1645
+ subject: updates.subject,
1646
+ body: updates.body,
1647
+ language: updates.language,
1648
+ variables: updates.variables
1649
+ }
1650
+ );
1651
+ return this.getTemplate(templateId);
1652
+ }
1653
+ /**
1654
+ * Delete a template.
1655
+ * Dispatch op: messaging-msgDeleteTemplate
1656
+ * Service contract: { templateId }
1657
+ * Response: { success }
1658
+ */
1659
+ async deleteTemplate(templateId) {
1660
+ await this.apiClient.dispatch(
1661
+ "messaging-msgDeleteTemplate",
1662
+ { templateId }
1663
+ );
1664
+ }
1665
+ // Topic Subscription
1666
+ /**
1667
+ * Subscribe to a topic for push notifications.
1668
+ * Dispatch op: messaging-subscribeToTopic
1669
+ * Service contract: { token, topic, tenantId }
1670
+ * Token is acquired from the current device registration.
1671
+ * If no token is available, the subscription request is sent with userId context only.
1672
+ */
1673
+ async subscribeToTopic(topic) {
1674
+ if (!this.vapidKey) {
1675
+ throw new MAKEMINDError("CONFIGURATION_ERROR", "VAPID key required for topic subscriptions");
1676
+ }
1677
+ const token = await this.acquirePushToken();
1678
+ await this.apiClient.dispatch(
1679
+ "messaging-subscribeToTopic",
1680
+ {
1681
+ token,
1682
+ topic,
1683
+ tenantId: this.tenantId
1684
+ }
1685
+ );
1686
+ }
1687
+ /**
1688
+ * Unsubscribe from a topic.
1689
+ * Dispatch op: messaging-unsubscribeFromTopic
1690
+ * Service contract: { token, topic, tenantId }
1691
+ */
1692
+ async unsubscribeFromTopic(topic) {
1693
+ if (!this.vapidKey) {
1694
+ throw new MAKEMINDError("CONFIGURATION_ERROR", "VAPID key required for topic subscriptions");
1695
+ }
1696
+ const token = await this.acquirePushToken();
1697
+ await this.apiClient.dispatch(
1698
+ "messaging-unsubscribeFromTopic",
1699
+ {
1700
+ token,
1701
+ topic,
1702
+ tenantId: this.tenantId
1703
+ }
1704
+ );
1705
+ }
1706
+ /**
1707
+ * List subscribed topics for the current user.
1708
+ * Dispatch op: messaging-getMyTopics
1709
+ * Service contract: { tenantId? }
1710
+ * Response: { topics: Array<{ topic, fullTopic, tenantId, subscribedAt }> }
1711
+ */
1712
+ async listSubscribedTopics() {
1713
+ const response = await this.apiClient.dispatch(
1714
+ "messaging-getMyTopics",
1715
+ { tenantId: this.tenantId }
1716
+ );
1717
+ return response.topics.map((t) => t.topic);
1718
+ }
1719
+ /**
1720
+ * Send message to a topic.
1721
+ * Dispatch op: messaging-sendTopicMessage
1722
+ * Service contract: { topic, subject?, body, imageUrl?, data? }
1723
+ * Response: { messageId, fcmMessageId }
1724
+ */
1725
+ async sendToTopic(topic, message) {
1726
+ const response = await this.apiClient.dispatch(
1727
+ "messaging-sendTopicMessage",
1728
+ {
1729
+ topic,
1730
+ subject: message.title,
1731
+ body: message.body,
1732
+ data: message.data
1733
+ }
1734
+ );
1735
+ return { messageId: response.messageId };
1736
+ }
1737
+ // Message History
1738
+ /**
1739
+ * Get message history for this tenant.
1740
+ * Dispatch op: messaging-getMessages
1741
+ * Service contract: { channelType?, status?, limit? }
1742
+ * Response: { messages }
1743
+ */
1744
+ async getMessageHistory(options) {
1745
+ const response = await this.apiClient.dispatch(
1746
+ "messaging-getMessages",
1747
+ {
1748
+ channelType: options?.channel,
1749
+ status: options?.status,
1750
+ limit: options?.limit ?? 50
1751
+ }
1752
+ );
1753
+ return {
1754
+ messages: response.messages
1755
+ };
1756
+ }
1757
+ /**
1758
+ * Get full details of a single message.
1759
+ * Dispatch op: messaging-getMessage
1760
+ * Service contract: { messageId }
1761
+ * Response: full message document (FirebaseFirestore.DocumentData)
1762
+ */
1763
+ async getMessageDetails(messageId) {
1764
+ const response = await this.apiClient.dispatch(
1765
+ "messaging-getMessage",
1766
+ { messageId }
1767
+ );
1768
+ return response;
1769
+ }
1770
+ /**
1771
+ * Retry a failed message.
1772
+ * No backend messaging onCall exists for retry (confirmed: two searches — grep
1773
+ * across messaging/service/* and messaging/*.ts both returned no match).
1774
+ * Dispatch op: messaging-retryFailedMessage (new onCall implemented below).
1775
+ */
1776
+ async retryMessage(messageId) {
1777
+ const response = await this.apiClient.dispatch(
1778
+ "messaging-retryFailedMessage",
1779
+ { messageId }
1780
+ );
1781
+ return { messageId: response.messageId };
1782
+ }
1783
+ };
1784
+
1785
+ // src/services/settlement.ts
1786
+ var MAKEMINDSettlement = class {
1787
+ constructor(config) {
1788
+ this.apiClient = config.apiClient;
1789
+ this.getTenantId = config.tenantId;
1790
+ }
1791
+ get tenantId() {
1792
+ const id = this.getTenantId();
1793
+ if (!id) {
1794
+ throw new MAKEMINDError("UNAUTHENTICATED", "No tenant context set");
1795
+ }
1796
+ return id;
1797
+ }
1798
+ // Record a usage event
1799
+ async recordUsage(options) {
1800
+ await this.apiClient.dispatch("settlement-recordUsage", {
1801
+ metric: options.eventType,
1802
+ value: options.quantity ?? 1,
1803
+ unit: "count",
1804
+ sourceService: options.resourceId || "sdk",
1805
+ metadata: options.metadata
1806
+ });
1807
+ }
1808
+ /**
1809
+ * Record a sale and distribute it to n recipients in one atomic posting.
1810
+ *
1811
+ * The revenue leg and every share leg commit together or not at all. Do not
1812
+ * emulate this by recording the sale and then each share separately — that
1813
+ * makes every partial-failure combination yours to recover, and a
1814
+ * half-distributed sale cannot be repaired from outside the ledger.
1815
+ *
1816
+ * The split rule is the caller's: send the recipients and amounts you
1817
+ * computed. Each cut is either a percentage or a fixed amount, and the two
1818
+ * may be mixed; the combined claim may not exceed the sale. Recipients are settlement accounts, so the same call serves an
1819
+ * End User seller, another party in the tenant, or a platform cut.
1820
+ *
1821
+ * Shares are floored to the currency's minor unit; the remainder stays with
1822
+ * the receiving party. A share too small to express posts no leg and comes
1823
+ * back as `0` / `null`.
1824
+ */
1825
+ async recordSaleWithShares(options) {
1826
+ return this.apiClient.dispatch("settlement-recordSaleWithShares", options);
1827
+ }
1828
+ /**
1829
+ * Reverse a split sale, clawing back each recipient's share in the same
1830
+ * posting. Partial refunds claw back proportionally (floored, so a claw-back
1831
+ * never exceeds what the recipient received; the difference is yours).
1832
+ *
1833
+ * Use this rather than a plain refund for anything recorded with
1834
+ * `recordSaleWithShares` — otherwise the buyer is refunded while the shares
1835
+ * stay paid out, and the ledger is short by exactly the shares.
1836
+ */
1837
+ async reverseSaleWithShares(options) {
1838
+ return this.apiClient.dispatch("settlement-reverseSaleWithShares", options);
1839
+ }
1840
+ // Get usage statistics.
1841
+ // Backend REST GET /settlement/usage/summary accepts startDate/endDate/metric
1842
+ // query params (not a 'period' string). The response is
1843
+ // { data: { summary: Record<metric, { total, count, unit }> } }.
1844
+ // Mapped to UsageStats shape here.
1845
+ async getUsageStats(options) {
1846
+ const params = new URLSearchParams();
1847
+ if (options?.startDate) params.append("startDate", options.startDate);
1848
+ if (options?.endDate) params.append("endDate", options.endDate);
1849
+ if (options?.metric) params.append("metric", options.metric);
1850
+ const raw = await this.apiClient.get(`/settlement/usage/summary?${params.toString()}`);
1851
+ const summary = (raw.data?.summary ?? raw.summary) || {};
1852
+ const events = {};
1853
+ let totalUsage = 0;
1854
+ for (const [key, val] of Object.entries(summary)) {
1855
+ events[key] = val.total;
1856
+ totalUsage += val.total;
1857
+ }
1858
+ return {
1859
+ period: options?.startDate && options?.endDate ? `${options.startDate}/${options.endDate}` : "all",
1860
+ events,
1861
+ totalUsage
1862
+ };
1863
+ }
1864
+ // Get billing summary
1865
+ // NOTE: No backend aggregate function exists. This assembles the summary from
1866
+ // the current subscription (for plan name) and the main account balance (for
1867
+ // current balance + pending charges) via two sequential REST calls. A
1868
+ // dedicated settlement-getBillingSummary onCall is not yet deployed.
1869
+ async getBillingSummary() {
1870
+ const [subResult, accountResult] = await Promise.allSettled([
1871
+ this.apiClient.get(
1872
+ "/settlement/subscriptions/current"
1873
+ ),
1874
+ this.apiClient.get(
1875
+ "/settlement/accounts/main/balance"
1876
+ )
1877
+ ]);
1878
+ const sub = subResult.status === "fulfilled" ? subResult.value : null;
1879
+ const account = accountResult.status === "fulfilled" ? accountResult.value : { balance: 0, pendingBalance: 0, currency: "USD" };
1880
+ return {
1881
+ currentBalance: account.balance ?? 0,
1882
+ pendingCharges: account.pendingBalance ?? 0,
1883
+ currency: account.currency ?? "USD",
1884
+ plan: sub?.planName ?? "free"
1885
+ };
1886
+ }
1887
+ // Get quota status
1888
+ // TODO(gateway): no dedicated quota endpoint exists in the backend. A
1889
+ // settlement-getQuotaStatus onCall or REST route that maps plan limits against
1890
+ // live usage records has not yet been implemented. Requires new business logic
1891
+ // (scope: plan limits table + usage aggregation per metric).
1892
+ async getQuotaStatus(_resourceType) {
1893
+ throw new MAKEMINDError(
1894
+ "NOT_IMPLEMENTED",
1895
+ "getQuotaStatus requires a backend quota endpoint that is not yet available"
1896
+ );
1897
+ }
1898
+ // List invoices.
1899
+ // Backend InvoiceStatus includes 'cancelled' and 'refunded' in addition to
1900
+ // the four values the type/index.ts Invoice interface declares; the filter
1901
+ // param remains the narrower set since the SDK contract targets tenant-portal
1902
+ // display (admin-console can query all statuses directly).
1903
+ async listInvoices(options) {
1904
+ const params = new URLSearchParams();
1905
+ if (options?.status) params.append("status", options.status);
1906
+ if (options?.limit) params.append("limit", String(options.limit));
1907
+ const response = await this.apiClient.get(
1908
+ `/settlement/invoices?${params.toString()}`
1909
+ );
1910
+ return response.items.map((invoice) => ({
1911
+ ...invoice,
1912
+ periodStart: new Date(invoice.periodStart),
1913
+ periodEnd: new Date(invoice.periodEnd),
1914
+ createdAt: new Date(invoice.createdAt)
1915
+ }));
1916
+ }
1917
+ // Get invoice by ID
1918
+ async getInvoice(invoiceId) {
1919
+ return this.apiClient.get(`/settlement/invoices/${invoiceId}`);
1920
+ }
1921
+ // Download invoice PDF
1922
+ // TODO(gateway): invoice PDF generation (rendering, signing, storage) is not
1923
+ // yet implemented in the backend. Requires a new Cloud Function and/or storage
1924
+ // pipeline (scope: PDF renderer + signed GCS URL generation + optional caching).
1925
+ async downloadInvoice(_invoiceId) {
1926
+ throw new MAKEMINDError(
1927
+ "NOT_IMPLEMENTED",
1928
+ "downloadInvoice requires a PDF generation pipeline that is not yet available"
1929
+ );
1930
+ }
1931
+ // Get current plan
1932
+ // Resolves via two dispatch calls: (1) get the active subscription to learn the
1933
+ // productId, then (2) fetch the product record for features/limits.
1934
+ async getCurrentPlan() {
1935
+ const sub = await this.apiClient.get(
1936
+ "/settlement/subscriptions/current"
1937
+ );
1938
+ const productId = sub?.planId;
1939
+ if (!productId) {
1940
+ return { id: "free", name: "Free", features: [], limits: {} };
1941
+ }
1942
+ const product = await this.apiClient.dispatch("settlement-getProduct", { productId });
1943
+ return {
1944
+ id: product.id,
1945
+ name: product.name,
1946
+ features: product.features ?? [],
1947
+ limits: product.includedUsage ?? {}
1948
+ };
1949
+ }
1950
+ // List available plans
1951
+ // Routes to settlement-listProducts filtering on productType='saasPlan'. The
1952
+ // response shape is adapted to the SDK plan contract.
1953
+ async listPlans() {
1954
+ const result = await this.apiClient.dispatch("settlement-listProducts", {
1955
+ productType: "saasPlan",
1956
+ status: "active"
1957
+ });
1958
+ return (result.products ?? []).map((p) => ({
1959
+ id: p.id,
1960
+ name: p.name,
1961
+ description: p.description ?? "",
1962
+ price: p.price ?? 0,
1963
+ currency: p.currency ?? "USD",
1964
+ features: p.features ?? [],
1965
+ limits: p.includedUsage ?? {}
1966
+ }));
1967
+ }
1968
+ // Upgrade/change plan
1969
+ // Routes to POST /settlement/subscriptions which cancels the existing active
1970
+ // subscription and creates a new one for the target plan in a single batch.
1971
+ async changePlan(planId) {
1972
+ await this.apiClient.post("/settlement/subscriptions", { planId });
1973
+ }
1974
+ // ============================================================================
1975
+ // Subscription Management
1976
+ // ============================================================================
1977
+ /**
1978
+ * Get current subscription details
1979
+ */
1980
+ async getCurrentSubscription() {
1981
+ try {
1982
+ return await this.apiClient.get("/settlement/subscriptions/current");
1983
+ } catch (error) {
1984
+ if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
1985
+ return null;
1986
+ }
1987
+ throw error;
1988
+ }
1989
+ }
1990
+ /**
1991
+ * Subscribe to a plan
1992
+ */
1993
+ async subscribe(options) {
1994
+ return this.apiClient.post("/settlement/subscriptions", {
1995
+ planId: options.planId,
1996
+ billingCycle: options.billingCycle || "monthly",
1997
+ paymentMethod: options.paymentMethodId,
1998
+ couponCode: options.couponCode,
1999
+ startImmediately: options.startImmediately ?? true
2000
+ });
2001
+ }
2002
+ /**
2003
+ * Update current subscription (e.g., change plan, billing cycle).
2004
+ * The backend has no in-place PATCH; a plan update is implemented by creating
2005
+ * a new subscription (which cancels the current one server-side). billingCycle
2006
+ * and prorate are forwarded as metadata but only planId is acted upon today.
2007
+ */
2008
+ async updateSubscription(options) {
2009
+ if (!options.planId) {
2010
+ throw new MAKEMINDError(
2011
+ "VALIDATION_ERROR",
2012
+ "planId is required for updateSubscription"
2013
+ );
2014
+ }
2015
+ return this.apiClient.post("/settlement/subscriptions", {
2016
+ planId: options.planId,
2017
+ billingCycle: options.billingCycle
2018
+ });
2019
+ }
2020
+ /**
2021
+ * Cancel current subscription.
2022
+ * Resolves the active subscriptionId first, then dispatches to
2023
+ * settlement-cancelSubscription. Backend field is cancelImmediately (not
2024
+ * cancelAtPeriodEnd); meaning is inverted — SDK default cancelAtPeriodEnd=true
2025
+ * maps to cancelImmediately=false.
2026
+ */
2027
+ async cancelSubscription(options) {
2028
+ const sub = await this.getCurrentSubscription();
2029
+ if (!sub) {
2030
+ throw new MAKEMINDError("NOT_FOUND", "No active subscription to cancel");
2031
+ }
2032
+ await this.apiClient.dispatch("settlement-cancelSubscription", {
2033
+ subscriptionId: sub.id,
2034
+ cancelImmediately: !(options?.cancelAtPeriodEnd ?? true),
2035
+ reason: options?.reason
2036
+ });
2037
+ }
2038
+ /**
2039
+ * Pause subscription (if supported by plan).
2040
+ * Backend accepts only { subscriptionId }; pauseUntil is not supported yet.
2041
+ */
2042
+ async pauseSubscription(_options) {
2043
+ const sub = await this.getCurrentSubscription();
2044
+ if (!sub) {
2045
+ throw new MAKEMINDError("NOT_FOUND", "No active subscription to pause");
2046
+ }
2047
+ await this.apiClient.dispatch("settlement-pauseSubscription", {
2048
+ subscriptionId: sub.id
2049
+ });
2050
+ }
2051
+ /**
2052
+ * Resume a paused subscription.
2053
+ */
2054
+ async resumeSubscription() {
2055
+ const sub = await this.getCurrentSubscription();
2056
+ if (!sub) {
2057
+ throw new MAKEMINDError("NOT_FOUND", "No subscription found to resume");
2058
+ }
2059
+ return this.apiClient.dispatch("settlement-resumeSubscription", {
2060
+ subscriptionId: sub.id
2061
+ });
2062
+ }
2063
+ /**
2064
+ * Reactivate a canceled subscription (before period end).
2065
+ * Routes to settlement-resumeSubscription: fetches the most-recently-canceled
2066
+ * subscription and resumes it. This is semantically equivalent for the
2067
+ * cancel-at-period-end case where the subscription is still within its period.
2068
+ */
2069
+ async reactivateSubscription() {
2070
+ const sub = await this.getCurrentSubscription();
2071
+ if (!sub) {
2072
+ throw new MAKEMINDError("NOT_FOUND", "No subscription found to reactivate");
2073
+ }
2074
+ return this.apiClient.dispatch("settlement-resumeSubscription", {
2075
+ subscriptionId: sub.id
2076
+ });
2077
+ }
2078
+ /**
2079
+ * Get subscription history.
2080
+ * TODO(gateway): no dedicated subscription history table/function exists in the
2081
+ * backend. Requires a new settlement_subscription_events table or equivalent
2082
+ * audit trail (scope: schema + backfill trigger + query function).
2083
+ */
2084
+ async getSubscriptionHistory(_options) {
2085
+ throw new MAKEMINDError(
2086
+ "NOT_IMPLEMENTED",
2087
+ "getSubscriptionHistory requires a subscription audit trail that is not yet available"
2088
+ );
2089
+ }
2090
+ /**
2091
+ * Preview subscription change (get prorated amounts).
2092
+ * TODO(gateway): proration calculation requires billing period overlap logic
2093
+ * that is not yet implemented in the backend (scope: period credits, immediate
2094
+ * charge delta, next-billing projection — needs a new onCall or REST endpoint).
2095
+ */
2096
+ async previewSubscriptionChange(_newPlanId) {
2097
+ throw new MAKEMINDError(
2098
+ "NOT_IMPLEMENTED",
2099
+ "previewSubscriptionChange requires proration logic that is not yet available"
2100
+ );
2101
+ }
2102
+ /**
2103
+ * Apply a coupon to current subscription.
2104
+ * TODO(gateway): coupon engine (validation, redemption ledger, discount
2105
+ * application) is not yet implemented (scope: coupon collection + validation
2106
+ * function + subscription discount field update).
2107
+ */
2108
+ async applyCoupon(_couponCode) {
2109
+ throw new MAKEMINDError(
2110
+ "NOT_IMPLEMENTED",
2111
+ "applyCoupon requires a coupon engine that is not yet available"
2112
+ );
2113
+ }
2114
+ // Get payment methods
2115
+ // Gateway exposes no /settlement/payment-methods REST route; routed through the
2116
+ // settlement dispatch operation instead.
2117
+ async getPaymentMethods() {
2118
+ const response = await this.apiClient.dispatch("settlement-listPaymentMethods", {});
2119
+ return response.paymentMethods;
2120
+ }
2121
+ // Add payment method.
2122
+ // Backend onCall expects { provider, stripe?, toss?, kakaoPay?, manual? }.
2123
+ async addPaymentMethod(data) {
2124
+ return this.apiClient.dispatch(
2125
+ "settlement-addPaymentMethod",
2126
+ data
2127
+ );
2128
+ }
2129
+ // Remove payment method.
2130
+ // Backend field name is paymentAccountId (not paymentMethodId).
2131
+ async removePaymentMethod(paymentAccountId) {
2132
+ await this.apiClient.dispatch("settlement-removePaymentMethod", { paymentAccountId });
2133
+ }
2134
+ // Set default payment method.
2135
+ // Backend field name is paymentAccountId (not paymentMethodId).
2136
+ async setDefaultPaymentMethod(paymentAccountId) {
2137
+ await this.apiClient.dispatch("settlement-setDefaultPaymentMethod", { paymentAccountId });
2138
+ }
2139
+ // PLM-specific usage events
2140
+ // Record product creation
2141
+ async recordProductCreation(productId) {
2142
+ return this.recordUsage({
2143
+ eventType: "productCreated",
2144
+ resourceId: productId
2145
+ });
2146
+ }
2147
+ // Record document upload
2148
+ async recordDocumentUpload(documentId, fileSizeMb) {
2149
+ return this.recordUsage({
2150
+ eventType: "documentUpload",
2151
+ resourceId: documentId,
2152
+ metadata: { fileSizeMb }
2153
+ });
2154
+ }
2155
+ // Record ECO submission
2156
+ async recordEcoSubmission(ecoId) {
2157
+ return this.recordUsage({
2158
+ eventType: "ecoSubmitted",
2159
+ resourceId: ecoId
2160
+ });
2161
+ }
2162
+ // Record marketplace bid
2163
+ async recordMarketplaceBid(bidId, requestId) {
2164
+ return this.recordUsage({
2165
+ eventType: "marketplaceBidSubmitted",
2166
+ resourceId: bidId,
2167
+ metadata: { requestId }
2168
+ });
2169
+ }
2170
+ // Record participant addition
2171
+ async recordParticipantAdded(productId, participantTenantId) {
2172
+ return this.recordUsage({
2173
+ eventType: "participantAdded",
2174
+ resourceId: productId,
2175
+ metadata: { participantTenantId }
2176
+ });
2177
+ }
2178
+ };
2179
+
2180
+ // src/services/partners.ts
2181
+ var MAKEMINDPartners = class {
2182
+ constructor(config) {
2183
+ this.apiClient = config.apiClient;
2184
+ }
2185
+ // --- Partner self-service ---
2186
+ /** Register the signed-in individual as a partner (requires a verified email). */
2187
+ async registerSelf(displayName) {
2188
+ return this.apiClient.dispatch("settlement-partnersRegisterSelf", displayName ? { displayName } : {});
2189
+ }
2190
+ /** Browse the open partner-program directory. */
2191
+ async listPrograms() {
2192
+ return this.apiClient.dispatch("settlement-partnersListPrograms", {});
2193
+ }
2194
+ /** Apply to a tenant's program (creates a pending registration). */
2195
+ async apply(tenantId) {
2196
+ return this.apiClient.dispatch("settlement-partnersApply", { tenantId });
2197
+ }
2198
+ // --- M2M: referral capture + derivation (product-site backend) ---
2199
+ /** Auto-attribute a subject to a partner via a referral code (at checkout/entry). */
2200
+ async registerReferral(input) {
2201
+ return this.apiClient.dispatch("settlement-partnersRegisterReferral", input);
2202
+ }
2203
+ /** Derive commission for a settled sale for any partner that referred the subject. */
2204
+ async deriveCommission(input) {
2205
+ return this.apiClient.dispatch("settlement-partnersDeriveCommission", input);
2206
+ }
2207
+ /**
2208
+ * Canonical settlement intake: report a revenue event; the engine derives the
2209
+ * full line set (tax + commission + net waterfall) and records partner
2210
+ * commissions when a saleId is given.
2211
+ */
2212
+ async deriveSettlementForEvent(input) {
2213
+ return this.apiClient.dispatch("settlement-deriveSettlementForEvent", input);
2214
+ }
2215
+ // --- Tenant admin (also usable by platform admin with an explicit tenantId) ---
2216
+ /** Publish/update the tenant's partner program (directory listing). */
2217
+ async publishProgram(input) {
2218
+ return this.apiClient.dispatch("settlement-partnersPublishProgram", input);
2219
+ }
2220
+ /** Approve a pending/invited registration (optionally overriding the rate). */
2221
+ async approve(input) {
2222
+ return this.apiClient.dispatch("settlement-partnersApprove", input);
2223
+ }
2224
+ /** Add an already-registered partner directly by id (arranged relationship). */
2225
+ async addById(input) {
2226
+ return this.apiClient.dispatch("settlement-partnersAddById", input);
2227
+ }
2228
+ /** Invite a partner by email (fallback for someone not yet self-registered). */
2229
+ async invite(input) {
2230
+ return this.apiClient.dispatch("settlement-partnersInvite", input);
2231
+ }
2232
+ /** List the partners registered in the current tenant. */
2233
+ async list(tenantId) {
2234
+ return this.apiClient.dispatch("settlement-partnersList", tenantId ? { tenantId } : {});
2235
+ }
2236
+ /** Update a registration's rate and/or status. */
2237
+ async update(input) {
2238
+ return this.apiClient.dispatch("settlement-partnersUpdateRegistration", input);
2239
+ }
2240
+ /** Record a commission accrual for the current tenant (idempotent on idempotencyKey). */
2241
+ async recordCommission(input) {
2242
+ return this.apiClient.dispatch("settlement-partnersRecordCommission", input);
2243
+ }
2244
+ /** Claw back a commission entry. */
2245
+ async reverseCommission(entryId) {
2246
+ return this.apiClient.dispatch("settlement-partnersReverseCommission", { entryId });
2247
+ }
2248
+ /** Record a manual payout against a partner's owed commission. */
2249
+ async recordPayout(input) {
2250
+ return this.apiClient.dispatch("settlement-partnersRecordPayout", input);
2251
+ }
2252
+ // --- Partner self-view ---
2253
+ /** Accept an invitation, claiming the partner identity (requires a verified email). */
2254
+ async acceptInvitation(partnerId, tenantId) {
2255
+ return this.apiClient.dispatch("settlement-partnersAcceptInvitation", { partnerId, tenantId });
2256
+ }
2257
+ /** The signed-in partner's own cross-tenant commission statement (union). */
2258
+ async myCommissions() {
2259
+ return this.apiClient.dispatch("settlement-partnersMyCommissions", {});
2260
+ }
2261
+ // --- Platform admin (Guild) ---
2262
+ /** Any partner's statement (platform admin only). */
2263
+ async commissionsOf(partnerId) {
2264
+ return this.apiClient.dispatch("settlement-partnersGetStatement", { partnerId });
2265
+ }
2266
+ };
2267
+
2268
+ // src/services/storage.ts
2269
+ var MAKEMINDStorage = class {
2270
+ constructor(config) {
2271
+ this.apiClient = config.apiClient;
2272
+ this.getTenantId = config.tenantId;
2273
+ }
2274
+ get tenantId() {
2275
+ const id = this.getTenantId();
2276
+ if (!id) {
2277
+ throw new MAKEMINDError("UNAUTHENTICATED", "No tenant context set");
2278
+ }
2279
+ return id;
2280
+ }
2281
+ /**
2282
+ * Encode a storage object path for use as a path parameter, preserving the
2283
+ * `/` separators (the gateway captures the full sub-path via a wildcard).
2284
+ */
2285
+ encodePath(path) {
2286
+ return path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
2287
+ }
2288
+ /**
2289
+ * Upload a file
2290
+ */
2291
+ async upload(options) {
2292
+ const { uploadUrl } = await this.apiClient.post(
2293
+ "/storage/upload",
2294
+ {
2295
+ path: options.path,
2296
+ contentType: options.file.type || "application/octet-stream",
2297
+ metadata: options.metadata
2298
+ }
2299
+ );
2300
+ await this.uploadToSignedUrl(uploadUrl, options.file, options.onProgress);
2301
+ return {
2302
+ path: options.path,
2303
+ url: await this.getDownloadUrl(options.path),
2304
+ size: options.file.size,
2305
+ contentType: options.file.type || "application/octet-stream",
2306
+ metadata: options.metadata
2307
+ };
2308
+ }
2309
+ async uploadToSignedUrl(url, file, onProgress) {
2310
+ return new Promise((resolve, reject) => {
2311
+ const xhr = new XMLHttpRequest();
2312
+ if (onProgress) {
2313
+ xhr.upload.addEventListener("progress", (event) => {
2314
+ if (event.lengthComputable) {
2315
+ const progress = event.loaded / event.total * 100;
2316
+ onProgress(progress);
2317
+ }
2318
+ });
2319
+ }
2320
+ xhr.addEventListener("load", () => {
2321
+ if (xhr.status >= 200 && xhr.status < 300) {
2322
+ resolve();
2323
+ } else {
2324
+ reject(new MAKEMINDError("STORAGE_ERROR", `Upload failed with status ${xhr.status}`));
2325
+ }
2326
+ });
2327
+ xhr.addEventListener("error", () => {
2328
+ reject(new MAKEMINDError("STORAGE_ERROR", "Upload failed"));
2329
+ });
2330
+ xhr.open("PUT", url);
2331
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
2332
+ xhr.send(file);
2333
+ });
2334
+ }
2335
+ /**
2336
+ * Get download URL for a file
2337
+ */
2338
+ async getDownloadUrl(path) {
2339
+ const response = await this.apiClient.get(
2340
+ `/storage/download/${this.encodePath(path)}`
2341
+ );
2342
+ return response.downloadUrl;
2343
+ }
2344
+ /**
2345
+ * Download a file directly
2346
+ * Returns the file as a Blob
2347
+ */
2348
+ async download(path) {
2349
+ const url = await this.getDownloadUrl(path);
2350
+ const response = await fetch(url, {
2351
+ method: "GET"
2352
+ });
2353
+ if (!response.ok) {
2354
+ throw new MAKEMINDError("STORAGE_ERROR", `Download failed with status ${response.status}`);
2355
+ }
2356
+ return response.blob();
2357
+ }
2358
+ /**
2359
+ * Download a file and trigger browser download
2360
+ */
2361
+ async downloadToFile(path, filename) {
2362
+ const blob = await this.download(path);
2363
+ const downloadFilename = filename || path.split("/").pop() || "download";
2364
+ const link = document.createElement("a");
2365
+ link.href = URL.createObjectURL(blob);
2366
+ link.download = downloadFilename;
2367
+ document.body.appendChild(link);
2368
+ link.click();
2369
+ document.body.removeChild(link);
2370
+ URL.revokeObjectURL(link.href);
2371
+ }
2372
+ /**
2373
+ * Get file metadata
2374
+ */
2375
+ async getMetadata(path) {
2376
+ const response = await this.apiClient.get(`/storage/${this.encodePath(path)}/metadata`);
2377
+ return {
2378
+ name: response.path.split("/").pop() || response.path,
2379
+ path: response.path,
2380
+ size: response.size,
2381
+ contentType: response.contentType,
2382
+ createdAt: new Date(response.created),
2383
+ updatedAt: new Date(response.updated),
2384
+ metadata: response.metadata
2385
+ };
2386
+ }
2387
+ /**
2388
+ * Update file metadata
2389
+ */
2390
+ async updateMetadata(path, metadata) {
2391
+ await this.apiClient.patch(`/storage/${this.encodePath(path)}/metadata`, {
2392
+ metadata
2393
+ });
2394
+ }
2395
+ /**
2396
+ * List files in a directory
2397
+ */
2398
+ async listFiles(options) {
2399
+ const params = new URLSearchParams();
2400
+ if (options?.prefix) params.append("prefix", options.prefix);
2401
+ if (options?.maxResults) params.append("limit", String(options.maxResults));
2402
+ if (options?.pageToken) params.append("cursor", options.pageToken);
2403
+ const items = await this.apiClient.get(`/storage/list?${params.toString()}`);
2404
+ return {
2405
+ files: items.map((file) => ({
2406
+ name: file.path.split("/").pop() || file.path,
2407
+ path: file.path,
2408
+ size: file.size,
2409
+ contentType: file.contentType,
2410
+ createdAt: new Date(file.updated),
2411
+ updatedAt: new Date(file.updated)
2412
+ }))
2413
+ };
2414
+ }
2415
+ /**
2416
+ * Delete a file
2417
+ */
2418
+ async delete(path) {
2419
+ await this.apiClient.delete(`/storage/${this.encodePath(path)}`);
2420
+ }
2421
+ /**
2422
+ * Delete multiple files
2423
+ */
2424
+ // TODO(gateway): no matching route — no batch delete route nor dispatch operation;
2425
+ // falls back to issuing individual DELETE /storage/:path requests.
2426
+ async deleteMany(paths) {
2427
+ await Promise.all(paths.map((path) => this.delete(path)));
2428
+ }
2429
+ /**
2430
+ * Copy a file
2431
+ */
2432
+ async copy(sourcePath, destinationPath) {
2433
+ return this.apiClient.post("/storage/copy", {
2434
+ source: sourcePath,
2435
+ destination: destinationPath
2436
+ });
2437
+ }
2438
+ /**
2439
+ * Move a file
2440
+ */
2441
+ async move(sourcePath, destinationPath) {
2442
+ return this.apiClient.post("/storage/move", {
2443
+ source: sourcePath,
2444
+ destination: destinationPath
2445
+ });
2446
+ }
2447
+ // PLM-specific helpers
2448
+ /**
2449
+ * Upload a document for a product
2450
+ */
2451
+ async uploadProductDocument(productId, file, options) {
2452
+ const path = `products/${productId}/documents/${file.name}`;
2453
+ return this.upload({
2454
+ path,
2455
+ file,
2456
+ metadata: {
2457
+ productId,
2458
+ documentType: options?.documentType || "general",
2459
+ version: options?.version || "1.0"
2460
+ },
2461
+ onProgress: options?.onProgress
2462
+ });
2463
+ }
2464
+ /**
2465
+ * Upload an ECO attachment
2466
+ */
2467
+ async uploadEcoAttachment(ecoId, file, onProgress) {
2468
+ const path = `eco/${ecoId}/attachments/${file.name}`;
2469
+ return this.upload({
2470
+ path,
2471
+ file,
2472
+ metadata: {
2473
+ ecoId
2474
+ },
2475
+ onProgress
2476
+ });
2477
+ }
2478
+ /**
2479
+ * Upload a quality issue attachment
2480
+ */
2481
+ async uploadQualityAttachment(issueId, file, onProgress) {
2482
+ const path = `quality/${issueId}/attachments/${file.name}`;
2483
+ return this.upload({
2484
+ path,
2485
+ file,
2486
+ metadata: {
2487
+ issueId
2488
+ },
2489
+ onProgress
2490
+ });
2491
+ }
2492
+ /**
2493
+ * Get all documents for a product
2494
+ */
2495
+ async getProductDocuments(productId) {
2496
+ const result = await this.listFiles({
2497
+ prefix: `products/${productId}/documents/`
2498
+ });
2499
+ return result.files;
2500
+ }
2501
+ // Bucket Management (05-data-platform-saas.md)
2502
+ /**
2503
+ * List all buckets for the tenant
2504
+ */
2505
+ async listBuckets() {
2506
+ const response = await this.apiClient.get("/storage/buckets");
2507
+ return response.buckets;
2508
+ }
2509
+ /**
2510
+ * Create a new bucket.
2511
+ *
2512
+ * The REST route expects { name, settings } where settings carries the
2513
+ * bucket policy fields. publicAccess, corsConfig, and lifecycleRules from
2514
+ * CreateBucketOptions are folded into the settings envelope so the backend
2515
+ * handler can read them from req.body.settings.
2516
+ */
2517
+ async createBucket(options) {
2518
+ return this.apiClient.post("/storage/buckets", {
2519
+ name: options.name,
2520
+ settings: {
2521
+ publicAccess: options.publicAccess ?? false,
2522
+ corsConfig: options.corsConfig,
2523
+ lifecycleRules: options.lifecycleRules
2524
+ }
2525
+ });
2526
+ }
2527
+ /**
2528
+ * Get bucket info
2529
+ */
2530
+ async getBucket(bucketId) {
2531
+ return this.apiClient.get(`/storage/buckets/${bucketId}`);
2532
+ }
2533
+ /**
2534
+ * Update bucket settings.
2535
+ *
2536
+ * The REST route reads { settings } from req.body. Flatten the
2537
+ * CreateBucketOptions partial into a settings envelope so the backend
2538
+ * merges the diff correctly.
2539
+ */
2540
+ async updateBucket(bucketId, updates) {
2541
+ const { name: _name, ...rest } = updates;
2542
+ return this.apiClient.patch(`/storage/buckets/${bucketId}`, {
2543
+ settings: rest
2544
+ });
2545
+ }
2546
+ /**
2547
+ * Delete a bucket (must be empty)
2548
+ */
2549
+ async deleteBucket(bucketId) {
2550
+ await this.apiClient.delete(`/storage/buckets/${bucketId}`);
2551
+ }
2552
+ /**
2553
+ * Get a pre-signed URL for direct upload (bypassing SDK)
2554
+ */
2555
+ async getUploadUrl(path, options) {
2556
+ const segments = path.split("/").filter((segment) => segment.length > 0);
2557
+ const fileName = segments.pop() ?? path;
2558
+ const folder = segments.join("/");
2559
+ const response = await this.apiClient.dispatch("dataGetUploadUrl", {
2560
+ fileName,
2561
+ contentType: options?.contentType || "application/octet-stream",
2562
+ ...folder ? { folder } : {}
2563
+ });
2564
+ return { uploadUrl: response.uploadUrl, fileId: response.fileId };
2565
+ }
2566
+ /**
2567
+ * Check if a file exists
2568
+ */
2569
+ async exists(path) {
2570
+ try {
2571
+ await this.getMetadata(path);
2572
+ return true;
2573
+ } catch {
2574
+ return false;
2575
+ }
2576
+ }
2577
+ /**
2578
+ * Get storage usage statistics
2579
+ */
2580
+ async getUsageStats() {
2581
+ return this.apiClient.get("/storage/stats");
2582
+ }
2583
+ };
2584
+
2585
+ // src/services/ai.ts
2586
+ var MAKEMINDAI = class {
2587
+ constructor(config) {
2588
+ this.apiClient = config.apiClient;
2589
+ this.getTenantId = config.tenantId;
2590
+ }
2591
+ // Chat completion
2592
+ async chat(messages, options) {
2593
+ const requestMessages = options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }, ...messages] : messages;
2594
+ const response = await this.apiClient.dispatch("ai-aiChat", {
2595
+ tenantId: this.getTenantId(),
2596
+ messages: requestMessages,
2597
+ model: options?.model,
2598
+ temperature: options?.temperature,
2599
+ maxTokens: options?.maxTokens
2600
+ });
2601
+ return {
2602
+ message: { role: "assistant", content: response.content },
2603
+ usage: {
2604
+ promptTokens: response.usage.inputTokens,
2605
+ completionTokens: response.usage.outputTokens,
2606
+ totalTokens: response.usage.totalTokens
2607
+ }
2608
+ };
2609
+ }
2610
+ /**
2611
+ * Stream chat completion via the aiStreamChat onCall backend.
2612
+ *
2613
+ * The backend writes chunks progressively to Firestore
2614
+ * (aiStreamChunks/{streamId}/chunks). This method polls the stream
2615
+ * document until the status reaches 'completed' or 'failed', invoking
2616
+ * onChunk for each content chunk in order. Callers that need real-time
2617
+ * granularity should subscribe to the Firestore collection directly using
2618
+ * the returned streamId from aiStreamChat.
2619
+ *
2620
+ * @param messages - Chat messages
2621
+ * @param onChunk - Callback for each streamed content chunk
2622
+ * @param options - Chat options and optional onDone callback
2623
+ * @returns Promise that resolves when streaming is complete
2624
+ */
2625
+ async chatStream(messages, onChunk, options) {
2626
+ const requestMessages = options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }, ...messages] : messages;
2627
+ const startResult = await this.apiClient.dispatch("ai-aiStreamChat", {
2628
+ tenantId: this.getTenantId(),
2629
+ messages: requestMessages,
2630
+ model: options?.model,
2631
+ temperature: options?.temperature,
2632
+ maxTokens: options?.maxTokens
2633
+ });
2634
+ const { streamId } = startResult;
2635
+ const pollInterval = 500;
2636
+ const maxWaitMs = 12e4;
2637
+ const startAt = Date.now();
2638
+ let lastContent = "";
2639
+ while (Date.now() - startAt < maxWaitMs) {
2640
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
2641
+ const poll = await this.apiClient.dispatch("ai-aiGetStreamResult", { streamId });
2642
+ if (poll.status === "failed") {
2643
+ throw new Error(poll.error ?? "Stream failed");
2644
+ }
2645
+ if (poll.status === "completed") {
2646
+ const fullContent = poll.content ?? "";
2647
+ const delta2 = fullContent.slice(lastContent.length);
2648
+ if (delta2) {
2649
+ onChunk(delta2);
2650
+ }
2651
+ const rawUsage = poll.usage;
2652
+ const finalResponse = {
2653
+ message: { role: "assistant", content: fullContent },
2654
+ usage: {
2655
+ promptTokens: rawUsage?.inputTokens ?? 0,
2656
+ completionTokens: rawUsage?.outputTokens ?? 0,
2657
+ totalTokens: rawUsage?.totalTokens ?? 0
2658
+ }
2659
+ };
2660
+ if (options?.onDone) {
2661
+ options.onDone(finalResponse);
2662
+ }
2663
+ return finalResponse;
2664
+ }
2665
+ const currentContent = poll.content ?? "";
2666
+ const delta = currentContent.slice(lastContent.length);
2667
+ if (delta) {
2668
+ onChunk(delta);
2669
+ lastContent = currentContent;
2670
+ }
2671
+ }
2672
+ throw new Error("Stream timed out");
2673
+ }
2674
+ /**
2675
+ * Stream chat using async generator for easier consumption.
2676
+ * Yields content deltas via polling on aiGetStreamResult.
2677
+ */
2678
+ async *streamChat(messages, options) {
2679
+ const requestMessages = options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }, ...messages] : messages;
2680
+ const startResult = await this.apiClient.dispatch("ai-aiStreamChat", {
2681
+ tenantId: this.getTenantId(),
2682
+ messages: requestMessages,
2683
+ model: options?.model,
2684
+ temperature: options?.temperature,
2685
+ maxTokens: options?.maxTokens
2686
+ });
2687
+ const { streamId } = startResult;
2688
+ const pollInterval = 500;
2689
+ const maxWaitMs = 12e4;
2690
+ const startAt = Date.now();
2691
+ let lastContent = "";
2692
+ while (Date.now() - startAt < maxWaitMs) {
2693
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
2694
+ const poll = await this.apiClient.dispatch("ai-aiGetStreamResult", { streamId });
2695
+ if (poll.status === "failed") {
2696
+ throw new Error(poll.error ?? "Stream failed");
2697
+ }
2698
+ const currentContent = poll.content ?? "";
2699
+ const delta = currentContent.slice(lastContent.length);
2700
+ if (delta) {
2701
+ yield delta;
2702
+ lastContent = currentContent;
2703
+ }
2704
+ if (poll.status === "completed") {
2705
+ const rawUsage = poll.usage;
2706
+ return {
2707
+ message: { role: "assistant", content: currentContent },
2708
+ usage: {
2709
+ promptTokens: rawUsage?.inputTokens ?? 0,
2710
+ completionTokens: rawUsage?.outputTokens ?? 0,
2711
+ totalTokens: rawUsage?.totalTokens ?? 0
2712
+ }
2713
+ };
2714
+ }
2715
+ }
2716
+ throw new Error("Stream timed out");
2717
+ }
2718
+ // Embeddings
2719
+ async createEmbedding(text, options) {
2720
+ const texts = Array.isArray(text) ? text : [text];
2721
+ const response = await this.apiClient.dispatch("ai-aiEmbeddings", {
2722
+ tenantId: this.getTenantId(),
2723
+ input: texts,
2724
+ model: options?.model
2725
+ });
2726
+ return { embeddings: response.embeddings, usage: response.usage };
2727
+ }
2728
+ /**
2729
+ * Vector search over a tenant knowledge collection.
2730
+ * Routes to the knowledge domain dispatch (knowledge-knowledgeSearch),
2731
+ * which performs semantic search with embeddings over indexed documents.
2732
+ */
2733
+ async vectorSearch(collection, query, options) {
2734
+ const response = await this.apiClient.dispatch("knowledge-knowledgeSearch", {
2735
+ tenantId: this.getTenantId(),
2736
+ collectionId: collection,
2737
+ query,
2738
+ limit: options?.limit,
2739
+ filter: options?.filter
2740
+ });
2741
+ return response.results;
2742
+ }
2743
+ /**
2744
+ * Vision analysis for a publicly accessible image URL.
2745
+ * Routes to ai-aiVision (aiVision onCall). The image is passed as a
2746
+ * ChatCompletionRequest message using the imageUrl ContentPart type.
2747
+ */
2748
+ async analyzeImage(imageUrl, prompt, options) {
2749
+ const response = await this.apiClient.dispatch("ai-aiVision", {
2750
+ tenantId: this.getTenantId(),
2751
+ model: options?.model,
2752
+ maxTokens: options?.maxTokens,
2753
+ messages: [
2754
+ {
2755
+ role: "user",
2756
+ content: [
2757
+ { type: "imageUrl", imageUrl: { url: imageUrl, detail: "auto" } },
2758
+ { type: "text", text: prompt }
2759
+ ]
2760
+ }
2761
+ ]
2762
+ });
2763
+ return {
2764
+ content: response.content,
2765
+ usage: { totalTokens: response.usage.totalTokens }
2766
+ };
2767
+ }
2768
+ /**
2769
+ * Vision analysis for a base64-encoded image.
2770
+ * The image is embedded as a data URI in the imageUrl ContentPart.
2771
+ */
2772
+ async analyzeImageBase64(base64Image, mimeType, prompt, options) {
2773
+ const dataUri = `data:${mimeType};base64,${base64Image}`;
2774
+ const response = await this.apiClient.dispatch("ai-aiVision", {
2775
+ tenantId: this.getTenantId(),
2776
+ model: options?.model,
2777
+ maxTokens: options?.maxTokens,
2778
+ messages: [
2779
+ {
2780
+ role: "user",
2781
+ content: [
2782
+ { type: "imageUrl", imageUrl: { url: dataUri, detail: "auto" } },
2783
+ { type: "text", text: prompt }
2784
+ ]
2785
+ }
2786
+ ]
2787
+ });
2788
+ return {
2789
+ content: response.content,
2790
+ usage: { totalTokens: response.usage.totalTokens }
2791
+ };
2792
+ }
2793
+ /**
2794
+ * Chat completion with tool definitions (function calling).
2795
+ * Routes to ai-aiChat — the aiChat onCall natively supports the tools field
2796
+ * in ChatCompletionRequest, no separate endpoint required.
2797
+ */
2798
+ async chatWithTools(messages, tools, options) {
2799
+ const requestMessages = options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }, ...messages] : messages;
2800
+ const aiTools = tools.map((t) => ({
2801
+ type: "function",
2802
+ function: {
2803
+ name: t.name,
2804
+ description: t.description,
2805
+ parameters: t.parameters
2806
+ }
2807
+ }));
2808
+ const response = await this.apiClient.dispatch("ai-aiChat", {
2809
+ tenantId: this.getTenantId(),
2810
+ messages: requestMessages,
2811
+ tools: aiTools,
2812
+ model: options?.model,
2813
+ temperature: options?.temperature,
2814
+ maxTokens: options?.maxTokens
2815
+ });
2816
+ const toolCalls = response.toolCalls?.map((tc) => ({
2817
+ name: tc.function.name,
2818
+ arguments: (() => {
2819
+ try {
2820
+ return JSON.parse(tc.function.arguments);
2821
+ } catch {
2822
+ return {};
2823
+ }
2824
+ })()
2825
+ }));
2826
+ return {
2827
+ message: { role: "assistant", content: response.content },
2828
+ toolCalls,
2829
+ usage: {
2830
+ promptTokens: response.usage.inputTokens,
2831
+ completionTokens: response.usage.outputTokens,
2832
+ totalTokens: response.usage.totalTokens
2833
+ }
2834
+ };
2835
+ }
2836
+ /**
2837
+ * Document analysis (summary, field extraction, or QA).
2838
+ * No dedicated onCall exists in the ai domain; implemented via ai-aiChat
2839
+ * with a structured system prompt that targets the requested analysis type.
2840
+ * The document content must be fetched by the caller and passed as text —
2841
+ * the backend has no URL-fetch / OCR pipeline for onCall yet.
2842
+ */
2843
+ async analyzeDocument(documentUrl, analysisType, options) {
2844
+ const systemPromptMap = {
2845
+ summary: "You are a document summarization assistant. Summarize the provided document concisely.",
2846
+ extract: `You are a document extraction assistant. Extract the following fields from the document as JSON: ${(options?.extractFields ?? []).join(", ")}.`,
2847
+ qa: `You are a document QA assistant. Answer the following question based on the document: ${options?.question ?? ""}`
2848
+ };
2849
+ const response = await this.apiClient.dispatch("ai-aiChat", {
2850
+ tenantId: this.getTenantId(),
2851
+ messages: [
2852
+ { role: "system", content: systemPromptMap[analysisType] },
2853
+ { role: "user", content: `Document URL: ${documentUrl}` }
2854
+ ]
2855
+ });
2856
+ let result = response.content;
2857
+ if (analysisType === "extract") {
2858
+ try {
2859
+ result = JSON.parse(response.content);
2860
+ } catch {
2861
+ result = response.content;
2862
+ }
2863
+ }
2864
+ return {
2865
+ result,
2866
+ usage: { totalTokens: response.usage.totalTokens }
2867
+ };
2868
+ }
2869
+ // Text generation
2870
+ async generate(prompt, options) {
2871
+ const response = await this.chat(
2872
+ [{ role: "user", content: prompt }],
2873
+ options
2874
+ );
2875
+ return {
2876
+ text: response.message.content,
2877
+ usage: { totalTokens: response.usage.totalTokens }
2878
+ };
2879
+ }
2880
+ /**
2881
+ * Text summarization via ai-aiChat.
2882
+ * No dedicated onCall exists; implemented as a structured chat request
2883
+ * with a summarization system prompt.
2884
+ */
2885
+ async summarize(text, options) {
2886
+ const styleInstruction = options?.style === "bullet" ? "Use bullet points." : "Write in paragraph form.";
2887
+ const lengthInstruction = options?.maxLength ? ` Keep the summary under ${options.maxLength} words.` : "";
2888
+ const response = await this.apiClient.dispatch("ai-aiChat", {
2889
+ tenantId: this.getTenantId(),
2890
+ messages: [
2891
+ {
2892
+ role: "system",
2893
+ content: `You are a summarization assistant. Summarize the text concisely. ${styleInstruction}${lengthInstruction}`
2894
+ },
2895
+ { role: "user", content: text }
2896
+ ]
2897
+ });
2898
+ return {
2899
+ summary: response.content,
2900
+ usage: { totalTokens: response.usage.totalTokens }
2901
+ };
2902
+ }
2903
+ /**
2904
+ * Text translation via ai-aiChat.
2905
+ * No dedicated onCall exists; implemented as a structured chat request
2906
+ * with a translation system prompt.
2907
+ */
2908
+ async translate(text, targetLanguage, sourceLanguage) {
2909
+ const sourceInstruction = sourceLanguage ? ` The source language is ${sourceLanguage}.` : " Detect the source language automatically.";
2910
+ const response = await this.apiClient.dispatch("ai-aiChat", {
2911
+ tenantId: this.getTenantId(),
2912
+ messages: [
2913
+ {
2914
+ role: "system",
2915
+ content: `You are a translation assistant.${sourceInstruction} Translate the user's text to ${targetLanguage}. Return only the translation, nothing else.`
2916
+ },
2917
+ { role: "user", content: text }
2918
+ ]
2919
+ });
2920
+ return {
2921
+ translation: response.content,
2922
+ usage: { totalTokens: response.usage.totalTokens }
2923
+ };
2924
+ }
2925
+ };
2926
+
2927
+ // src/services/search.ts
2928
+ var MAKEMINDSearch = class {
2929
+ constructor(config) {
2930
+ this.apiClient = config.apiClient;
2931
+ this.getTenantId = config.tenantId;
2932
+ }
2933
+ async query(options) {
2934
+ const params = new URLSearchParams();
2935
+ params.set("q", options.q);
2936
+ if (options.collections) params.set("collections", options.collections.join(","));
2937
+ if (options.page) params.set("page", String(options.page));
2938
+ if (options.perPage) params.set("perPage", String(options.perPage));
2939
+ return this.apiClient.get(`/search?${params.toString()}`);
2940
+ }
2941
+ async advanced(options) {
2942
+ return this.apiClient.post("/search/advanced", options);
2943
+ }
2944
+ async suggestions(options) {
2945
+ const params = new URLSearchParams();
2946
+ params.set("prefix", options.prefix);
2947
+ params.set("collection", options.collection);
2948
+ if (options.limit) params.set("limit", String(options.limit));
2949
+ return this.apiClient.get(`/search/suggestions?${params.toString()}`);
2950
+ }
2951
+ async getAnalytics(options) {
2952
+ const params = new URLSearchParams();
2953
+ if (options?.startDate) params.set("startDate", options.startDate);
2954
+ if (options?.endDate) params.set("endDate", options.endDate);
2955
+ const queryString = params.toString();
2956
+ return this.apiClient.get(`/search/analytics${queryString ? `?${queryString}` : ""}`);
2957
+ }
2958
+ async bulkReindex(options) {
2959
+ return this.apiClient.post("/search/reindex", options || {});
2960
+ }
2961
+ async getBulkReindexStatus(options) {
2962
+ return this.apiClient.get(`/search/reindex/${options.jobId}`);
2963
+ }
2964
+ };
2965
+
2966
+ // src/services/apps.ts
2967
+ var MAKEMINDApps = class {
2968
+ constructor(config) {
2969
+ this.apiClient = config.apiClient;
2970
+ this.getTenantId = config.tenantId;
2971
+ }
2972
+ get tenantId() {
2973
+ const id = this.getTenantId();
2974
+ if (!id) {
2975
+ throw new MAKEMINDError("UNAUTHENTICATED", "No tenant context set");
2976
+ }
2977
+ return id;
2978
+ }
2979
+ // ============================================
2980
+ // App Management
2981
+ // ============================================
2982
+ async createApp(params) {
2983
+ const response = await this.apiClient.post("/apps", { ...params });
2984
+ return response;
2985
+ }
2986
+ async getApp(appId) {
2987
+ try {
2988
+ const response = await this.apiClient.get(`/apps/${appId}`);
2989
+ return response.app;
2990
+ } catch (error) {
2991
+ if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
2992
+ return null;
2993
+ }
2994
+ throw error;
2995
+ }
2996
+ }
2997
+ async listApps(params = {}) {
2998
+ const query = new URLSearchParams();
2999
+ if (params.status) query.append("status", params.status);
3000
+ if (params.limit) query.append("limit", String(params.limit));
3001
+ if (params.startAfter) query.append("startAfter", params.startAfter);
3002
+ const qs = query.toString();
3003
+ const response = await this.apiClient.get(`/apps${qs ? `?${qs}` : ""}`);
3004
+ return response;
3005
+ }
3006
+ async updateApp(appId, params) {
3007
+ await this.apiClient.put(`/apps/${appId}`, params);
3008
+ }
3009
+ async deleteApp(appId, hardDelete = false) {
3010
+ await this.apiClient.delete(`/apps/${appId}${hardDelete ? "?hardDelete=true" : ""}`);
3011
+ }
3012
+ async duplicateApp(appId, newName) {
3013
+ const response = await this.apiClient.post(
3014
+ `/apps/${appId}/duplicate`,
3015
+ { name: newName }
3016
+ );
3017
+ return { appId: response.appId };
3018
+ }
3019
+ // Stream app changes (polling-based)
3020
+ onAppSnapshot(appId, callback) {
3021
+ let isSubscribed = true;
3022
+ const poll = async () => {
3023
+ if (!isSubscribed) return;
3024
+ try {
3025
+ const app = await this.getApp(appId);
3026
+ callback(app);
3027
+ } catch {
3028
+ }
3029
+ if (isSubscribed) {
3030
+ setTimeout(poll, 5e3);
3031
+ }
3032
+ };
3033
+ poll();
3034
+ return () => {
3035
+ isSubscribed = false;
3036
+ };
3037
+ }
3038
+ // ============================================
3039
+ // Page Management
3040
+ // ============================================
3041
+ async createPage(appId, params) {
3042
+ const response = await this.apiClient.post(
3043
+ `/apps/${appId}/pages`,
3044
+ params
3045
+ );
3046
+ return response;
3047
+ }
3048
+ async getPage(appId, pageId) {
3049
+ try {
3050
+ const response = await this.apiClient.get(
3051
+ `/apps/${appId}/pages/${pageId}`
3052
+ );
3053
+ return response.page;
3054
+ } catch (error) {
3055
+ if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
3056
+ return null;
3057
+ }
3058
+ throw error;
3059
+ }
3060
+ }
3061
+ async listPages(appId) {
3062
+ const response = await this.apiClient.get(`/apps/${appId}/pages`);
3063
+ return response.pages;
3064
+ }
3065
+ async updatePage(appId, pageId, params) {
3066
+ await this.apiClient.put(`/apps/${appId}/pages/${pageId}`, params);
3067
+ }
3068
+ async deletePage(appId, pageId) {
3069
+ await this.apiClient.delete(`/apps/${appId}/pages/${pageId}`);
3070
+ }
3071
+ async reorderPages(appId, order) {
3072
+ await this.apiClient.put(`/apps/${appId}/pages/reorder`, { order });
3073
+ }
3074
+ async duplicatePage(appId, pageId, newName, newPath) {
3075
+ const response = await this.apiClient.post(
3076
+ `/apps/${appId}/pages/${pageId}/duplicate`,
3077
+ { newName, newPath }
3078
+ );
3079
+ return response;
3080
+ }
3081
+ // ============================================
3082
+ // Publishing & Versioning
3083
+ // ============================================
3084
+ async publish(appId, changelog) {
3085
+ const response = await this.apiClient.post(`/apps/${appId}/publish`, { changelog });
3086
+ return response;
3087
+ }
3088
+ async unpublish(appId) {
3089
+ await this.apiClient.post(`/apps/${appId}/unpublish`, {});
3090
+ }
3091
+ async rollback(appId, targetVersion) {
3092
+ const response = await this.apiClient.post(`/apps/${appId}/rollback`, { targetVersion });
3093
+ return response;
3094
+ }
3095
+ async getVersions(appId, limit = 20) {
3096
+ const response = await this.apiClient.get(
3097
+ `/apps/${appId}/versions?limit=${limit}`
3098
+ );
3099
+ return response.versions;
3100
+ }
3101
+ async getVersionDetail(appId, versionId) {
3102
+ const response = await this.apiClient.get(`/apps/${appId}/versions/${versionId}`);
3103
+ return response.version;
3104
+ }
3105
+ async compareVersions(appId, version1, version2) {
3106
+ const response = await this.apiClient.get(`/apps/${appId}/versions/compare?version1=${version1}&version2=${version2}`);
3107
+ return response.comparison;
3108
+ }
3109
+ // ============================================
3110
+ // Domain Management
3111
+ // ============================================
3112
+ /**
3113
+ * Add a platform subdomain (`<subdomain>.<platform-host>`) for an app.
3114
+ * `target` is an optional generic reverse-proxy routing target.
3115
+ *
3116
+ * Requires domain-service activation. The control-plane call is live, but
3117
+ * a registered domain/target does not serve traffic until the
3118
+ * domain-serving edge is provisioned (Phase 2). Custom and nested
3119
+ * (sub-tenant) domains additionally require an active domain entitlement
3120
+ * (paid SKU); pricing is not yet set. Subdomains are free.
3121
+ */
3122
+ async addSubdomain(appId, subdomain, target) {
3123
+ const response = await this.apiClient.post(`/apps/${appId}/domains/subdomain`, { subdomain, target });
3124
+ return response;
3125
+ }
3126
+ /**
3127
+ * Add a custom (BYO) domain for an app. Returns a verification token and
3128
+ * DNS records the caller must publish before `verifyDomain` will pass.
3129
+ * `target` is an optional generic reverse-proxy routing target.
3130
+ *
3131
+ * Requires domain-service activation. The control-plane call is live, but
3132
+ * a registered domain/target does not serve traffic until the
3133
+ * domain-serving edge is provisioned (Phase 2). Custom and nested
3134
+ * (sub-tenant) domains additionally require an active domain entitlement
3135
+ * (paid SKU); pricing is not yet set. Subdomains are free.
3136
+ */
3137
+ async addCustomDomain(appId, domain, target) {
3138
+ const response = await this.apiClient.post(`/apps/${appId}/domains/custom`, { domain, target });
3139
+ return response;
3140
+ }
3141
+ async verifyDomain(appId, domain) {
3142
+ const response = await this.apiClient.post(`/apps/${appId}/domains/${encodeURIComponent(domain)}/verify`, {});
3143
+ return response;
3144
+ }
3145
+ async removeDomain(appId, domain) {
3146
+ await this.apiClient.delete(`/apps/${appId}/domains/${encodeURIComponent(domain)}`);
3147
+ }
3148
+ async getDomainStatus(appId) {
3149
+ const response = await this.apiClient.get(
3150
+ `/apps/${appId}/domains`
3151
+ );
3152
+ return response.domains;
3153
+ }
3154
+ // ============================================
3155
+ // Web Entries — an address that opens an MCP origin as a web app
3156
+ //
3157
+ // An entry gives an app a public address: visiting it serves the renderer
3158
+ // and a per-host document that declares which origin to open. Dispatch ops
3159
+ // under the `apps-` prefix, `domains:read` / `domains:write` — an entry IS a
3160
+ // registry hostname, so splitting authority over one resource across a
3161
+ // second scope name would only make it ambiguous.
3162
+ // ============================================
3163
+ /**
3164
+ * Mint an address for an app.
3165
+ *
3166
+ * The caller proposes a **label**, not a hostname: the platform derives and
3167
+ * reserves the host under its own uniqueness and reserved-name rules. A
3168
+ * caller cannot claim a hostname it does not own, and the origin is derived
3169
+ * from the app being bound rather than supplied — the whole capability rests
3170
+ * on the issuer deciding the origin, and a free-text URL field is exactly how
3171
+ * that property gets lost.
3172
+ *
3173
+ * `display` feeds both the share metadata (`og:*`) and the boot declaration,
3174
+ * so a link preview and the running app cannot disagree.
3175
+ */
3176
+ async mintWebEntry(params) {
3177
+ return this.apiClient.dispatch("apps-mintWebEntry", params);
3178
+ }
3179
+ /** Read one entry the caller owns. Requires `domains:read`. */
3180
+ async getWebEntry(host) {
3181
+ return this.apiClient.dispatch("apps-getWebEntry", { host });
3182
+ }
3183
+ /** List the caller's entries, optionally narrowed to one party. Requires `domains:read`. */
3184
+ async listWebEntries(params = {}) {
3185
+ return this.apiClient.dispatch("apps-listWebEntries", params);
3186
+ }
3187
+ /**
3188
+ * Repoint an entry at a different (still proven) origin, or change how it
3189
+ * appears when shared. The address itself is immutable — a different address
3190
+ * is a different entry.
3191
+ */
3192
+ async updateWebEntry(params) {
3193
+ return this.apiClient.dispatch("apps-updateWebEntry", params);
3194
+ }
3195
+ /** Retire an entry — the address stops serving immediately. Requires `domains:write`. */
3196
+ async retireWebEntry(host) {
3197
+ return this.apiClient.dispatch("apps-retireWebEntry", { host });
3198
+ }
3199
+ // ============================================
3200
+ // Delegated (Sub-Tenant) Domains
3201
+ //
3202
+ // "Custom domains for your customers" — a tenant registers/verifies/lists/
3203
+ // removes domains on behalf of its own sub-tenants (nested delegation).
3204
+ // These are dispatch ops (no REST path); routed through the gateway RPC
3205
+ // dispatch route under the `apps-` prefix. All four require an API key
3206
+ // with the noted scope.
3207
+ // ============================================
3208
+ /**
3209
+ * Register a custom domain on behalf of one of the caller tenant's
3210
+ * sub-tenants ("custom domains for your customers"). `parentTenantId`,
3211
+ * when provided, must equal the caller's own tenantId — the server never
3212
+ * trusts a client-supplied parent tenant for cross-tenant registration.
3213
+ * Requires API key scope `domains:write`.
3214
+ *
3215
+ * Requires domain-service activation. The control-plane call is live, but
3216
+ * a registered domain/target does not serve traffic until the
3217
+ * domain-serving edge is provisioned (Phase 2). Custom and nested
3218
+ * (sub-tenant) domains additionally require an active domain entitlement
3219
+ * (paid SKU); pricing is not yet set. Subdomains are free.
3220
+ */
3221
+ async registerDomainForSubTenant(params) {
3222
+ return this.apiClient.dispatch("apps-registerDomainForSubTenant", params);
3223
+ }
3224
+ /**
3225
+ * Verify a delegated sub-tenant domain's DNS configuration.
3226
+ * Requires API key scope `domains:write`.
3227
+ *
3228
+ * Requires domain-service activation. The control-plane call is live, but
3229
+ * a registered domain/target does not serve traffic until the
3230
+ * domain-serving edge is provisioned (Phase 2). Custom and nested
3231
+ * (sub-tenant) domains additionally require an active domain entitlement
3232
+ * (paid SKU); pricing is not yet set. Subdomains are free.
3233
+ */
3234
+ async verifyDomainForSubTenant(domain) {
3235
+ return this.apiClient.dispatch("apps-verifyDomainForSubTenant", { domain });
3236
+ }
3237
+ /**
3238
+ * List the caller tenant's delegated sub-tenant domains, optionally
3239
+ * filtered to one `subTenantId`. Requires API key scope `domains:read`.
3240
+ *
3241
+ * Requires domain-service activation. The control-plane call is live, but
3242
+ * a registered domain/target does not serve traffic until the
3243
+ * domain-serving edge is provisioned (Phase 2). Custom and nested
3244
+ * (sub-tenant) domains additionally require an active domain entitlement
3245
+ * (paid SKU); pricing is not yet set. Subdomains are free.
3246
+ */
3247
+ async listSubTenantDomains(subTenantId) {
3248
+ return this.apiClient.dispatch(
3249
+ "apps-listSubTenantDomains",
3250
+ { subTenantId }
3251
+ );
3252
+ }
3253
+ /**
3254
+ * Remove a delegated sub-tenant domain. Requires API key scope
3255
+ * `domains:write`.
3256
+ *
3257
+ * Requires domain-service activation. The control-plane call is live, but
3258
+ * a registered domain/target does not serve traffic until the
3259
+ * domain-serving edge is provisioned (Phase 2). Custom and nested
3260
+ * (sub-tenant) domains additionally require an active domain entitlement
3261
+ * (paid SKU); pricing is not yet set. Subdomains are free.
3262
+ */
3263
+ async removeSubTenantDomain(domain) {
3264
+ return this.apiClient.dispatch("apps-removeSubTenantDomain", {
3265
+ domain
3266
+ });
3267
+ }
3268
+ // ============================================
3269
+ // Analytics
3270
+ // ============================================
3271
+ async getAnalytics(appId, params) {
3272
+ const query = new URLSearchParams();
3273
+ if (params?.startDate) query.append("startDate", params.startDate);
3274
+ if (params?.endDate) query.append("endDate", params.endDate);
3275
+ const qs = query.toString();
3276
+ const response = await this.apiClient.get(
3277
+ `/apps/${appId}/analytics${qs ? `?${qs}` : ""}`
3278
+ );
3279
+ return response.analytics;
3280
+ }
3281
+ // ============================================
3282
+ // AI Features
3283
+ // ============================================
3284
+ async generateUI(prompt, context, appId) {
3285
+ const response = await this.apiClient.post(
3286
+ "/apps/ai/generate-ui",
3287
+ { prompt, context, appId }
3288
+ );
3289
+ return { components: response.components };
3290
+ }
3291
+ async suggestActions(appId, componentType, context) {
3292
+ const response = await this.apiClient.post(`/apps/${appId}/ai/suggest-actions`, { componentType, context });
3293
+ return { actions: response.suggestions };
3294
+ }
3295
+ // ============================================
3296
+ // Collaboration
3297
+ // ============================================
3298
+ async startSession(appId, pageId) {
3299
+ const response = await this.apiClient.post(`/apps/${appId}/pages/${pageId}/sessions`, {});
3300
+ return { sessionToken: response.sessionToken, wsEndpoint: response.wsEndpoint };
3301
+ }
3302
+ async addComment(appId, pageId, componentId, content, threadId) {
3303
+ const response = await this.apiClient.post(
3304
+ `/apps/${appId}/pages/${pageId}/comments`,
3305
+ { componentId, content, threadId }
3306
+ );
3307
+ return response;
3308
+ }
3309
+ async resolveComment(appId, pageId, commentId) {
3310
+ await this.apiClient.post(
3311
+ `/apps/${appId}/pages/${pageId}/comments/${commentId}/resolve`,
3312
+ {}
3313
+ );
3314
+ }
3315
+ };
3316
+
3317
+ // src/services/marketplace.ts
3318
+ var MAKEMINDMarketplace = class {
3319
+ constructor(config) {
3320
+ this.apiClient = config.apiClient;
3321
+ this.getTenantId = config.tenantId;
3322
+ }
3323
+ // ============================================
3324
+ // Discovery
3325
+ // ============================================
3326
+ async search(options = {}) {
3327
+ const params = new URLSearchParams();
3328
+ if (options.query) params.set("query", options.query);
3329
+ if (options.category) params.set("category", options.category);
3330
+ if (options.tags) params.set("tags", options.tags.join(","));
3331
+ if (options.pricingModel) params.set("pricingModel", options.pricingModel.join(","));
3332
+ if (options.sortBy) params.set("sortBy", options.sortBy);
3333
+ if (options.page) params.set("page", String(options.page));
3334
+ if (options.limit) params.set("limit", String(options.limit));
3335
+ const qs = params.toString();
3336
+ return this.apiClient.get(`/marketplace/listings${qs ? `?${qs}` : ""}`);
3337
+ }
3338
+ async getDetail(listingId) {
3339
+ return this.apiClient.get(`/marketplace/listings/${listingId}`);
3340
+ }
3341
+ async browseCategory(categorySlug, options) {
3342
+ const params = new URLSearchParams();
3343
+ if (options?.sortBy) params.set("sortBy", options.sortBy);
3344
+ if (options?.page) params.set("page", String(options.page));
3345
+ if (options?.limit) params.set("limit", String(options.limit));
3346
+ const qs = params.toString();
3347
+ return this.apiClient.get(`/marketplace/categories/${categorySlug}/listings${qs ? `?${qs}` : ""}`);
3348
+ }
3349
+ async getFeatured() {
3350
+ return this.apiClient.get("/marketplace/featured");
3351
+ }
3352
+ async getTrending() {
3353
+ return this.apiClient.get("/marketplace/trending");
3354
+ }
3355
+ async getRecommendations() {
3356
+ return this.apiClient.get("/marketplace/recommendations");
3357
+ }
3358
+ async listCategories() {
3359
+ return this.apiClient.get("/marketplace/categories");
3360
+ }
3361
+ // ============================================
3362
+ // Installation
3363
+ // ============================================
3364
+ async install(listingId) {
3365
+ return this.apiClient.post("/marketplace/install", { listingId });
3366
+ }
3367
+ async uninstall(listingId, cleanupData = false) {
3368
+ return this.apiClient.post("/marketplace/uninstall", { listingId, cleanupData });
3369
+ }
3370
+ async listInstalled(options) {
3371
+ const params = new URLSearchParams();
3372
+ if (options?.status) params.set("status", options.status);
3373
+ if (options?.page) params.set("page", String(options.page));
3374
+ if (options?.limit) params.set("limit", String(options.limit));
3375
+ const qs = params.toString();
3376
+ return this.apiClient.get(`/marketplace/installed${qs ? `?${qs}` : ""}`);
3377
+ }
3378
+ async updateApp(listingId) {
3379
+ return this.apiClient.post(`/marketplace/installed/${listingId}/update`);
3380
+ }
3381
+ // ============================================
3382
+ // App Runtime (proxy, config, credentials)
3383
+ // ============================================
3384
+ async proxy(bindingId, options) {
3385
+ const queryStr = options.query ? `?${new URLSearchParams(options.query).toString()}` : "";
3386
+ const path = `/marketplace/apps/${bindingId}/proxy/${options.path.replace(/^\//, "")}${queryStr}`;
3387
+ return this.apiClient.request(path, { method: options.method, body: options.body });
3388
+ }
3389
+ async getApiSpec(bindingId) {
3390
+ return this.apiClient.get(`/marketplace/apps/${bindingId}/spec`);
3391
+ }
3392
+ async getConfig(bindingId) {
3393
+ return this.apiClient.get(`/marketplace/apps/${bindingId}/config`);
3394
+ }
3395
+ async updateConfig(bindingId, updates) {
3396
+ return this.apiClient.put(`/marketplace/apps/${bindingId}/config`, updates);
3397
+ }
3398
+ async issueCredentials(bindingId) {
3399
+ return this.apiClient.post(`/marketplace/apps/${bindingId}/credentials`);
3400
+ }
3401
+ async revokeCredentials(bindingId) {
3402
+ return this.apiClient.delete(`/marketplace/apps/${bindingId}/credentials`);
3403
+ }
3404
+ // ============================================
3405
+ // Reviews
3406
+ // ============================================
3407
+ async createReview(input) {
3408
+ return this.apiClient.post("/marketplace/reviews", input);
3409
+ }
3410
+ async updateReview(reviewId, listingId, updates) {
3411
+ return this.apiClient.put(`/marketplace/reviews/${reviewId}`, { listingId, ...updates });
3412
+ }
3413
+ async deleteReview(reviewId, listingId) {
3414
+ return this.apiClient.delete(`/marketplace/reviews/${reviewId}`, { body: { listingId } });
3415
+ }
3416
+ async listReviews(listingId, options) {
3417
+ const params = new URLSearchParams();
3418
+ if (options?.sortBy) params.set("sortBy", options.sortBy);
3419
+ if (options?.page) params.set("page", String(options.page));
3420
+ if (options?.limit) params.set("limit", String(options.limit));
3421
+ const qs = params.toString();
3422
+ return this.apiClient.get(`/marketplace/listings/${listingId}/reviews${qs ? `?${qs}` : ""}`);
3423
+ }
3424
+ async replyToReview(reviewId, listingId, body) {
3425
+ return this.apiClient.post(`/marketplace/reviews/${reviewId}/reply`, { listingId, body });
3426
+ }
3427
+ async reportReview(reviewId, listingId, reason) {
3428
+ return this.apiClient.post(`/marketplace/reviews/${reviewId}/report`, { listingId, reason });
3429
+ }
3430
+ async voteHelpful(reviewId, listingId) {
3431
+ return this.apiClient.post(`/marketplace/reviews/${reviewId}/helpful`, { listingId });
3432
+ }
3433
+ // ============================================
3434
+ // Publishing
3435
+ // ============================================
3436
+ async createPublisherProfile(input) {
3437
+ return this.apiClient.post("/marketplace/publisher", input);
3438
+ }
3439
+ async updatePublisherProfile(updates) {
3440
+ return this.apiClient.put("/marketplace/publisher", updates);
3441
+ }
3442
+ async getDashboard() {
3443
+ return this.apiClient.get("/marketplace/publisher/dashboard");
3444
+ }
3445
+ async createListing(input) {
3446
+ return this.apiClient.post("/marketplace/publisher/listings", input);
3447
+ }
3448
+ async updateListing(listingId, updates) {
3449
+ return this.apiClient.put(`/marketplace/publisher/listings/${listingId}`, updates);
3450
+ }
3451
+ async submitForReview(listingId) {
3452
+ return this.apiClient.post(`/marketplace/publisher/listings/${listingId}/submit`);
3453
+ }
3454
+ async publishListing(listingId) {
3455
+ return this.apiClient.post(`/marketplace/publisher/listings/${listingId}/publish`);
3456
+ }
3457
+ async archiveListing(listingId) {
3458
+ return this.apiClient.post(`/marketplace/publisher/listings/${listingId}/archive`);
3459
+ }
3460
+ async reactivateListing(listingId) {
3461
+ return this.apiClient.post(`/marketplace/publisher/listings/${listingId}/reactivate`);
3462
+ }
3463
+ async createVersion(listingId, version, changelog, appVersionId, breakingChanges = false) {
3464
+ return this.apiClient.post(`/marketplace/publisher/listings/${listingId}/versions`, { version, changelog, appVersionId, breakingChanges });
3465
+ }
3466
+ // ============================================
3467
+ // Admin (Platform Admin only)
3468
+ // ============================================
3469
+ async approveListing(listingId) {
3470
+ return this.apiClient.post(`/marketplace/admin/listings/${listingId}/approve`);
3471
+ }
3472
+ async rejectListing(listingId, reason) {
3473
+ return this.apiClient.post(`/marketplace/admin/listings/${listingId}/reject`, { reason });
3474
+ }
3475
+ async suspendListing(listingId, reason) {
3476
+ return this.apiClient.post(`/marketplace/admin/listings/${listingId}/suspend`, { reason });
3477
+ }
3478
+ async approveVersion(listingId, versionId) {
3479
+ return this.apiClient.post(`/marketplace/admin/listings/${listingId}/versions/${versionId}/approve`);
3480
+ }
3481
+ async verifyPublisher(tenantId) {
3482
+ return this.apiClient.post(`/marketplace/admin/publishers/${tenantId}/verify`);
3483
+ }
3484
+ async suspendPublisher(tenantId, reason) {
3485
+ return this.apiClient.post(`/marketplace/admin/publishers/${tenantId}/suspend`, { reason });
3486
+ }
3487
+ async reactivatePublisher(tenantId) {
3488
+ return this.apiClient.post(`/marketplace/admin/publishers/${tenantId}/reactivate`);
3489
+ }
3490
+ async adminCreateCategory(data) {
3491
+ return this.apiClient.post("/marketplace/admin/categories", data);
3492
+ }
3493
+ async adminUpdateCategory(categoryId, data) {
3494
+ return this.apiClient.put(`/marketplace/admin/categories/${categoryId}`, data);
3495
+ }
3496
+ async adminDeleteCategory(categoryId) {
3497
+ return this.apiClient.delete(`/marketplace/admin/categories/${categoryId}`);
3498
+ }
3499
+ async getAnalytics(params) {
3500
+ const qs = new URLSearchParams();
3501
+ if (params?.startDate) qs.set("startDate", params.startDate);
3502
+ if (params?.endDate) qs.set("endDate", params.endDate);
3503
+ if (params?.granularity) qs.set("granularity", params.granularity);
3504
+ const queryStr = qs.toString();
3505
+ return this.apiClient.get(`/marketplace/admin/analytics${queryStr ? `?${queryStr}` : ""}`);
3506
+ }
3507
+ async getPendingListings(params) {
3508
+ const qs = new URLSearchParams();
3509
+ if (params?.status) qs.set("status", params.status);
3510
+ if (params?.page) qs.set("page", String(params.page));
3511
+ if (params?.limit) qs.set("limit", String(params.limit));
3512
+ const queryStr = qs.toString();
3513
+ return this.apiClient.get(`/marketplace/admin/pending-listings${queryStr ? `?${queryStr}` : ""}`);
3514
+ }
3515
+ async listPublishers(params) {
3516
+ const qs = new URLSearchParams();
3517
+ if (params?.status) qs.set("status", params.status);
3518
+ if (params?.page) qs.set("page", String(params.page));
3519
+ if (params?.limit) qs.set("limit", String(params.limit));
3520
+ const queryStr = qs.toString();
3521
+ return this.apiClient.get(`/marketplace/admin/publishers${queryStr ? `?${queryStr}` : ""}`);
3522
+ }
3523
+ };
3524
+
3525
+ // src/utils/api-client.ts
3526
+ var ApiClient = class {
3527
+ constructor(config) {
3528
+ this.baseUrl = config.baseUrl.replace(/\/$/, "").replace(/\/api\/v\d+$/, "");
3529
+ this.apiVersion = config.apiVersion || "v1";
3530
+ this.apiKey = config.apiKey;
3531
+ this.applicationId = config.applicationId;
3532
+ this.getAuthToken = config.getAuthToken;
3533
+ this.getTenantId = config.getTenantId;
3534
+ this.getAppCheckToken = config.getAppCheckToken;
3535
+ }
3536
+ async request(endpoint, options = {}) {
3537
+ const { method = "GET", body, headers = {}, skipAuth = false } = options;
3538
+ const url = `${this.baseUrl}/api/${this.apiVersion}${endpoint}`;
3539
+ const requestHeaders = {
3540
+ "Content-Type": "application/json",
3541
+ "X-API-Key": this.apiKey,
3542
+ "X-Application-ID": this.applicationId,
3543
+ ...headers
3544
+ };
3545
+ const authToken = this.getAuthToken();
3546
+ if (authToken && !skipAuth) {
3547
+ requestHeaders["Authorization"] = `Bearer ${authToken}`;
3548
+ }
3549
+ const tenantId = this.getTenantId();
3550
+ if (tenantId) {
3551
+ requestHeaders["X-Tenant-ID"] = tenantId;
3552
+ }
3553
+ if (this.getAppCheckToken) {
3554
+ const appCheckToken = await this.getAppCheckToken();
3555
+ if (appCheckToken) {
3556
+ requestHeaders["X-Firebase-AppCheck"] = appCheckToken;
3557
+ }
3558
+ }
3559
+ const csrf = readCsrfToken();
3560
+ if (csrf) requestHeaders["X-MM-CSRF"] = csrf;
3561
+ const response = await fetch(url, {
3562
+ method,
3563
+ headers: requestHeaders,
3564
+ credentials: "include",
3565
+ body: body ? JSON.stringify(body) : void 0
3566
+ });
3567
+ if (!response.ok) {
3568
+ await this.handleErrorResponse(response);
3569
+ }
3570
+ const contentType = response.headers.get("content-type");
3571
+ if (contentType?.includes("application/json")) {
3572
+ const json = await response.json();
3573
+ return json.data !== void 0 ? json.data : json;
3574
+ }
3575
+ return {};
3576
+ }
3577
+ async handleErrorResponse(response) {
3578
+ let body = {};
3579
+ try {
3580
+ body = await response.json();
3581
+ } catch {
3582
+ }
3583
+ const errorData = body.error ?? body;
3584
+ const code = errorData.code || this.mapStatusToCode(response.status);
3585
+ const message = errorData.message || response.statusText || `Request failed (HTTP ${response.status})`;
3586
+ const details = errorData.requestId ? { ...errorData.details ?? {}, requestId: errorData.requestId } : errorData.details;
3587
+ throw new MAKEMINDError(code, message, details);
3588
+ }
3589
+ mapStatusToCode(status) {
3590
+ switch (status) {
3591
+ case 400:
3592
+ return ErrorCodes.VALIDATION_ERROR;
3593
+ case 401:
3594
+ return ErrorCodes.UNAUTHENTICATED;
3595
+ case 403:
3596
+ return ErrorCodes.PERMISSION_DENIED;
3597
+ case 404:
3598
+ return ErrorCodes.NOT_FOUND;
3599
+ case 409:
3600
+ return ErrorCodes.ALREADY_EXISTS;
3601
+ case 429:
3602
+ return ErrorCodes.QUOTA_EXCEEDED;
3603
+ case 500:
3604
+ return ErrorCodes.INTERNAL_ERROR;
3605
+ case 503:
3606
+ return ErrorCodes.SERVICE_UNAVAILABLE;
3607
+ default:
3608
+ return ErrorCodes.UNKNOWN_ERROR;
3609
+ }
3610
+ }
3611
+ // Convenience methods
3612
+ async get(endpoint, options) {
3613
+ return this.request(endpoint, { ...options, method: "GET" });
3614
+ }
3615
+ async post(endpoint, body, options) {
3616
+ return this.request(endpoint, { ...options, method: "POST", body });
3617
+ }
3618
+ async put(endpoint, body, options) {
3619
+ return this.request(endpoint, { ...options, method: "PUT", body });
3620
+ }
3621
+ async patch(endpoint, body, options) {
3622
+ return this.request(endpoint, { ...options, method: "PATCH", body });
3623
+ }
3624
+ async delete(endpoint, options) {
3625
+ return this.request(endpoint, { ...options, method: "DELETE" });
3626
+ }
3627
+ /**
3628
+ * Execute a tenant-defined logic function by name via the logic service.
3629
+ * This is NOT the gateway RPC dispatch — use `dispatch()` for Callable operations.
3630
+ */
3631
+ async call(functionName, data) {
3632
+ return this.post("/logic/call", { functionName, input: data });
3633
+ }
3634
+ /**
3635
+ * Invoke a platform Callable operation through the gateway RPC dispatch route
3636
+ * (`POST /api/v<version>/call/:operation`). The operation key is the deployed
3637
+ * Callable name (`<domain>-<name>` for namespaced groups, bare name for flat
3638
+ * exports). The gateway expects the payload under a `data` envelope.
3639
+ */
3640
+ async dispatch(operation, payload) {
3641
+ return this.post(`/call/${operation}`, { data: payload ?? {} });
3642
+ }
3643
+ /**
3644
+ * Get the version-qualified base URL for direct API calls (e.g. streaming),
3645
+ * including the `/api/<version>` segment.
3646
+ */
3647
+ getBaseUrl() {
3648
+ return `${this.baseUrl}/api/${this.apiVersion}`;
3649
+ }
3650
+ /**
3651
+ * Get auth headers for direct API calls (e.g., streaming)
3652
+ */
3653
+ getAuthHeaders() {
3654
+ const headers = {
3655
+ "X-API-Key": this.apiKey,
3656
+ "X-Application-ID": this.applicationId
3657
+ };
3658
+ const authToken = this.getAuthToken();
3659
+ if (authToken) {
3660
+ headers["Authorization"] = `Bearer ${authToken}`;
3661
+ }
3662
+ const tenantId = this.getTenantId();
3663
+ if (tenantId) {
3664
+ headers["X-Tenant-ID"] = tenantId;
3665
+ }
3666
+ return headers;
3667
+ }
3668
+ };
3669
+ function readCsrfToken() {
3670
+ if (typeof document === "undefined") return void 0;
3671
+ for (const part of document.cookie.split(";")) {
3672
+ const eq = part.indexOf("=");
3673
+ if (eq < 0) continue;
3674
+ if (part.slice(0, eq).trim() === "mm_csrf") {
3675
+ return decodeURIComponent(part.slice(eq + 1).trim());
3676
+ }
3677
+ }
3678
+ return void 0;
3679
+ }
3680
+
3681
+ // src/client.ts
3682
+ var MAKEMINDClient = class {
3683
+ constructor(config) {
3684
+ this._auth = null;
3685
+ this._data = null;
3686
+ this._logic = null;
3687
+ this._tenant = null;
3688
+ this._messaging = null;
3689
+ this._settlement = null;
3690
+ this._partners = null;
3691
+ this._storage = null;
3692
+ this._ai = null;
3693
+ this._search = null;
3694
+ this._apps = null;
3695
+ this._marketplace = null;
3696
+ this.currentTenantId = null;
3697
+ this.currentUserId = null;
3698
+ this.authToken = null;
3699
+ this.applicationId = config.applicationId;
3700
+ this.environment = config.environment || "production";
3701
+ this.apiClient = new ApiClient({
3702
+ baseUrl: config.baseUrl || "https://api.makemind.app",
3703
+ apiVersion: config.apiVersion,
3704
+ apiKey: config.apiKey,
3705
+ applicationId: config.applicationId,
3706
+ getAuthToken: () => this.authToken,
3707
+ getTenantId: () => this.currentTenantId,
3708
+ getAppCheckToken: config.getAppCheckToken
3709
+ });
3710
+ this._auth = new MAKEMINDAuth({
3711
+ apiClient: this.apiClient,
3712
+ applicationId: this.applicationId,
3713
+ onAuthStateChange: (user) => {
3714
+ if (user) {
3715
+ this.currentUserId = user.uid;
3716
+ this.currentTenantId = user.tenantId;
3717
+ } else {
3718
+ this.currentUserId = null;
3719
+ this.currentTenantId = null;
3720
+ this.authToken = null;
3721
+ }
3722
+ },
3723
+ setAuthToken: (token) => {
3724
+ this.authToken = token;
3725
+ }
3726
+ });
3727
+ }
3728
+ // Internal: Set auth token after login
3729
+ setAuthToken(token) {
3730
+ this.authToken = token;
3731
+ }
3732
+ // Internal: Set user context after login
3733
+ setUserContext(userId, tenantId) {
3734
+ this.currentUserId = userId;
3735
+ this.currentTenantId = tenantId;
3736
+ }
3737
+ // Getters for context
3738
+ get tenantId() {
3739
+ return this.currentTenantId;
3740
+ }
3741
+ get userId() {
3742
+ return this.currentUserId;
3743
+ }
3744
+ // Set tenant context manually (for switching tenants)
3745
+ setTenantContext(tenantId) {
3746
+ this.currentTenantId = tenantId;
3747
+ }
3748
+ // Get API client for direct access
3749
+ getApiClient() {
3750
+ return this.apiClient;
3751
+ }
3752
+ /**
3753
+ * Invoke a platform Callable operation through the gateway RPC dispatch
3754
+ * (`POST /api/v1/call/:operation`). The operation name matches the deployed
3755
+ * Callable key (namespaced groups use `<prefix>-<name>`, e.g.
3756
+ * `settlement-getMRR`; flat exports use the bare name, e.g. `createTenant`).
3757
+ *
3758
+ * This is the platform-operation channel, distinct from `logic.call()` which
3759
+ * runs a tenant-defined logic function. Each operation enforces its own
3760
+ * authorization server-side (platform roles / tenant scopes), so exposing
3761
+ * this method does not bypass access control. Returns the operation's result
3762
+ * value directly (the `{ data }` envelope is already unwrapped).
3763
+ */
3764
+ call(operation, input) {
3765
+ return this.apiClient.dispatch(operation, input);
3766
+ }
3767
+ // Service accessors
3768
+ get auth() {
3769
+ return this._auth;
3770
+ }
3771
+ get data() {
3772
+ if (!this._data) {
3773
+ this._data = new MAKEMINDData({
3774
+ apiClient: this.apiClient,
3775
+ applicationId: this.applicationId,
3776
+ tenantId: () => this.currentTenantId
3777
+ });
3778
+ }
3779
+ return this._data;
3780
+ }
3781
+ get logic() {
3782
+ if (!this._logic) {
3783
+ this._logic = new MAKEMINDLogic({
3784
+ apiClient: this.apiClient,
3785
+ applicationId: this.applicationId,
3786
+ tenantId: () => this.currentTenantId
3787
+ });
3788
+ }
3789
+ return this._logic;
3790
+ }
3791
+ get tenant() {
3792
+ if (!this._tenant) {
3793
+ this._tenant = new MAKEMINDTenant({
3794
+ apiClient: this.apiClient,
3795
+ applicationId: this.applicationId,
3796
+ tenantId: () => this.currentTenantId,
3797
+ onTenantCreated: async (tenantId) => {
3798
+ this.currentTenantId = tenantId;
3799
+ try {
3800
+ await this.auth.refreshAuthToken();
3801
+ } catch {
3802
+ }
3803
+ this.currentTenantId = tenantId;
3804
+ }
3805
+ });
3806
+ }
3807
+ return this._tenant;
3808
+ }
3809
+ get messaging() {
3810
+ if (!this._messaging) {
3811
+ this._messaging = new MAKEMINDMessaging({
3812
+ apiClient: this.apiClient,
3813
+ applicationId: this.applicationId,
3814
+ tenantId: () => this.currentTenantId,
3815
+ userId: () => this.currentUserId
3816
+ });
3817
+ }
3818
+ return this._messaging;
3819
+ }
3820
+ get settlement() {
3821
+ if (!this._settlement) {
3822
+ this._settlement = new MAKEMINDSettlement({
3823
+ apiClient: this.apiClient,
3824
+ applicationId: this.applicationId,
3825
+ tenantId: () => this.currentTenantId
3826
+ });
3827
+ }
3828
+ return this._settlement;
3829
+ }
3830
+ get partners() {
3831
+ if (!this._partners) {
3832
+ this._partners = new MAKEMINDPartners({
3833
+ apiClient: this.apiClient,
3834
+ applicationId: this.applicationId,
3835
+ tenantId: () => this.currentTenantId
3836
+ });
3837
+ }
3838
+ return this._partners;
3839
+ }
3840
+ get storage() {
3841
+ if (!this._storage) {
3842
+ this._storage = new MAKEMINDStorage({
3843
+ apiClient: this.apiClient,
3844
+ applicationId: this.applicationId,
3845
+ tenantId: () => this.currentTenantId
3846
+ });
3847
+ }
3848
+ return this._storage;
3849
+ }
3850
+ get ai() {
3851
+ if (!this._ai) {
3852
+ this._ai = new MAKEMINDAI({
3853
+ apiClient: this.apiClient,
3854
+ applicationId: this.applicationId,
3855
+ tenantId: () => this.currentTenantId
3856
+ });
3857
+ }
3858
+ return this._ai;
3859
+ }
3860
+ get search() {
3861
+ if (!this._search) {
3862
+ this._search = new MAKEMINDSearch({
3863
+ apiClient: this.apiClient,
3864
+ applicationId: this.applicationId,
3865
+ tenantId: () => this.currentTenantId
3866
+ });
3867
+ }
3868
+ return this._search;
3869
+ }
3870
+ get apps() {
3871
+ if (!this._apps) {
3872
+ this._apps = new MAKEMINDApps({
3873
+ apiClient: this.apiClient,
3874
+ applicationId: this.applicationId,
3875
+ tenantId: () => this.currentTenantId
3876
+ });
3877
+ }
3878
+ return this._apps;
3879
+ }
3880
+ get marketplace() {
3881
+ if (!this._marketplace) {
3882
+ this._marketplace = new MAKEMINDMarketplace({
3883
+ apiClient: this.apiClient,
3884
+ applicationId: this.applicationId,
3885
+ tenantId: () => this.currentTenantId
3886
+ });
3887
+ }
3888
+ return this._marketplace;
3889
+ }
3890
+ };
3891
+ var defaultClient = null;
3892
+ function initializeMAKEMIND(config) {
3893
+ defaultClient = new MAKEMINDClient(config);
3894
+ return defaultClient;
3895
+ }
3896
+ function getMAKEMIND() {
3897
+ if (!defaultClient) {
3898
+ throw new Error("MAKEMIND SDK not initialized. Call initializeMAKEMIND() first.");
3899
+ }
3900
+ return defaultClient;
3901
+ }
3902
+
3903
+ // src/services/experts.ts
3904
+ var ExpertDoorError = class extends Error {
3905
+ constructor(code, message, status) {
3906
+ super(message);
3907
+ this.name = "ExpertDoorError";
3908
+ this.code = code;
3909
+ this.status = status;
3910
+ }
3911
+ };
3912
+ var MAKEMINDExperts = class {
3913
+ constructor(config) {
3914
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
3915
+ this.tenantId = config.tenantId;
3916
+ this.appCheck = config.appCheck;
3917
+ this.fetchImpl = config.fetchImpl ?? ((...a) => fetch(...a));
3918
+ }
3919
+ async post(path, body) {
3920
+ const headers = { "Content-Type": "application/json" };
3921
+ if (this.appCheck) {
3922
+ const token = await this.appCheck();
3923
+ if (token) headers["X-Firebase-AppCheck"] = token;
3924
+ }
3925
+ const res = await this.fetchImpl(`${this.baseUrl}/api/v1/public/experts/${path}`, {
3926
+ method: "POST",
3927
+ headers,
3928
+ body: JSON.stringify({ tenantId: this.tenantId, ...body })
3929
+ });
3930
+ const payload = await res.json().catch(() => ({}));
3931
+ if (!res.ok) {
3932
+ const err = payload.error;
3933
+ throw new ExpertDoorError(err?.code ?? "UNKNOWN", err?.message ?? `HTTP ${res.status}`, res.status);
3934
+ }
3935
+ return payload.data;
3936
+ }
3937
+ /** One question, one answer, and the capability to read it back. */
3938
+ async consult(options) {
3939
+ return this.post("consult", {
3940
+ expertId: options.expertId,
3941
+ question: options.question,
3942
+ ...options.facts !== void 0 ? { facts: options.facts } : {}
3943
+ });
3944
+ }
3945
+ /**
3946
+ * Open a conversation, or resume one whose capability the caller kept.
3947
+ * @param {Object} options The expert to open, or a thread to resume.
3948
+ * @return {ExpertChat} A thread that carries its own capability.
3949
+ */
3950
+ chat(options) {
3951
+ return new ExpertChat(this, options);
3952
+ }
3953
+ /** @internal */
3954
+ async converse(body) {
3955
+ return this.post("converse", body);
3956
+ }
3957
+ async getConsultation(options) {
3958
+ return this.post("consultation", options);
3959
+ }
3960
+ async getSession(options) {
3961
+ return this.post("session", options);
3962
+ }
3963
+ };
3964
+ var ExpertChat = class {
3965
+ constructor(api, options) {
3966
+ this.turns = [];
3967
+ this.api = api;
3968
+ this.expertId = options.expertId;
3969
+ this.session = options.sessionId;
3970
+ this.token = options.accessToken;
3971
+ }
3972
+ get sessionId() {
3973
+ return this.session;
3974
+ }
3975
+ get accessToken() {
3976
+ return this.token;
3977
+ }
3978
+ get history() {
3979
+ return [...this.turns];
3980
+ }
3981
+ /**
3982
+ * Ask, and keep whatever the thread needs to be continued.
3983
+ * @param {string} question What the visitor asked.
3984
+ * @param {Object} [facts] Structured facts the rules read.
3985
+ * @return {Promise<ExpertConsultation>} The turn's record. A refusal comes
3986
+ * back as an answer with `refusal` set; only a closed door throws.
3987
+ */
3988
+ async ask(question, facts) {
3989
+ const result = await this.api.converse({
3990
+ // A thread names its performer once. Sending the expert again on a later
3991
+ // turn is refused by the platform as renaming it.
3992
+ ...this.session ? { sessionId: this.session } : { expertId: this.expertId },
3993
+ ...this.token ? { accessToken: this.token } : {},
3994
+ question,
3995
+ ...facts !== void 0 ? { facts } : {}
3996
+ });
3997
+ this.session = result.sessionId;
3998
+ if (result.access?.token) this.token = result.access.token;
3999
+ this.turns.push({
4000
+ question,
4001
+ consultationId: result.consultation.id,
4002
+ ...result.consultation.synthesis?.answer !== void 0 ? { answer: result.consultation.synthesis.answer } : {}
4003
+ });
4004
+ return result.consultation;
4005
+ }
4006
+ };
4007
+
4008
+ // src/services/expert-chat-widget.ts
4009
+ var DEFAULT_NOTICE = "This conversation lives in this browser only. Lose the link and the thread is gone \u2014 there is no account to recover it into.";
4010
+ function defaultStorage() {
4011
+ try {
4012
+ return typeof localStorage === "undefined" ? null : localStorage;
4013
+ } catch {
4014
+ return null;
4015
+ }
4016
+ }
4017
+ function mountExpertChat(el, options) {
4018
+ const doc = el.ownerDocument;
4019
+ const api = new MAKEMINDExperts(options);
4020
+ const store = options.storage === void 0 ? defaultStorage() : options.storage;
4021
+ const key = `makemind.expert.${options.tenantId}.${options.expertId}`;
4022
+ let saved = {};
4023
+ try {
4024
+ const raw = store?.getItem(key);
4025
+ if (raw) saved = JSON.parse(raw);
4026
+ } catch {
4027
+ saved = {};
4028
+ }
4029
+ const chat = api.chat({ expertId: options.expertId, ...saved });
4030
+ const root = doc.createElement("div");
4031
+ root.className = "mm-expert-chat";
4032
+ const heading = doc.createElement("div");
4033
+ heading.className = "mm-expert-chat__title";
4034
+ heading.textContent = options.title ?? "Ask the expert";
4035
+ const thread = doc.createElement("div");
4036
+ thread.className = "mm-expert-chat__thread";
4037
+ const form = doc.createElement("form");
4038
+ form.className = "mm-expert-chat__form";
4039
+ const input = doc.createElement("input");
4040
+ input.type = "text";
4041
+ input.placeholder = options.placeholder ?? "Type your question";
4042
+ const button = doc.createElement("button");
4043
+ button.type = "submit";
4044
+ button.textContent = "Ask";
4045
+ const notice = doc.createElement("p");
4046
+ notice.className = "mm-expert-chat__notice";
4047
+ notice.textContent = options.bearerNotice ?? DEFAULT_NOTICE;
4048
+ form.append(input, button);
4049
+ root.append(heading, thread, form, notice);
4050
+ el.textContent = "";
4051
+ el.append(root);
4052
+ function line(role, text) {
4053
+ const p = doc.createElement("p");
4054
+ p.className = `mm-expert-chat__${role}`;
4055
+ p.textContent = text;
4056
+ thread.append(p);
4057
+ return p;
4058
+ }
4059
+ for (const turn of chat.history) {
4060
+ line("question", turn.question);
4061
+ if (turn.answer) line("answer", turn.answer);
4062
+ }
4063
+ function remember() {
4064
+ try {
4065
+ store?.setItem(key, JSON.stringify({ sessionId: chat.sessionId, accessToken: chat.accessToken }));
4066
+ } catch {
4067
+ }
4068
+ }
4069
+ function render(turn) {
4070
+ if (turn.refusal) line("refusal", turn.synthesis?.answer || turn.refusal);
4071
+ else line("answer", turn.synthesis?.answer ?? "");
4072
+ }
4073
+ let asking = false;
4074
+ async function ask(question) {
4075
+ const text = question.trim();
4076
+ if (!text || asking) return;
4077
+ asking = true;
4078
+ button.disabled = true;
4079
+ line("question", text);
4080
+ try {
4081
+ render(await chat.ask(text));
4082
+ remember();
4083
+ } catch (e) {
4084
+ line("error", e instanceof ExpertDoorError ? e.message : "The expert could not be reached.");
4085
+ } finally {
4086
+ asking = false;
4087
+ button.disabled = false;
4088
+ }
4089
+ }
4090
+ const onSubmit = (event) => {
4091
+ event.preventDefault();
4092
+ const question = input.value;
4093
+ input.value = "";
4094
+ void ask(question);
4095
+ };
4096
+ form.addEventListener("submit", onSubmit);
4097
+ return {
4098
+ ask,
4099
+ destroy() {
4100
+ form.removeEventListener("submit", onSubmit);
4101
+ root.remove();
4102
+ },
4103
+ get thread() {
4104
+ return { sessionId: chat.sessionId, accessToken: chat.accessToken };
4105
+ }
4106
+ };
4107
+ }
4108
+ // Annotate the CommonJS export names for ESM import in node:
4109
+ 0 && (module.exports = {
4110
+ ApiClient,
4111
+ ErrorCodes,
4112
+ ExpertChat,
4113
+ ExpertDoorError,
4114
+ MAKEMINDAI,
4115
+ MAKEMINDApps,
4116
+ MAKEMINDAuth,
4117
+ MAKEMINDClient,
4118
+ MAKEMINDData,
4119
+ MAKEMINDError,
4120
+ MAKEMINDExperts,
4121
+ MAKEMINDLogic,
4122
+ MAKEMINDMarketplace,
4123
+ MAKEMINDMessaging,
4124
+ MAKEMINDSearch,
4125
+ MAKEMINDSettlement,
4126
+ MAKEMINDStorage,
4127
+ MAKEMINDTenant,
4128
+ getMAKEMIND,
4129
+ initializeMAKEMIND,
4130
+ mountExpertChat
4131
+ });