@makemind/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,65 @@ All notable changes to `@makemind/sdk` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.2.0 — 2026-10-09
9
+
10
+ ### Changed — breaking
11
+
12
+ - Pages carry `dsl` (an MCP UI DSL page) in place of `components`, `layout`,
13
+ `variables`, `dataSources` and `events`; apps carry `definition` (theme,
14
+ navigation, languages) and `rollout`. `ComponentNode` is replaced by
15
+ `DslNode` / `DslPage`.
16
+ - `apps.generateUI` returns `{ content }` — a DSL widget subtree.
17
+ `apps.suggestActions(appId, widgetType?)` returns DSL actions.
18
+ - `apps.publish(appId, changelog?, { rolloutPercent })` publishes to a share of
19
+ visitors.
20
+
21
+ ### Added
22
+
23
+ - `recordSaleWithShares({ delivered })` — what went out with the sale (name,
24
+ quantity, unit, item id), recorded beside the money.
25
+ - `reverseSaleWithShares({ returnedQuantity })` — units that came back. Without
26
+ it a partial refund is a price adjustment and the refund that completes the
27
+ sale returns every unit still out.
28
+
29
+ ### Removed (breaking)
30
+
31
+ - `signUp`, `sendPasswordResetEmail`, `confirmPasswordReset` and `verifyEmail`.
32
+ The routes behind them were removed from the platform on 2026-09-17 and
33
+ answer `404`, so every call could only fail. A published client that offers
34
+ an operation the server does not have is a promise its caller discovers is
35
+ false at runtime.
36
+
37
+ What replaces them: a platform account exists once its person signs in at the
38
+ tenant portal; a tenant's own end users register in that tenant's pool
39
+ (`endusers-selfRegisterEndUser`). Sign-in (`signInWithEmail`,
40
+ `signInWithOAuthToken`) and `updatePassword` for a signed-in person are
41
+ unchanged.
42
+
43
+ ## 0.1.1 — 2026-09-12
44
+
45
+ ### Fixed
46
+
47
+ - The `data` service spanned two stores. `create`, `get`, `update` and `delete` went through the
48
+ REST data door — a separate tree that admits only a fixed set of collection names — while
49
+ `query`, the batch methods, `aggregate` and `search` address the document platform. A document
50
+ created with `create` was invisible to `query` and refused by `batchDelete`, and a collection
51
+ of your own naming was refused by `create` outright. All four now address the document
52
+ platform: ids are `DOC_…`, `get` answers `{ id, data }`, and a collection is taken as named.
53
+ - `refreshAuthToken()` signed the user out — and revoked their refresh tokens server-side — when
54
+ the gateway answered that no refresh token was sent. That is the answer every client without the
55
+ refresh cookie gets (a server-side process, for one), and its id token is still valid. It now
56
+ keeps the session and raises `MAKEMINDError('AUTH_REFRESH_UNAVAILABLE')`; a refresh token the
57
+ server refused still signs out. `createTenant` and `switchTenant` refresh internally, so a
58
+ server-side consumer signs in again after them to pick up the new claims.
59
+
60
+ - `batchCreate`, `batchUpdate` and `batchDelete` reported a batch the backend partly refused as
61
+ written. `dataBatchWrite` answers per item, and an item it will not perform comes back
62
+ `status: 'denied'` with a reason (`DATA_ALREADY_EXISTS`, `DATA_WRITE_ONCE_VIOLATION`,
63
+ `AUTH_ACCESS_DENIED`) while the rest commit. A denied item now raises
64
+ `MAKEMINDError('BATCH_PARTIALLY_DENIED')`, whose `details.results` lists every item and
65
+ `details.denied` the refused ones.
66
+
8
67
  ## 0.1.0
9
68
 
10
69
  First release. The public surface is not frozen: while this package is 0.x, a minor bump may change it.
package/README.md CHANGED
@@ -61,14 +61,11 @@ initializeMAKEMIND({
61
61
  ```typescript
62
62
  const makemind = getMAKEMIND();
63
63
 
64
- // Sign in with email + password
64
+ // Sign in with email + password. There is no sign-up here: a platform account
65
+ // exists once its person signs in at the tenant portal, and a tenant's own end
66
+ // users register in that tenant's pool (`endusers-selfRegisterEndUser`).
65
67
  const user = await makemind.auth.signInWithEmail(email, password);
66
68
 
67
- // Sign up a new user
68
- const newUser = await makemind.auth.signUp(email, password, {
69
- displayName: 'Jane Doe',
70
- });
71
-
72
69
  // Listen to auth state changes
73
70
  const unsubscribe = makemind.auth.onAuthStateChanged((user) => {
74
71
  if (user) {
@@ -88,6 +85,12 @@ const memberships = await makemind.auth.getUserMemberships();
88
85
  await makemind.auth.switchTenant('new-tenant-id');
89
86
  ```
90
87
 
88
+ The refresh token is an HttpOnly cookie, so only a browser can refresh a session. A
89
+ server-side process keeps its id token until it expires; `refreshAuthToken()` there raises
90
+ `AUTH_REFRESH_UNAVAILABLE` and leaves the session intact. `createTenant` and `switchTenant`
91
+ refresh internally, so on a server sign in again after them to pick up the new claims — or use
92
+ an API key, which is the credential made for machine callers.
93
+
91
94
  ## Data Operations
92
95
 
93
96
  ```typescript
@@ -110,7 +113,8 @@ const product = await makemind.data.get({
110
113
  documentId: 'product-id',
111
114
  });
112
115
 
113
- // Create document (tenant_id auto-added)
116
+ // Create document — every data method addresses the same document store,
117
+ // under whatever collection name you give it; ids are DOC_-prefixed
114
118
  const result = await makemind.data.create({
115
119
  collection: 'plm_products',
116
120
  data: {
package/dist/index.d.mts CHANGED
@@ -224,6 +224,19 @@ declare class MAKEMINDSettlement {
224
224
  description?: string;
225
225
  metadata?: Record<string, unknown>;
226
226
  }>;
227
+ /**
228
+ * What went out with the sale, recorded beside the money — at a price of
229
+ * nought too. One line per sale, at the gross.
230
+ */
231
+ delivered?: {
232
+ name: string;
233
+ /** Defaults to 1. */
234
+ quantity?: number;
235
+ unit?: string;
236
+ productId?: string;
237
+ itemRef?: string;
238
+ subscriptionId?: string;
239
+ };
227
240
  }): Promise<{
228
241
  transactionId: string;
229
242
  shareTransactionIds: (string | null)[];
@@ -243,6 +256,13 @@ declare class MAKEMINDSettlement {
243
256
  reverseSaleWithShares(options: {
244
257
  transactionId: string;
245
258
  amount?: number;
259
+ /**
260
+ * Units that came back, when you know. Without it the ledger never guesses
261
+ * units from money: a refund short of the whole is a price adjustment (the
262
+ * thing stays delivered), and the refund that completes it returns every
263
+ * unit still out.
264
+ */
265
+ returnedQuantity?: number;
246
266
  reason?: string;
247
267
  idempotencyKey: string;
248
268
  }): Promise<{
@@ -583,12 +603,7 @@ declare class MAKEMINDAuth {
583
603
  loadMemberships(): Promise<TenantMembership[]>;
584
604
  signInWithEmail(email: string, password: string): Promise<AuthUser>;
585
605
  signInWithEmailAndPassword(email: string, password: string): Promise<AuthUser>;
586
- signUp(email: string, password: string, options?: {
587
- displayName?: string;
588
- phoneNumber?: string;
589
- }): Promise<AuthUser>;
590
606
  signOut(): Promise<void>;
591
- sendPasswordResetEmail(email: string): Promise<void>;
592
607
  onAuthStateChanged(callback: AuthStateCallback): () => void;
593
608
  private notifyAuthStateCallbacks;
594
609
  refreshAuthToken(): Promise<void>;
@@ -617,14 +632,6 @@ declare class MAKEMINDAuth {
617
632
  * Backend dispatch 'resendVerificationEmail' — uid resolved from auth context, no payload needed.
618
633
  */
619
634
  sendEmailVerification(): Promise<void>;
620
- /**
621
- * Verify email with code
622
- */
623
- verifyEmail(code: string): Promise<void>;
624
- /**
625
- * Confirm password reset with code
626
- */
627
- confirmPasswordReset(code: string, newPassword: string): Promise<void>;
628
635
  /**
629
636
  * OAuth redirect-based sign-in is NOT supported by this backend.
630
637
  * The backend has no /oauth/initiate or /oauth/callback routes.
@@ -707,6 +714,17 @@ declare class MAKEMINDData {
707
714
  data: Record<string, unknown>;
708
715
  documentId?: string;
709
716
  }>): Promise<Array<DocumentResult<T>>>;
717
+ /**
718
+ * Send a batch and refuse to report a partial one as whole.
719
+ *
720
+ * The backend answers per item: an item it would not perform comes back
721
+ * `status: 'denied'` with the reason (`DATA_ALREADY_EXISTS` for a create over
722
+ * an id that exists, `DATA_WRITE_ONCE_VIOLATION`, `AUTH_ACCESS_DENIED`) while
723
+ * the others commit. Reading only the ids told the caller every item was
724
+ * written. A denied item now raises `BATCH_PARTIALLY_DENIED`, and the error
725
+ * carries every item's result so the caller can see which ones did commit.
726
+ */
727
+ private batchWrite;
710
728
  batchUpdate(collection: string, updates: Array<{
711
729
  documentId: string;
712
730
  data: Record<string, unknown>;
@@ -2099,7 +2117,6 @@ declare class MAKEMINDSearch {
2099
2117
  */
2100
2118
 
2101
2119
  type AppStatus = 'draft' | 'published' | 'archived';
2102
- type LayoutType = 'blank' | 'sidebar' | 'topnav' | 'dashboard' | 'centered';
2103
2120
  type SSLStatus = 'pending' | 'provisioning' | 'active' | 'expired' | 'failed';
2104
2121
  type CustomDomainStatus = 'pending' | 'verifying' | 'active' | 'failed';
2105
2122
  /** Where a hostname's traffic is routed once it reaches the domain-serving edge. */
@@ -2208,18 +2225,17 @@ interface App {
2208
2225
  publishedVersion: number | null;
2209
2226
  domain: DomainConfig;
2210
2227
  settings: AppSettings;
2228
+ definition?: AppDefinition;
2229
+ /** A version served to a share of visitors beside the published one. */
2230
+ rollout?: {
2231
+ version: number;
2232
+ percent: number;
2233
+ };
2211
2234
  createdAt: string;
2212
2235
  createdBy: string;
2213
2236
  updatedAt: string;
2214
2237
  updatedBy: string;
2215
2238
  }
2216
- interface LayoutConfig {
2217
- sidebarWidth?: number;
2218
- sidebarCollapsible?: boolean;
2219
- headerHeight?: number;
2220
- footerHeight?: number;
2221
- contentMaxWidth?: number;
2222
- }
2223
2239
  interface PageSEO {
2224
2240
  title: string;
2225
2241
  description: string;
@@ -2227,61 +2243,42 @@ interface PageSEO {
2227
2243
  noIndex?: boolean;
2228
2244
  }
2229
2245
  /**
2230
- * A node of a builder page, as stored and as the API returns it.
2231
- *
2232
- * This described nine of the tree's fields while the editor writes fifteen:
2233
- * `dataBinding`, `validation`, `a11y` and `conditions.className` were absent
2234
- * entirely. TypeScript does not delete what it cannot see, so a round trip
2235
- * through `getPage`/`updatePage` kept them — but a consumer BUILDING a node
2236
- * from this type had no way to express a binding or a validation rule, and
2237
- * one rebuilding a node field-by-field dropped them silently.
2246
+ * A widget of a page document — MCP UI DSL 1.4 (app builder DDD 08 §2).
2238
2247
  *
2239
- * `type` stays `string` rather than the editor's 108-member union: this
2240
- * describes a REST payload, and the palette must be able to grow without
2241
- * every SDK consumer needing a new release to accept the new type.
2242
- *
2243
- * Kept in step with `@makemind/app-builder-ui`'s `ComponentNode` by
2244
- * `src/__tests__/component-node-parity.test.ts`.
2248
+ * `type` is a widget type the spec defines; every node the builder holds
2249
+ * carries `x-id`. The other properties are the widget's own, as the spec
2250
+ * declares them, so they are not enumerated here.
2245
2251
  */
2246
- interface ComponentNode {
2247
- id: string;
2252
+ interface DslNode {
2248
2253
  type: string;
2249
- name: string;
2250
- props: Record<string, unknown>;
2251
- styles: Record<string, unknown>;
2252
- children?: ComponentNode[];
2253
- conditions?: {
2254
- visible?: string;
2255
- enabled?: string;
2256
- className?: string;
2254
+ 'x-id'?: string;
2255
+ [prop: string]: unknown;
2256
+ }
2257
+ /** A page document (page.schema). */
2258
+ interface DslPage {
2259
+ type: 'page';
2260
+ title?: string;
2261
+ content: DslNode;
2262
+ state?: {
2263
+ initial?: Record<string, unknown>;
2257
2264
  };
2258
- events?: Record<string, unknown[]>;
2259
- dataBinding?: {
2260
- source: string;
2261
- field: string;
2262
- transform?: string;
2265
+ lifecycle?: Record<string, unknown>;
2266
+ [prop: string]: unknown;
2267
+ }
2268
+ /** Theme, navigation, languages and app state — the DSL around the pages. */
2269
+ interface AppDefinition {
2270
+ title?: string;
2271
+ theme?: Record<string, unknown>;
2272
+ navigation?: {
2273
+ type: 'drawer' | 'bottomBar' | 'rail' | 'tabs';
2274
+ items?: unknown[];
2275
+ [key: string]: unknown;
2263
2276
  };
2264
- validation?: Array<{
2265
- type: string;
2266
- value?: unknown;
2267
- message: string;
2268
- }>;
2269
- a11y?: {
2270
- label?: string;
2271
- description?: string;
2272
- role?: string;
2277
+ i18n?: Record<string, unknown>;
2278
+ state?: {
2279
+ initial?: Record<string, unknown>;
2273
2280
  };
2274
- }
2275
- interface DataSource {
2276
- id: string;
2277
- name: string;
2278
- type: 'query' | 'document' | 'api' | 'flow' | 'variable' | 'realtime';
2279
- config: Record<string, unknown>;
2280
- }
2281
- interface PageVariable {
2282
- name: string;
2283
- type: 'string' | 'number' | 'boolean' | 'object' | 'array';
2284
- defaultValue: unknown;
2281
+ [key: string]: unknown;
2285
2282
  }
2286
2283
  interface Page {
2287
2284
  id: string;
@@ -2293,13 +2290,8 @@ interface Page {
2293
2290
  isHomePage: boolean;
2294
2291
  isPublic: boolean;
2295
2292
  accessRoles: string[];
2296
- layout: {
2297
- type: LayoutType;
2298
- config: LayoutConfig;
2299
- };
2300
- components: ComponentNode[];
2301
- dataSources: DataSource[];
2302
- variables: PageVariable[];
2293
+ /** The page document the runner draws. */
2294
+ dsl: DslPage;
2303
2295
  seo: PageSEO;
2304
2296
  order: number;
2305
2297
  createdAt: string;
@@ -2373,6 +2365,13 @@ interface UpdateAppParams {
2373
2365
  icon?: string;
2374
2366
  status?: AppStatus;
2375
2367
  settings?: AppSettings;
2368
+ /** Replaces the app definition as a whole. */
2369
+ definition?: AppDefinition;
2370
+ /** Adjust a rollout; `percent: 100` promotes it, `null` ends it. */
2371
+ rollout?: {
2372
+ version: number;
2373
+ percent: number;
2374
+ } | null;
2376
2375
  }
2377
2376
  interface ListAppsParams {
2378
2377
  status?: AppStatus;
@@ -2386,10 +2385,9 @@ interface CreatePageParams {
2386
2385
  description?: string;
2387
2386
  isHomePage?: boolean;
2388
2387
  isPublic?: boolean;
2389
- layout?: {
2390
- type: LayoutType;
2391
- config?: LayoutConfig;
2392
- };
2388
+ /** The page document; an empty column when absent. */
2389
+ dsl?: DslPage;
2390
+ seo?: Partial<PageSEO>;
2393
2391
  }
2394
2392
  interface UpdatePageParams {
2395
2393
  name?: string;
@@ -2399,13 +2397,8 @@ interface UpdatePageParams {
2399
2397
  isHomePage?: boolean;
2400
2398
  isPublic?: boolean;
2401
2399
  accessRoles?: string[];
2402
- layout?: {
2403
- type: LayoutType;
2404
- config?: LayoutConfig;
2405
- };
2406
- components?: ComponentNode[];
2407
- dataSources?: DataSource[];
2408
- variables?: PageVariable[];
2400
+ /** Replaces the page document as a whole. */
2401
+ dsl?: DslPage;
2409
2402
  seo?: PageSEO;
2410
2403
  }
2411
2404
  interface MAKEMINDAppsConfig extends MAKEMINDConfig {
@@ -2443,7 +2436,13 @@ declare class MAKEMINDApps {
2443
2436
  duplicatePage(appId: string, pageId: string, newName?: string, newPath?: string): Promise<{
2444
2437
  pageId: string;
2445
2438
  }>;
2446
- publish(appId: string, changelog?: string): Promise<{
2439
+ /**
2440
+ * Cut a version and publish it — to everyone, or with `rolloutPercent`
2441
+ * (1–99) to that share of visitors beside the published version.
2442
+ */
2443
+ publish(appId: string, changelog?: string, options?: {
2444
+ rolloutPercent?: number;
2445
+ }): Promise<{
2447
2446
  success: boolean;
2448
2447
  version: number;
2449
2448
  publishedAt: string;
@@ -2661,11 +2660,16 @@ declare class MAKEMINDApps {
2661
2660
  startDate?: string;
2662
2661
  endDate?: string;
2663
2662
  }): Promise<AppAnalytics>;
2663
+ /** A widget subtree in DSL from a description (app builder DDD 08 §9). */
2664
2664
  generateUI(prompt: string, context?: Record<string, unknown>, appId?: string): Promise<{
2665
- components: ComponentNode[];
2665
+ content: DslNode;
2666
2666
  }>;
2667
- suggestActions(appId: string, componentType?: string, context?: Record<string, unknown>): Promise<{
2668
- actions: unknown[];
2667
+ /** DSL actions a widget could run, from the tools the app's origin offers. */
2668
+ suggestActions(appId: string, widgetType?: string): Promise<{
2669
+ suggestions: Array<{
2670
+ label: string;
2671
+ action: Record<string, unknown>;
2672
+ }>;
2669
2673
  }>;
2670
2674
  startSession(appId: string, pageId: string): Promise<{
2671
2675
  sessionToken: string;
@@ -3067,6 +3071,8 @@ declare const ErrorCodes: {
3067
3071
  readonly NOT_FOUND: "NOT_FOUND";
3068
3072
  readonly ALREADY_EXISTS: "ALREADY_EXISTS";
3069
3073
  readonly VALIDATION_ERROR: "VALIDATION_ERROR";
3074
+ /** No refresh credential on this client; the session is intact but cannot be refreshed here. */
3075
+ readonly AUTH_REFRESH_UNAVAILABLE: "AUTH_REFRESH_UNAVAILABLE";
3070
3076
  readonly TENANT_NOT_FOUND: "TENANT_NOT_FOUND";
3071
3077
  readonly TENANT_SUSPENDED: "TENANT_SUSPENDED";
3072
3078
  readonly NOT_A_MEMBER: "NOT_A_MEMBER";
package/dist/index.d.ts CHANGED
@@ -224,6 +224,19 @@ declare class MAKEMINDSettlement {
224
224
  description?: string;
225
225
  metadata?: Record<string, unknown>;
226
226
  }>;
227
+ /**
228
+ * What went out with the sale, recorded beside the money — at a price of
229
+ * nought too. One line per sale, at the gross.
230
+ */
231
+ delivered?: {
232
+ name: string;
233
+ /** Defaults to 1. */
234
+ quantity?: number;
235
+ unit?: string;
236
+ productId?: string;
237
+ itemRef?: string;
238
+ subscriptionId?: string;
239
+ };
227
240
  }): Promise<{
228
241
  transactionId: string;
229
242
  shareTransactionIds: (string | null)[];
@@ -243,6 +256,13 @@ declare class MAKEMINDSettlement {
243
256
  reverseSaleWithShares(options: {
244
257
  transactionId: string;
245
258
  amount?: number;
259
+ /**
260
+ * Units that came back, when you know. Without it the ledger never guesses
261
+ * units from money: a refund short of the whole is a price adjustment (the
262
+ * thing stays delivered), and the refund that completes it returns every
263
+ * unit still out.
264
+ */
265
+ returnedQuantity?: number;
246
266
  reason?: string;
247
267
  idempotencyKey: string;
248
268
  }): Promise<{
@@ -583,12 +603,7 @@ declare class MAKEMINDAuth {
583
603
  loadMemberships(): Promise<TenantMembership[]>;
584
604
  signInWithEmail(email: string, password: string): Promise<AuthUser>;
585
605
  signInWithEmailAndPassword(email: string, password: string): Promise<AuthUser>;
586
- signUp(email: string, password: string, options?: {
587
- displayName?: string;
588
- phoneNumber?: string;
589
- }): Promise<AuthUser>;
590
606
  signOut(): Promise<void>;
591
- sendPasswordResetEmail(email: string): Promise<void>;
592
607
  onAuthStateChanged(callback: AuthStateCallback): () => void;
593
608
  private notifyAuthStateCallbacks;
594
609
  refreshAuthToken(): Promise<void>;
@@ -617,14 +632,6 @@ declare class MAKEMINDAuth {
617
632
  * Backend dispatch 'resendVerificationEmail' — uid resolved from auth context, no payload needed.
618
633
  */
619
634
  sendEmailVerification(): Promise<void>;
620
- /**
621
- * Verify email with code
622
- */
623
- verifyEmail(code: string): Promise<void>;
624
- /**
625
- * Confirm password reset with code
626
- */
627
- confirmPasswordReset(code: string, newPassword: string): Promise<void>;
628
635
  /**
629
636
  * OAuth redirect-based sign-in is NOT supported by this backend.
630
637
  * The backend has no /oauth/initiate or /oauth/callback routes.
@@ -707,6 +714,17 @@ declare class MAKEMINDData {
707
714
  data: Record<string, unknown>;
708
715
  documentId?: string;
709
716
  }>): Promise<Array<DocumentResult<T>>>;
717
+ /**
718
+ * Send a batch and refuse to report a partial one as whole.
719
+ *
720
+ * The backend answers per item: an item it would not perform comes back
721
+ * `status: 'denied'` with the reason (`DATA_ALREADY_EXISTS` for a create over
722
+ * an id that exists, `DATA_WRITE_ONCE_VIOLATION`, `AUTH_ACCESS_DENIED`) while
723
+ * the others commit. Reading only the ids told the caller every item was
724
+ * written. A denied item now raises `BATCH_PARTIALLY_DENIED`, and the error
725
+ * carries every item's result so the caller can see which ones did commit.
726
+ */
727
+ private batchWrite;
710
728
  batchUpdate(collection: string, updates: Array<{
711
729
  documentId: string;
712
730
  data: Record<string, unknown>;
@@ -2099,7 +2117,6 @@ declare class MAKEMINDSearch {
2099
2117
  */
2100
2118
 
2101
2119
  type AppStatus = 'draft' | 'published' | 'archived';
2102
- type LayoutType = 'blank' | 'sidebar' | 'topnav' | 'dashboard' | 'centered';
2103
2120
  type SSLStatus = 'pending' | 'provisioning' | 'active' | 'expired' | 'failed';
2104
2121
  type CustomDomainStatus = 'pending' | 'verifying' | 'active' | 'failed';
2105
2122
  /** Where a hostname's traffic is routed once it reaches the domain-serving edge. */
@@ -2208,18 +2225,17 @@ interface App {
2208
2225
  publishedVersion: number | null;
2209
2226
  domain: DomainConfig;
2210
2227
  settings: AppSettings;
2228
+ definition?: AppDefinition;
2229
+ /** A version served to a share of visitors beside the published one. */
2230
+ rollout?: {
2231
+ version: number;
2232
+ percent: number;
2233
+ };
2211
2234
  createdAt: string;
2212
2235
  createdBy: string;
2213
2236
  updatedAt: string;
2214
2237
  updatedBy: string;
2215
2238
  }
2216
- interface LayoutConfig {
2217
- sidebarWidth?: number;
2218
- sidebarCollapsible?: boolean;
2219
- headerHeight?: number;
2220
- footerHeight?: number;
2221
- contentMaxWidth?: number;
2222
- }
2223
2239
  interface PageSEO {
2224
2240
  title: string;
2225
2241
  description: string;
@@ -2227,61 +2243,42 @@ interface PageSEO {
2227
2243
  noIndex?: boolean;
2228
2244
  }
2229
2245
  /**
2230
- * A node of a builder page, as stored and as the API returns it.
2231
- *
2232
- * This described nine of the tree's fields while the editor writes fifteen:
2233
- * `dataBinding`, `validation`, `a11y` and `conditions.className` were absent
2234
- * entirely. TypeScript does not delete what it cannot see, so a round trip
2235
- * through `getPage`/`updatePage` kept them — but a consumer BUILDING a node
2236
- * from this type had no way to express a binding or a validation rule, and
2237
- * one rebuilding a node field-by-field dropped them silently.
2246
+ * A widget of a page document — MCP UI DSL 1.4 (app builder DDD 08 §2).
2238
2247
  *
2239
- * `type` stays `string` rather than the editor's 108-member union: this
2240
- * describes a REST payload, and the palette must be able to grow without
2241
- * every SDK consumer needing a new release to accept the new type.
2242
- *
2243
- * Kept in step with `@makemind/app-builder-ui`'s `ComponentNode` by
2244
- * `src/__tests__/component-node-parity.test.ts`.
2248
+ * `type` is a widget type the spec defines; every node the builder holds
2249
+ * carries `x-id`. The other properties are the widget's own, as the spec
2250
+ * declares them, so they are not enumerated here.
2245
2251
  */
2246
- interface ComponentNode {
2247
- id: string;
2252
+ interface DslNode {
2248
2253
  type: string;
2249
- name: string;
2250
- props: Record<string, unknown>;
2251
- styles: Record<string, unknown>;
2252
- children?: ComponentNode[];
2253
- conditions?: {
2254
- visible?: string;
2255
- enabled?: string;
2256
- className?: string;
2254
+ 'x-id'?: string;
2255
+ [prop: string]: unknown;
2256
+ }
2257
+ /** A page document (page.schema). */
2258
+ interface DslPage {
2259
+ type: 'page';
2260
+ title?: string;
2261
+ content: DslNode;
2262
+ state?: {
2263
+ initial?: Record<string, unknown>;
2257
2264
  };
2258
- events?: Record<string, unknown[]>;
2259
- dataBinding?: {
2260
- source: string;
2261
- field: string;
2262
- transform?: string;
2265
+ lifecycle?: Record<string, unknown>;
2266
+ [prop: string]: unknown;
2267
+ }
2268
+ /** Theme, navigation, languages and app state — the DSL around the pages. */
2269
+ interface AppDefinition {
2270
+ title?: string;
2271
+ theme?: Record<string, unknown>;
2272
+ navigation?: {
2273
+ type: 'drawer' | 'bottomBar' | 'rail' | 'tabs';
2274
+ items?: unknown[];
2275
+ [key: string]: unknown;
2263
2276
  };
2264
- validation?: Array<{
2265
- type: string;
2266
- value?: unknown;
2267
- message: string;
2268
- }>;
2269
- a11y?: {
2270
- label?: string;
2271
- description?: string;
2272
- role?: string;
2277
+ i18n?: Record<string, unknown>;
2278
+ state?: {
2279
+ initial?: Record<string, unknown>;
2273
2280
  };
2274
- }
2275
- interface DataSource {
2276
- id: string;
2277
- name: string;
2278
- type: 'query' | 'document' | 'api' | 'flow' | 'variable' | 'realtime';
2279
- config: Record<string, unknown>;
2280
- }
2281
- interface PageVariable {
2282
- name: string;
2283
- type: 'string' | 'number' | 'boolean' | 'object' | 'array';
2284
- defaultValue: unknown;
2281
+ [key: string]: unknown;
2285
2282
  }
2286
2283
  interface Page {
2287
2284
  id: string;
@@ -2293,13 +2290,8 @@ interface Page {
2293
2290
  isHomePage: boolean;
2294
2291
  isPublic: boolean;
2295
2292
  accessRoles: string[];
2296
- layout: {
2297
- type: LayoutType;
2298
- config: LayoutConfig;
2299
- };
2300
- components: ComponentNode[];
2301
- dataSources: DataSource[];
2302
- variables: PageVariable[];
2293
+ /** The page document the runner draws. */
2294
+ dsl: DslPage;
2303
2295
  seo: PageSEO;
2304
2296
  order: number;
2305
2297
  createdAt: string;
@@ -2373,6 +2365,13 @@ interface UpdateAppParams {
2373
2365
  icon?: string;
2374
2366
  status?: AppStatus;
2375
2367
  settings?: AppSettings;
2368
+ /** Replaces the app definition as a whole. */
2369
+ definition?: AppDefinition;
2370
+ /** Adjust a rollout; `percent: 100` promotes it, `null` ends it. */
2371
+ rollout?: {
2372
+ version: number;
2373
+ percent: number;
2374
+ } | null;
2376
2375
  }
2377
2376
  interface ListAppsParams {
2378
2377
  status?: AppStatus;
@@ -2386,10 +2385,9 @@ interface CreatePageParams {
2386
2385
  description?: string;
2387
2386
  isHomePage?: boolean;
2388
2387
  isPublic?: boolean;
2389
- layout?: {
2390
- type: LayoutType;
2391
- config?: LayoutConfig;
2392
- };
2388
+ /** The page document; an empty column when absent. */
2389
+ dsl?: DslPage;
2390
+ seo?: Partial<PageSEO>;
2393
2391
  }
2394
2392
  interface UpdatePageParams {
2395
2393
  name?: string;
@@ -2399,13 +2397,8 @@ interface UpdatePageParams {
2399
2397
  isHomePage?: boolean;
2400
2398
  isPublic?: boolean;
2401
2399
  accessRoles?: string[];
2402
- layout?: {
2403
- type: LayoutType;
2404
- config?: LayoutConfig;
2405
- };
2406
- components?: ComponentNode[];
2407
- dataSources?: DataSource[];
2408
- variables?: PageVariable[];
2400
+ /** Replaces the page document as a whole. */
2401
+ dsl?: DslPage;
2409
2402
  seo?: PageSEO;
2410
2403
  }
2411
2404
  interface MAKEMINDAppsConfig extends MAKEMINDConfig {
@@ -2443,7 +2436,13 @@ declare class MAKEMINDApps {
2443
2436
  duplicatePage(appId: string, pageId: string, newName?: string, newPath?: string): Promise<{
2444
2437
  pageId: string;
2445
2438
  }>;
2446
- publish(appId: string, changelog?: string): Promise<{
2439
+ /**
2440
+ * Cut a version and publish it — to everyone, or with `rolloutPercent`
2441
+ * (1–99) to that share of visitors beside the published version.
2442
+ */
2443
+ publish(appId: string, changelog?: string, options?: {
2444
+ rolloutPercent?: number;
2445
+ }): Promise<{
2447
2446
  success: boolean;
2448
2447
  version: number;
2449
2448
  publishedAt: string;
@@ -2661,11 +2660,16 @@ declare class MAKEMINDApps {
2661
2660
  startDate?: string;
2662
2661
  endDate?: string;
2663
2662
  }): Promise<AppAnalytics>;
2663
+ /** A widget subtree in DSL from a description (app builder DDD 08 §9). */
2664
2664
  generateUI(prompt: string, context?: Record<string, unknown>, appId?: string): Promise<{
2665
- components: ComponentNode[];
2665
+ content: DslNode;
2666
2666
  }>;
2667
- suggestActions(appId: string, componentType?: string, context?: Record<string, unknown>): Promise<{
2668
- actions: unknown[];
2667
+ /** DSL actions a widget could run, from the tools the app's origin offers. */
2668
+ suggestActions(appId: string, widgetType?: string): Promise<{
2669
+ suggestions: Array<{
2670
+ label: string;
2671
+ action: Record<string, unknown>;
2672
+ }>;
2669
2673
  }>;
2670
2674
  startSession(appId: string, pageId: string): Promise<{
2671
2675
  sessionToken: string;
@@ -3067,6 +3071,8 @@ declare const ErrorCodes: {
3067
3071
  readonly NOT_FOUND: "NOT_FOUND";
3068
3072
  readonly ALREADY_EXISTS: "ALREADY_EXISTS";
3069
3073
  readonly VALIDATION_ERROR: "VALIDATION_ERROR";
3074
+ /** No refresh credential on this client; the session is intact but cannot be refreshed here. */
3075
+ readonly AUTH_REFRESH_UNAVAILABLE: "AUTH_REFRESH_UNAVAILABLE";
3070
3076
  readonly TENANT_NOT_FOUND: "TENANT_NOT_FOUND";
3071
3077
  readonly TENANT_SUSPENDED: "TENANT_SUSPENDED";
3072
3078
  readonly NOT_A_MEMBER: "NOT_A_MEMBER";
package/dist/index.js CHANGED
@@ -101,6 +101,8 @@ var ErrorCodes = {
101
101
  NOT_FOUND: "NOT_FOUND",
102
102
  ALREADY_EXISTS: "ALREADY_EXISTS",
103
103
  VALIDATION_ERROR: "VALIDATION_ERROR",
104
+ /** No refresh credential on this client; the session is intact but cannot be refreshed here. */
105
+ AUTH_REFRESH_UNAVAILABLE: "AUTH_REFRESH_UNAVAILABLE",
104
106
  // Tenant errors
105
107
  TENANT_NOT_FOUND: "TENANT_NOT_FOUND",
106
108
  TENANT_SUSPENDED: "TENANT_SUSPENDED",
@@ -265,30 +267,17 @@ var MAKEMINDAuth = class {
265
267
  async signInWithEmailAndPassword(email, password) {
266
268
  return this.signInWithEmail(email, password);
267
269
  }
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
- }
270
+ /*
271
+ * `signUp` stood here. The platform registers nobody from this surface
272
+ * (2026-09-17): `/auth/signup` was removed and answers 404, so the method
273
+ * could only ever fail. A published client that offers an operation the
274
+ * server does not have is a lie the caller finds at runtime; it is removed
275
+ * rather than left to throw.
276
+ *
277
+ * Where accounts come from now: a platform account exists once its person
278
+ * signs in at the tenant portal; a tenant's own end users register in that
279
+ * tenant's pool (`endusers-selfRegisterEndUser`).
280
+ */
292
281
  async signOut() {
293
282
  try {
294
283
  await this.apiClient.post("/auth/signout", {});
@@ -303,12 +292,12 @@ var MAKEMINDAuth = class {
303
292
  this.notifyAuthStateCallbacks(null);
304
293
  }
305
294
  }
306
- async sendPasswordResetEmail(email) {
307
- await this.apiClient.post("/auth/password/reset", {
308
- email,
309
- applicationId: this.applicationId
310
- }, { skipAuth: true });
311
- }
295
+ /*
296
+ * `sendPasswordResetEmail` stood here, with `confirmPasswordReset` and
297
+ * `verifyEmail` below. All three routes were removed with the sign-up they
298
+ * belonged to: the reset ended at an email this platform could not send,
299
+ * while the API answered that a link had been sent.
300
+ */
312
301
  onAuthStateChanged(callback) {
313
302
  this.authStateCallbacks.add(callback);
314
303
  callback(this._currentUser);
@@ -344,7 +333,13 @@ var MAKEMINDAuth = class {
344
333
  } else if (this._currentUser) {
345
334
  this.persistSession(this._currentUser, response.token, response.refreshToken, response.memberships);
346
335
  }
347
- } catch {
336
+ } catch (error) {
337
+ if (error instanceof MAKEMINDError && error.code === ErrorCodes.VALIDATION_ERROR) {
338
+ throw new MAKEMINDError(
339
+ ErrorCodes.AUTH_REFRESH_UNAVAILABLE,
340
+ "This client holds no refresh credential; sign in again to pick up new claims"
341
+ );
342
+ }
348
343
  await this.signOut();
349
344
  throw new MAKEMINDError("UNAUTHENTICATED", "Session expired");
350
345
  }
@@ -426,25 +421,6 @@ var MAKEMINDAuth = class {
426
421
  }
427
422
  await this.apiClient.dispatch("resendVerificationEmail");
428
423
  }
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
424
  /**
449
425
  * OAuth redirect-based sign-in is NOT supported by this backend.
450
426
  * The backend has no /oauth/initiate or /oauth/callback routes.
@@ -601,31 +577,49 @@ var MAKEMINDData = class {
601
577
  metadata: { hasMore: res.hasMore ?? false, lastDocId: res.nextCursor }
602
578
  };
603
579
  }
580
+ /*
581
+ * Every method on this service addresses the document platform — the store
582
+ * `query`, the batch methods, `aggregate` and `search` already read. The
583
+ * single-document methods used to go through the REST data door, which
584
+ * keeps a separate tree with a fixed set of collection names: a document
585
+ * created there was invisible to `query` and refused by `batchDelete`, and
586
+ * a collection of the tenant's own naming was refused at the door. One
587
+ * object, one store.
588
+ */
604
589
  async get(options) {
605
590
  try {
606
- const response = await this.apiClient.get(
607
- `/data/${options.collection}/${options.documentId}`
608
- );
609
- return response;
591
+ const doc = await this.apiClient.dispatch("dataGetDocument", {
592
+ id: options.documentId
593
+ });
594
+ return { id: doc.id ?? options.documentId, data: doc.data };
610
595
  } catch (error) {
611
- if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
596
+ if (error instanceof MAKEMINDError && (error.code === "NOT_FOUND" || error.code === "DATA_NOT_FOUND")) {
612
597
  return null;
613
598
  }
614
599
  throw error;
615
600
  }
616
601
  }
617
602
  async create(options) {
618
- const response = await this.apiClient.post(`/data/${options.collection}`, {
619
- ...options.data,
620
- documentId: options.documentId
621
- });
622
- return response;
603
+ const created = await this.apiClient.dispatch(
604
+ "dataCreateDocument",
605
+ {
606
+ collection: options.collection,
607
+ data: options.data,
608
+ id: options.documentId
609
+ }
610
+ );
611
+ return { id: created.id, data: options.data };
623
612
  }
624
613
  async update(options) {
625
- await this.apiClient.patch(`/data/${options.collection}/${options.documentId}`, options.data);
614
+ await this.apiClient.dispatch("dataUpdateDocument", {
615
+ id: options.documentId,
616
+ data: options.data
617
+ });
626
618
  }
627
619
  async delete(options) {
628
- await this.apiClient.delete(`/data/${options.collection}/${options.documentId}`);
620
+ await this.apiClient.dispatch("dataDeleteDocument", {
621
+ id: options.documentId
622
+ });
629
623
  }
630
624
  onSnapshot(options, callback) {
631
625
  const subscriptionId = `doc:${options.collection}:${options.documentId}`;
@@ -743,11 +737,33 @@ var MAKEMINDData = class {
743
737
  ...documentId ? { id: documentId } : {},
744
738
  data
745
739
  }));
740
+ const results = await this.batchWrite(operations);
741
+ return results.map((r) => ({ id: r.id, data: {} }));
742
+ }
743
+ /**
744
+ * Send a batch and refuse to report a partial one as whole.
745
+ *
746
+ * The backend answers per item: an item it would not perform comes back
747
+ * `status: 'denied'` with the reason (`DATA_ALREADY_EXISTS` for a create over
748
+ * an id that exists, `DATA_WRITE_ONCE_VIOLATION`, `AUTH_ACCESS_DENIED`) while
749
+ * the others commit. Reading only the ids told the caller every item was
750
+ * written. A denied item now raises `BATCH_PARTIALLY_DENIED`, and the error
751
+ * carries every item's result so the caller can see which ones did commit.
752
+ */
753
+ async batchWrite(operations) {
746
754
  const response = await this.apiClient.dispatch(
747
755
  "dataBatchWrite",
748
756
  { operations }
749
757
  );
750
- return response.results.map((r) => ({ id: r.id, data: {} }));
758
+ const denied = response.results.filter((r) => r.status === "denied");
759
+ if (denied.length > 0) {
760
+ throw new MAKEMINDError(
761
+ "BATCH_PARTIALLY_DENIED",
762
+ `${denied.length} of ${response.results.length} batch operations were refused: ` + denied.map((r) => `${r.id} (${r.error ?? "denied"})`).join(", "),
763
+ { results: response.results, denied }
764
+ );
765
+ }
766
+ return response.results;
751
767
  }
752
768
  async batchUpdate(collection, updates) {
753
769
  const operations = updates.map(({ documentId, data }) => ({
@@ -755,14 +771,14 @@ var MAKEMINDData = class {
755
771
  id: documentId,
756
772
  data
757
773
  }));
758
- await this.apiClient.dispatch("dataBatchWrite", { operations });
774
+ await this.batchWrite(operations);
759
775
  }
760
776
  async batchDelete(collection, documentIds) {
761
777
  const operations = documentIds.map((id) => ({
762
778
  type: "delete",
763
779
  id
764
780
  }));
765
- await this.apiClient.dispatch("dataBatchWrite", { operations });
781
+ await this.batchWrite(operations);
766
782
  }
767
783
  // Export/Import operations
768
784
  //
@@ -3081,8 +3097,12 @@ var MAKEMINDApps = class {
3081
3097
  // ============================================
3082
3098
  // Publishing & Versioning
3083
3099
  // ============================================
3084
- async publish(appId, changelog) {
3085
- const response = await this.apiClient.post(`/apps/${appId}/publish`, { changelog });
3100
+ /**
3101
+ * Cut a version and publish it — to everyone, or with `rolloutPercent`
3102
+ * (1–99) to that share of visitors beside the published version.
3103
+ */
3104
+ async publish(appId, changelog, options = {}) {
3105
+ const response = await this.apiClient.post(`/apps/${appId}/publish`, { changelog, ...options });
3086
3106
  return response;
3087
3107
  }
3088
3108
  async unpublish(appId) {
@@ -3281,16 +3301,20 @@ var MAKEMINDApps = class {
3281
3301
  // ============================================
3282
3302
  // AI Features
3283
3303
  // ============================================
3304
+ /** A widget subtree in DSL from a description (app builder DDD 08 §9). */
3284
3305
  async generateUI(prompt, context, appId) {
3285
3306
  const response = await this.apiClient.post(
3286
3307
  "/apps/ai/generate-ui",
3287
3308
  { prompt, context, appId }
3288
3309
  );
3289
- return { components: response.components };
3310
+ return { content: response.content };
3290
3311
  }
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 };
3312
+ /** DSL actions a widget could run, from the tools the app's origin offers. */
3313
+ async suggestActions(appId, widgetType) {
3314
+ return this.apiClient.post(
3315
+ `/apps/${appId}/ai/suggest-actions`,
3316
+ { widgetType }
3317
+ );
3294
3318
  }
3295
3319
  // ============================================
3296
3320
  // Collaboration
package/dist/index.mjs CHANGED
@@ -45,6 +45,8 @@ var ErrorCodes = {
45
45
  NOT_FOUND: "NOT_FOUND",
46
46
  ALREADY_EXISTS: "ALREADY_EXISTS",
47
47
  VALIDATION_ERROR: "VALIDATION_ERROR",
48
+ /** No refresh credential on this client; the session is intact but cannot be refreshed here. */
49
+ AUTH_REFRESH_UNAVAILABLE: "AUTH_REFRESH_UNAVAILABLE",
48
50
  // Tenant errors
49
51
  TENANT_NOT_FOUND: "TENANT_NOT_FOUND",
50
52
  TENANT_SUSPENDED: "TENANT_SUSPENDED",
@@ -209,30 +211,17 @@ var MAKEMINDAuth = class {
209
211
  async signInWithEmailAndPassword(email, password) {
210
212
  return this.signInWithEmail(email, password);
211
213
  }
212
- async signUp(email, password, options) {
213
- try {
214
- const response = await this.apiClient.post("/auth/signup", {
215
- email,
216
- password,
217
- displayName: options?.displayName,
218
- phoneNumber: options?.phoneNumber,
219
- applicationId: this.applicationId
220
- }, { skipAuth: true });
221
- this._currentUser = response.user;
222
- this.refreshToken = response.refreshToken ?? null;
223
- this._memberships = response.memberships || [];
224
- this.setAuthToken(response.token);
225
- this.persistSession(response.user, response.token, response.refreshToken, response.memberships);
226
- this.onAuthStateChange(response.user);
227
- this.notifyAuthStateCallbacks(response.user);
228
- return response.user;
229
- } catch (error) {
230
- if (error instanceof MAKEMINDError) {
231
- throw error;
232
- }
233
- throw new MAKEMINDError("AUTH_ERROR", "Failed to sign up");
234
- }
235
- }
214
+ /*
215
+ * `signUp` stood here. The platform registers nobody from this surface
216
+ * (2026-09-17): `/auth/signup` was removed and answers 404, so the method
217
+ * could only ever fail. A published client that offers an operation the
218
+ * server does not have is a lie the caller finds at runtime; it is removed
219
+ * rather than left to throw.
220
+ *
221
+ * Where accounts come from now: a platform account exists once its person
222
+ * signs in at the tenant portal; a tenant's own end users register in that
223
+ * tenant's pool (`endusers-selfRegisterEndUser`).
224
+ */
236
225
  async signOut() {
237
226
  try {
238
227
  await this.apiClient.post("/auth/signout", {});
@@ -247,12 +236,12 @@ var MAKEMINDAuth = class {
247
236
  this.notifyAuthStateCallbacks(null);
248
237
  }
249
238
  }
250
- async sendPasswordResetEmail(email) {
251
- await this.apiClient.post("/auth/password/reset", {
252
- email,
253
- applicationId: this.applicationId
254
- }, { skipAuth: true });
255
- }
239
+ /*
240
+ * `sendPasswordResetEmail` stood here, with `confirmPasswordReset` and
241
+ * `verifyEmail` below. All three routes were removed with the sign-up they
242
+ * belonged to: the reset ended at an email this platform could not send,
243
+ * while the API answered that a link had been sent.
244
+ */
256
245
  onAuthStateChanged(callback) {
257
246
  this.authStateCallbacks.add(callback);
258
247
  callback(this._currentUser);
@@ -288,7 +277,13 @@ var MAKEMINDAuth = class {
288
277
  } else if (this._currentUser) {
289
278
  this.persistSession(this._currentUser, response.token, response.refreshToken, response.memberships);
290
279
  }
291
- } catch {
280
+ } catch (error) {
281
+ if (error instanceof MAKEMINDError && error.code === ErrorCodes.VALIDATION_ERROR) {
282
+ throw new MAKEMINDError(
283
+ ErrorCodes.AUTH_REFRESH_UNAVAILABLE,
284
+ "This client holds no refresh credential; sign in again to pick up new claims"
285
+ );
286
+ }
292
287
  await this.signOut();
293
288
  throw new MAKEMINDError("UNAUTHENTICATED", "Session expired");
294
289
  }
@@ -370,25 +365,6 @@ var MAKEMINDAuth = class {
370
365
  }
371
366
  await this.apiClient.dispatch("resendVerificationEmail");
372
367
  }
373
- /**
374
- * Verify email with code
375
- */
376
- async verifyEmail(code) {
377
- await this.apiClient.post("/auth/verify-email", {
378
- code,
379
- applicationId: this.applicationId
380
- }, { skipAuth: true });
381
- }
382
- /**
383
- * Confirm password reset with code
384
- */
385
- async confirmPasswordReset(code, newPassword) {
386
- await this.apiClient.post("/auth/confirm-reset", {
387
- code,
388
- newPassword,
389
- applicationId: this.applicationId
390
- }, { skipAuth: true });
391
- }
392
368
  /**
393
369
  * OAuth redirect-based sign-in is NOT supported by this backend.
394
370
  * The backend has no /oauth/initiate or /oauth/callback routes.
@@ -545,31 +521,49 @@ var MAKEMINDData = class {
545
521
  metadata: { hasMore: res.hasMore ?? false, lastDocId: res.nextCursor }
546
522
  };
547
523
  }
524
+ /*
525
+ * Every method on this service addresses the document platform — the store
526
+ * `query`, the batch methods, `aggregate` and `search` already read. The
527
+ * single-document methods used to go through the REST data door, which
528
+ * keeps a separate tree with a fixed set of collection names: a document
529
+ * created there was invisible to `query` and refused by `batchDelete`, and
530
+ * a collection of the tenant's own naming was refused at the door. One
531
+ * object, one store.
532
+ */
548
533
  async get(options) {
549
534
  try {
550
- const response = await this.apiClient.get(
551
- `/data/${options.collection}/${options.documentId}`
552
- );
553
- return response;
535
+ const doc = await this.apiClient.dispatch("dataGetDocument", {
536
+ id: options.documentId
537
+ });
538
+ return { id: doc.id ?? options.documentId, data: doc.data };
554
539
  } catch (error) {
555
- if (error instanceof MAKEMINDError && error.code === "NOT_FOUND") {
540
+ if (error instanceof MAKEMINDError && (error.code === "NOT_FOUND" || error.code === "DATA_NOT_FOUND")) {
556
541
  return null;
557
542
  }
558
543
  throw error;
559
544
  }
560
545
  }
561
546
  async create(options) {
562
- const response = await this.apiClient.post(`/data/${options.collection}`, {
563
- ...options.data,
564
- documentId: options.documentId
565
- });
566
- return response;
547
+ const created = await this.apiClient.dispatch(
548
+ "dataCreateDocument",
549
+ {
550
+ collection: options.collection,
551
+ data: options.data,
552
+ id: options.documentId
553
+ }
554
+ );
555
+ return { id: created.id, data: options.data };
567
556
  }
568
557
  async update(options) {
569
- await this.apiClient.patch(`/data/${options.collection}/${options.documentId}`, options.data);
558
+ await this.apiClient.dispatch("dataUpdateDocument", {
559
+ id: options.documentId,
560
+ data: options.data
561
+ });
570
562
  }
571
563
  async delete(options) {
572
- await this.apiClient.delete(`/data/${options.collection}/${options.documentId}`);
564
+ await this.apiClient.dispatch("dataDeleteDocument", {
565
+ id: options.documentId
566
+ });
573
567
  }
574
568
  onSnapshot(options, callback) {
575
569
  const subscriptionId = `doc:${options.collection}:${options.documentId}`;
@@ -687,11 +681,33 @@ var MAKEMINDData = class {
687
681
  ...documentId ? { id: documentId } : {},
688
682
  data
689
683
  }));
684
+ const results = await this.batchWrite(operations);
685
+ return results.map((r) => ({ id: r.id, data: {} }));
686
+ }
687
+ /**
688
+ * Send a batch and refuse to report a partial one as whole.
689
+ *
690
+ * The backend answers per item: an item it would not perform comes back
691
+ * `status: 'denied'` with the reason (`DATA_ALREADY_EXISTS` for a create over
692
+ * an id that exists, `DATA_WRITE_ONCE_VIOLATION`, `AUTH_ACCESS_DENIED`) while
693
+ * the others commit. Reading only the ids told the caller every item was
694
+ * written. A denied item now raises `BATCH_PARTIALLY_DENIED`, and the error
695
+ * carries every item's result so the caller can see which ones did commit.
696
+ */
697
+ async batchWrite(operations) {
690
698
  const response = await this.apiClient.dispatch(
691
699
  "dataBatchWrite",
692
700
  { operations }
693
701
  );
694
- return response.results.map((r) => ({ id: r.id, data: {} }));
702
+ const denied = response.results.filter((r) => r.status === "denied");
703
+ if (denied.length > 0) {
704
+ throw new MAKEMINDError(
705
+ "BATCH_PARTIALLY_DENIED",
706
+ `${denied.length} of ${response.results.length} batch operations were refused: ` + denied.map((r) => `${r.id} (${r.error ?? "denied"})`).join(", "),
707
+ { results: response.results, denied }
708
+ );
709
+ }
710
+ return response.results;
695
711
  }
696
712
  async batchUpdate(collection, updates) {
697
713
  const operations = updates.map(({ documentId, data }) => ({
@@ -699,14 +715,14 @@ var MAKEMINDData = class {
699
715
  id: documentId,
700
716
  data
701
717
  }));
702
- await this.apiClient.dispatch("dataBatchWrite", { operations });
718
+ await this.batchWrite(operations);
703
719
  }
704
720
  async batchDelete(collection, documentIds) {
705
721
  const operations = documentIds.map((id) => ({
706
722
  type: "delete",
707
723
  id
708
724
  }));
709
- await this.apiClient.dispatch("dataBatchWrite", { operations });
725
+ await this.batchWrite(operations);
710
726
  }
711
727
  // Export/Import operations
712
728
  //
@@ -3025,8 +3041,12 @@ var MAKEMINDApps = class {
3025
3041
  // ============================================
3026
3042
  // Publishing & Versioning
3027
3043
  // ============================================
3028
- async publish(appId, changelog) {
3029
- const response = await this.apiClient.post(`/apps/${appId}/publish`, { changelog });
3044
+ /**
3045
+ * Cut a version and publish it — to everyone, or with `rolloutPercent`
3046
+ * (1–99) to that share of visitors beside the published version.
3047
+ */
3048
+ async publish(appId, changelog, options = {}) {
3049
+ const response = await this.apiClient.post(`/apps/${appId}/publish`, { changelog, ...options });
3030
3050
  return response;
3031
3051
  }
3032
3052
  async unpublish(appId) {
@@ -3225,16 +3245,20 @@ var MAKEMINDApps = class {
3225
3245
  // ============================================
3226
3246
  // AI Features
3227
3247
  // ============================================
3248
+ /** A widget subtree in DSL from a description (app builder DDD 08 §9). */
3228
3249
  async generateUI(prompt, context, appId) {
3229
3250
  const response = await this.apiClient.post(
3230
3251
  "/apps/ai/generate-ui",
3231
3252
  { prompt, context, appId }
3232
3253
  );
3233
- return { components: response.components };
3254
+ return { content: response.content };
3234
3255
  }
3235
- async suggestActions(appId, componentType, context) {
3236
- const response = await this.apiClient.post(`/apps/${appId}/ai/suggest-actions`, { componentType, context });
3237
- return { actions: response.suggestions };
3256
+ /** DSL actions a widget could run, from the tools the app's origin offers. */
3257
+ async suggestActions(appId, widgetType) {
3258
+ return this.apiClient.post(
3259
+ `/apps/${appId}/ai/suggest-actions`,
3260
+ { widgetType }
3261
+ );
3238
3262
  }
3239
3263
  // ============================================
3240
3264
  // Collaboration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makemind/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MAKEMIND Platform SDK for Web Applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -34,15 +34,17 @@
34
34
  "firebase": "^10.14.1"
35
35
  },
36
36
  "devDependencies": {
37
+ "@makemind/app-builder-ui": "file:../app-builder-ui",
37
38
  "@types/jsdom": "^30.0.0",
38
39
  "@types/node": "^20.10.0",
40
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
41
+ "@typescript-eslint/parser": "^7.18.0",
39
42
  "eslint": "^8.55.0",
40
43
  "firebase": "^10.7.0",
41
44
  "jsdom": "^30.0.1",
42
45
  "tsup": "^8.0.1",
43
46
  "typescript": "^5.3.0",
44
- "vitest": "^1.0.0",
45
- "@makemind/app-builder-ui": "file:../app-builder-ui"
47
+ "vitest": "^1.0.0"
46
48
  },
47
49
  "keywords": [
48
50
  "makemind",