brainerce 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -30
- package/dist/index.d.mts +418 -83
- package/dist/index.d.ts +418 -83
- package/dist/index.js +386 -112
- package/dist/index.mjs +386 -112
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -115,7 +115,7 @@ function isDevGuardsEnabled() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// src/version.ts
|
|
118
|
-
var SDK_VERSION = "2.
|
|
118
|
+
var SDK_VERSION = "2.5.0";
|
|
119
119
|
|
|
120
120
|
// src/client.ts
|
|
121
121
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
@@ -359,10 +359,20 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
359
359
|
* Rich Text, and Page.
|
|
360
360
|
*
|
|
361
361
|
* Works in all three SDK modes (vibe-coded, storefront, admin):
|
|
362
|
-
* - **Public reads** (`get`, `list`, `getBySlug`):
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
362
|
+
* - **Public reads** (`get`, `list`, `getBySlug`): storefront and
|
|
363
|
+
* vibe-coded mode. There is no admin equivalent of a by-key/by-slug
|
|
364
|
+
* read — in admin mode they throw and point you at `listAdmin()` /
|
|
365
|
+
* `findById()`.
|
|
366
|
+
* - **Admin reads** (`listAdmin`, `findById`) and **writes** (`create`,
|
|
367
|
+
* `update`, `publish`, `unpublish`, `remove`): admin mode only — they
|
|
368
|
+
* call `/api/content/...` with the API key. Calling from storefront /
|
|
369
|
+
* vibe-coded mode throws.
|
|
370
|
+
*
|
|
371
|
+
* **⚠️ Every admin method takes an explicit `storeId`.** Admin mode has no
|
|
372
|
+
* ambient store (`storeId` is only set in storefront mode), and the routes
|
|
373
|
+
* are store-scoped: omitting it is rejected fail-closed by the store scope
|
|
374
|
+
* guard (`403 STORE_SCOPE_REQUIRED`). Pass the id of the store your API key
|
|
375
|
+
* is bound to — naming any other store is rejected as cross-tenant.
|
|
366
376
|
*
|
|
367
377
|
* **Default key:** every type has `'main'` as its universal default key.
|
|
368
378
|
* Pass no argument to fetch the main entry; pass a custom key (e.g.
|
|
@@ -387,12 +397,15 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
387
397
|
* });
|
|
388
398
|
* }
|
|
389
399
|
*
|
|
390
|
-
* // Admin — create a shipping FAQ in DRAFT
|
|
391
|
-
* await client.content.faq.create(
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* }
|
|
400
|
+
* // Admin — create a shipping FAQ in DRAFT (storeId is required)
|
|
401
|
+
* await client.content.faq.create(
|
|
402
|
+
* {
|
|
403
|
+
* key: 'shipping',
|
|
404
|
+
* name: 'Shipping FAQ',
|
|
405
|
+
* data: { items: [{ question: '…', answer: '…' }] },
|
|
406
|
+
* },
|
|
407
|
+
* 'store_123'
|
|
408
|
+
* );
|
|
396
409
|
* ```
|
|
397
410
|
*/
|
|
398
411
|
this.content = (() => {
|
|
@@ -413,7 +426,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
413
426
|
return this.storefrontRequest("GET", path, void 0, query).catch(onNotFound);
|
|
414
427
|
}
|
|
415
428
|
throw new BrainerceError(
|
|
416
|
-
"content.<type>.get(key) is a public-read API. In admin mode, call client.content.
|
|
429
|
+
"content.<type>.get(key) is a public-read API. In admin mode, call client.content.listAdmin({ storeId, type: '" + type + "' }) and filter by key, or client.content.findById(id, storeId) if you already hold the id.",
|
|
417
430
|
400
|
|
418
431
|
);
|
|
419
432
|
};
|
|
@@ -426,9 +439,9 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
426
439
|
if (this.storeId && !this.apiKey) {
|
|
427
440
|
return this.storefrontRequest("GET", "/content", void 0, query);
|
|
428
441
|
}
|
|
429
|
-
|
|
430
|
-
"
|
|
431
|
-
|
|
442
|
+
throw new BrainerceError(
|
|
443
|
+
"content.<type>.list() is a public-read API. In admin mode, call client.content.listAdmin({ storeId, type: '" + type + "' }).",
|
|
444
|
+
400
|
|
432
445
|
);
|
|
433
446
|
};
|
|
434
447
|
const requireAdmin = (action) => {
|
|
@@ -439,32 +452,59 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
439
452
|
);
|
|
440
453
|
}
|
|
441
454
|
};
|
|
442
|
-
const
|
|
455
|
+
const requireStoreId = (action, storeId) => {
|
|
456
|
+
if (!storeId) {
|
|
457
|
+
throw new BrainerceError(
|
|
458
|
+
`client.content.${action}() requires a storeId. Admin mode has no ambient store \u2014 pass the id of the store your API key is bound to.`,
|
|
459
|
+
400
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return { storeId };
|
|
463
|
+
};
|
|
464
|
+
const createByType = async (type, input, storeId) => {
|
|
443
465
|
requireAdmin("create");
|
|
444
|
-
return this.adminRequest(
|
|
466
|
+
return this.adminRequest(
|
|
467
|
+
"POST",
|
|
468
|
+
adminBase(),
|
|
469
|
+
{ ...input, type },
|
|
470
|
+
requireStoreId("create", storeId)
|
|
471
|
+
);
|
|
445
472
|
};
|
|
446
|
-
const updateById = async (id, input) => {
|
|
473
|
+
const updateById = async (id, input, storeId) => {
|
|
447
474
|
requireAdmin("update");
|
|
448
475
|
return this.adminRequest(
|
|
449
476
|
"PATCH",
|
|
450
477
|
`${adminBase()}/${encodeURIComponent(id)}`,
|
|
451
|
-
input
|
|
478
|
+
input,
|
|
479
|
+
requireStoreId("update", storeId)
|
|
452
480
|
);
|
|
453
481
|
};
|
|
454
|
-
const publishById = async (id) => {
|
|
482
|
+
const publishById = async (id, storeId) => {
|
|
455
483
|
requireAdmin("publish");
|
|
456
|
-
return this.adminRequest(
|
|
484
|
+
return this.adminRequest(
|
|
485
|
+
"POST",
|
|
486
|
+
`${adminBase()}/${encodeURIComponent(id)}/publish`,
|
|
487
|
+
void 0,
|
|
488
|
+
requireStoreId("publish", storeId)
|
|
489
|
+
);
|
|
457
490
|
};
|
|
458
|
-
const unpublishById = async (id) => {
|
|
491
|
+
const unpublishById = async (id, storeId) => {
|
|
459
492
|
requireAdmin("unpublish");
|
|
460
493
|
return this.adminRequest(
|
|
461
494
|
"POST",
|
|
462
|
-
`${adminBase()}/${encodeURIComponent(id)}/unpublish
|
|
495
|
+
`${adminBase()}/${encodeURIComponent(id)}/unpublish`,
|
|
496
|
+
void 0,
|
|
497
|
+
requireStoreId("unpublish", storeId)
|
|
463
498
|
);
|
|
464
499
|
};
|
|
465
|
-
const removeById = async (id) => {
|
|
500
|
+
const removeById = async (id, storeId) => {
|
|
466
501
|
requireAdmin("remove");
|
|
467
|
-
await this.adminRequest(
|
|
502
|
+
await this.adminRequest(
|
|
503
|
+
"DELETE",
|
|
504
|
+
`${adminBase()}/${encodeURIComponent(id)}`,
|
|
505
|
+
void 0,
|
|
506
|
+
requireStoreId("remove", storeId)
|
|
507
|
+
);
|
|
468
508
|
};
|
|
469
509
|
function makeNamespace(type) {
|
|
470
510
|
return {
|
|
@@ -474,10 +514,17 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
474
514
|
* hasn't seeded yet.
|
|
475
515
|
*/
|
|
476
516
|
get: (key = DEFAULT_KEY, locale) => publicGet(type, key, locale),
|
|
477
|
-
/**
|
|
517
|
+
/**
|
|
518
|
+
* List all PUBLISHED entries of this type (storefront / vibe-coded
|
|
519
|
+
* mode). In admin mode this throws — use
|
|
520
|
+
* `client.content.listAdmin({ storeId, type })`.
|
|
521
|
+
*/
|
|
478
522
|
list: (locale) => publicList(type, locale),
|
|
479
|
-
/**
|
|
480
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Create a new entry in DRAFT (admin mode).
|
|
525
|
+
* `storeId` is required — see the namespace docs above.
|
|
526
|
+
*/
|
|
527
|
+
create: (input, storeId) => createByType(type, input, storeId)
|
|
481
528
|
};
|
|
482
529
|
}
|
|
483
530
|
return {
|
|
@@ -517,30 +564,78 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
517
564
|
}
|
|
518
565
|
},
|
|
519
566
|
// ---------- Admin operations (cross-type) ----------
|
|
520
|
-
/**
|
|
521
|
-
|
|
567
|
+
/**
|
|
568
|
+
* Find a single row by its admin id (admin mode).
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```typescript
|
|
572
|
+
* const row = await client.content.findById('cnt_123', 'store_123');
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
findById: async (id, storeId) => {
|
|
522
576
|
requireAdmin("findById");
|
|
523
|
-
return this.adminRequest("GET", `${adminBase()}/${encodeURIComponent(id)}`);
|
|
524
|
-
},
|
|
525
|
-
/** List rows in admin mode with optional filters. */
|
|
526
|
-
listAdmin: async (filters) => {
|
|
527
|
-
requireAdmin("listAdmin");
|
|
528
|
-
const params = new URLSearchParams();
|
|
529
|
-
if (filters?.type) params.set("type", filters.type);
|
|
530
|
-
if (filters?.status) params.set("status", filters.status);
|
|
531
|
-
const qs = params.toString();
|
|
532
577
|
return this.adminRequest(
|
|
533
578
|
"GET",
|
|
534
|
-
|
|
579
|
+
`${adminBase()}/${encodeURIComponent(id)}`,
|
|
580
|
+
void 0,
|
|
581
|
+
requireStoreId("findById", storeId)
|
|
535
582
|
);
|
|
536
583
|
},
|
|
537
|
-
/**
|
|
584
|
+
/**
|
|
585
|
+
* List rows in admin mode. `storeId` is required; `type` and `status`
|
|
586
|
+
* are optional filters.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```typescript
|
|
590
|
+
* const faqs = await client.content.listAdmin({
|
|
591
|
+
* storeId: 'store_123',
|
|
592
|
+
* type: 'FAQ',
|
|
593
|
+
* status: 'DRAFT',
|
|
594
|
+
* });
|
|
595
|
+
* ```
|
|
596
|
+
*/
|
|
597
|
+
listAdmin: async (filters) => {
|
|
598
|
+
requireAdmin("listAdmin");
|
|
599
|
+
const query = requireStoreId("listAdmin", filters?.storeId);
|
|
600
|
+
if (filters.type) query.type = filters.type;
|
|
601
|
+
if (filters.status) query.status = filters.status;
|
|
602
|
+
return this.adminRequest("GET", adminBase(), void 0, query);
|
|
603
|
+
},
|
|
604
|
+
/**
|
|
605
|
+
* Replace `data` (and optional metadata) on an existing row.
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```typescript
|
|
609
|
+
* await client.content.update('cnt_123', { name: 'Shipping FAQ' }, 'store_123');
|
|
610
|
+
* ```
|
|
611
|
+
*/
|
|
538
612
|
update: updateById,
|
|
539
|
-
/**
|
|
613
|
+
/**
|
|
614
|
+
* Transition status DRAFT → PUBLISHED.
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```typescript
|
|
618
|
+
* await client.content.publish('cnt_123', 'store_123');
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
540
621
|
publish: publishById,
|
|
541
|
-
/**
|
|
622
|
+
/**
|
|
623
|
+
* Transition status PUBLISHED → DRAFT.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* await client.content.unpublish('cnt_123', 'store_123');
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
542
630
|
unpublish: unpublishById,
|
|
543
|
-
/**
|
|
631
|
+
/**
|
|
632
|
+
* Hard delete the row. Admin mode only.
|
|
633
|
+
*
|
|
634
|
+
* @example
|
|
635
|
+
* ```typescript
|
|
636
|
+
* await client.content.remove('cnt_123', 'store_123');
|
|
637
|
+
* ```
|
|
638
|
+
*/
|
|
544
639
|
remove: removeById
|
|
545
640
|
};
|
|
546
641
|
})();
|
|
@@ -548,15 +643,26 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
548
643
|
/**
|
|
549
644
|
* Read and manage blog posts.
|
|
550
645
|
*
|
|
646
|
+
* **⚠️ Every admin call takes an explicit `storeId`.** Admin mode has no
|
|
647
|
+
* ambient store (`storeId` is only set in storefront mode) and the admin
|
|
648
|
+
* routes are store-scoped: omitting it is rejected fail-closed by the store
|
|
649
|
+
* scope guard (`403 STORE_SCOPE_REQUIRED`). Pass the id of the store your
|
|
650
|
+
* API key is bound to — naming any other store is rejected as cross-tenant.
|
|
651
|
+
*
|
|
652
|
+
* Admin lookups are **by id**, not by slug (`getPost(slug)` is a public read
|
|
653
|
+
* and throws in admin mode — use `findById(id, storeId)`).
|
|
654
|
+
*
|
|
551
655
|
* ```typescript
|
|
552
656
|
* // Storefront / vibe-coded: list published posts
|
|
553
657
|
* const { data: posts } = await brainerce.blog.getPosts({ category: 'news' });
|
|
554
658
|
*
|
|
555
|
-
* // Fetch one by slug
|
|
659
|
+
* // Fetch one by slug (storefront / vibe-coded)
|
|
556
660
|
* const post = await brainerce.blog.getPost('my-first-post');
|
|
557
661
|
*
|
|
558
|
-
* // Admin: create a draft
|
|
559
|
-
* const
|
|
662
|
+
* // Admin: list, read one, and create a draft
|
|
663
|
+
* const all = await brainerce.blog.getPosts({}, 'store_123');
|
|
664
|
+
* const one = await brainerce.blog.findById('post_123', 'store_123');
|
|
665
|
+
* const draft = await brainerce.blog.create({ title: 'Hello World' }, 'store_123');
|
|
560
666
|
* ```
|
|
561
667
|
*/
|
|
562
668
|
this.blog = /* @__PURE__ */ (() => {
|
|
@@ -577,12 +683,30 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
577
683
|
const buildQuery = (params) => Object.fromEntries(
|
|
578
684
|
Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
579
685
|
);
|
|
686
|
+
const requireStoreId = (action, storeId) => {
|
|
687
|
+
if (!storeId) {
|
|
688
|
+
throw new BrainerceError(
|
|
689
|
+
`client.blog.${action}() requires a storeId in admin mode. Admin mode has no ambient store \u2014 pass the id of the store your API key is bound to.`,
|
|
690
|
+
400
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
return storeId;
|
|
694
|
+
};
|
|
580
695
|
return {
|
|
581
696
|
/**
|
|
582
|
-
* List
|
|
583
|
-
*
|
|
697
|
+
* List posts. Filters: `category`, `tag`, `page`, `limit`.
|
|
698
|
+
*
|
|
699
|
+
* Storefront / vibe-coded mode lists PUBLISHED posts and ignores
|
|
700
|
+
* `storeId` (the store is already in the base URL). Admin mode lists
|
|
701
|
+
* drafts too and REQUIRES `storeId`.
|
|
702
|
+
*
|
|
703
|
+
* @example
|
|
704
|
+
* ```typescript
|
|
705
|
+
* const { data } = await client.blog.getPosts({ category: 'news' }); // storefront
|
|
706
|
+
* const { data } = await client.blog.getPosts({}, 'store_123'); // admin
|
|
707
|
+
* ```
|
|
584
708
|
*/
|
|
585
|
-
getPosts: (params = {}) => {
|
|
709
|
+
getPosts: (params = {}, storeId) => {
|
|
586
710
|
const query = buildQuery(params);
|
|
587
711
|
if (this.isVibeCodedMode()) {
|
|
588
712
|
return this.vibeCodedRequest(
|
|
@@ -604,12 +728,23 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
604
728
|
"GET",
|
|
605
729
|
adminBase,
|
|
606
730
|
void 0,
|
|
607
|
-
|
|
731
|
+
{
|
|
732
|
+
...query,
|
|
733
|
+
storeId: requireStoreId("getPosts", storeId)
|
|
734
|
+
}
|
|
608
735
|
);
|
|
609
736
|
},
|
|
610
737
|
/**
|
|
611
|
-
* Fetch one
|
|
612
|
-
*
|
|
738
|
+
* Fetch one PUBLISHED post by its slug. Returns `null` on 404.
|
|
739
|
+
*
|
|
740
|
+
* Storefront / vibe-coded mode only — the admin API has no by-slug
|
|
741
|
+
* lookup (`GET /api/blog/posts/:id` is by id), so this throws in admin
|
|
742
|
+
* mode rather than issuing a request that can only 404.
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* ```typescript
|
|
746
|
+
* const post = await client.blog.getPost('my-first-post');
|
|
747
|
+
* ```
|
|
613
748
|
*/
|
|
614
749
|
getPost: (slug) => {
|
|
615
750
|
const path = `${publicBase}/${encodeURIComponent(slug)}`;
|
|
@@ -619,42 +754,111 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
619
754
|
if (this.storeId && !this.apiKey) {
|
|
620
755
|
return this.storefrontRequest("GET", path).catch(onNotFound);
|
|
621
756
|
}
|
|
622
|
-
|
|
757
|
+
throw new BrainerceError(
|
|
758
|
+
"client.blog.getPost(slug) is a public-read API. In admin mode, look a post up by id with client.blog.findById(id, storeId), or find it with client.blog.getPosts({}, storeId).",
|
|
759
|
+
400
|
|
760
|
+
);
|
|
623
761
|
},
|
|
624
|
-
/**
|
|
625
|
-
|
|
762
|
+
/**
|
|
763
|
+
* Fetch one post by its admin id — drafts included. Admin mode only.
|
|
764
|
+
* Returns `null` on 404.
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* ```typescript
|
|
768
|
+
* const post = await client.blog.findById('post_123', 'store_123');
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
findById: (id, storeId) => {
|
|
772
|
+
requireAdmin("findById");
|
|
773
|
+
return this.adminRequest(
|
|
774
|
+
"GET",
|
|
775
|
+
`${adminBase}/${encodeURIComponent(id)}`,
|
|
776
|
+
void 0,
|
|
777
|
+
{ storeId: requireStoreId("findById", storeId) }
|
|
778
|
+
).catch(onNotFound);
|
|
779
|
+
},
|
|
780
|
+
/**
|
|
781
|
+
* Create a blog post in DRAFT status. Admin mode only.
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* ```typescript
|
|
785
|
+
* const draft = await client.blog.create({ title: 'Hello World' }, 'store_123');
|
|
786
|
+
* ```
|
|
787
|
+
*/
|
|
788
|
+
create: (input, storeId) => {
|
|
626
789
|
requireAdmin("create");
|
|
627
|
-
return this.adminRequest("POST", adminBase, input
|
|
790
|
+
return this.adminRequest("POST", adminBase, input, {
|
|
791
|
+
storeId: requireStoreId("create", storeId)
|
|
792
|
+
});
|
|
628
793
|
},
|
|
629
|
-
/**
|
|
630
|
-
|
|
794
|
+
/**
|
|
795
|
+
* Update a blog post by ID. Admin mode only.
|
|
796
|
+
*
|
|
797
|
+
* @example
|
|
798
|
+
* ```typescript
|
|
799
|
+
* await client.blog.update('post_123', { title: 'Renamed' }, 'store_123');
|
|
800
|
+
* ```
|
|
801
|
+
*/
|
|
802
|
+
update: (id, input, storeId) => {
|
|
631
803
|
requireAdmin("update");
|
|
632
804
|
return this.adminRequest(
|
|
633
805
|
"PATCH",
|
|
634
806
|
`${adminBase}/${encodeURIComponent(id)}`,
|
|
635
|
-
input
|
|
807
|
+
input,
|
|
808
|
+
{ storeId: requireStoreId("update", storeId) }
|
|
636
809
|
);
|
|
637
810
|
},
|
|
638
|
-
/**
|
|
639
|
-
|
|
811
|
+
/**
|
|
812
|
+
* Transition status → PUBLISHED (sets publishedAt = now if unset).
|
|
813
|
+
* Admin mode only.
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```typescript
|
|
817
|
+
* await client.blog.publish('post_123', 'store_123');
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
publish: (id, storeId) => {
|
|
640
821
|
requireAdmin("publish");
|
|
641
822
|
return this.adminRequest(
|
|
642
823
|
"POST",
|
|
643
|
-
`${adminBase}/${encodeURIComponent(id)}/publish
|
|
824
|
+
`${adminBase}/${encodeURIComponent(id)}/publish`,
|
|
825
|
+
void 0,
|
|
826
|
+
{ storeId: requireStoreId("publish", storeId) }
|
|
644
827
|
);
|
|
645
828
|
},
|
|
646
|
-
/**
|
|
647
|
-
|
|
829
|
+
/**
|
|
830
|
+
* Transition status PUBLISHED → DRAFT. Admin mode only.
|
|
831
|
+
*
|
|
832
|
+
* @example
|
|
833
|
+
* ```typescript
|
|
834
|
+
* await client.blog.unpublish('post_123', 'store_123');
|
|
835
|
+
* ```
|
|
836
|
+
*/
|
|
837
|
+
unpublish: (id, storeId) => {
|
|
648
838
|
requireAdmin("unpublish");
|
|
649
839
|
return this.adminRequest(
|
|
650
840
|
"POST",
|
|
651
|
-
`${adminBase}/${encodeURIComponent(id)}/unpublish
|
|
841
|
+
`${adminBase}/${encodeURIComponent(id)}/unpublish`,
|
|
842
|
+
void 0,
|
|
843
|
+
{ storeId: requireStoreId("unpublish", storeId) }
|
|
652
844
|
);
|
|
653
845
|
},
|
|
654
|
-
/**
|
|
655
|
-
|
|
846
|
+
/**
|
|
847
|
+
* Hard-delete a blog post. Admin mode only.
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* ```typescript
|
|
851
|
+
* await client.blog.remove('post_123', 'store_123');
|
|
852
|
+
* ```
|
|
853
|
+
*/
|
|
854
|
+
remove: async (id, storeId) => {
|
|
656
855
|
requireAdmin("remove");
|
|
657
|
-
await this.adminRequest(
|
|
856
|
+
await this.adminRequest(
|
|
857
|
+
"DELETE",
|
|
858
|
+
`${adminBase}/${encodeURIComponent(id)}`,
|
|
859
|
+
void 0,
|
|
860
|
+
{ storeId: requireStoreId("remove", storeId) }
|
|
861
|
+
);
|
|
658
862
|
}
|
|
659
863
|
};
|
|
660
864
|
})();
|
|
@@ -2181,6 +2385,59 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
2181
2385
|
* console.log('Product type:', product.type); // 'VARIABLE'
|
|
2182
2386
|
* ```
|
|
2183
2387
|
*/
|
|
2388
|
+
/**
|
|
2389
|
+
* Read a kit's contents, with its resolved price and how many can be sold.
|
|
2390
|
+
*
|
|
2391
|
+
* A KIT is one purchasable product assembled from other catalog products. It
|
|
2392
|
+
* holds no inventory of its own: `available` is whichever component runs out
|
|
2393
|
+
* first. Outside `FIXED` pricing the product row's `basePrice` is a
|
|
2394
|
+
* placeholder, so use the `price` returned here.
|
|
2395
|
+
*
|
|
2396
|
+
* Safe to call for any product — a non-KIT returns an empty, unsellable
|
|
2397
|
+
* shape rather than throwing, so callers need not branch on product type.
|
|
2398
|
+
*
|
|
2399
|
+
* @example
|
|
2400
|
+
* ```typescript
|
|
2401
|
+
* const kit = await client.getKitComponents('prod_123');
|
|
2402
|
+
* console.log(kit.price, kit.available, kit.components.length);
|
|
2403
|
+
* ```
|
|
2404
|
+
*/
|
|
2405
|
+
async getKitComponents(productId) {
|
|
2406
|
+
return this.request(
|
|
2407
|
+
"GET",
|
|
2408
|
+
`/api/v1/products/${encodePathSegment(productId)}/kit-components`
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Replace a kit's contents, and optionally how it is priced.
|
|
2413
|
+
*
|
|
2414
|
+
* A full replace, not a patch: send the list you want the kit to end up with.
|
|
2415
|
+
* Omit `pricingMode` to leave the kit's current mode untouched.
|
|
2416
|
+
*
|
|
2417
|
+
* Rejected: a product that is not a KIT, a component from another store, a
|
|
2418
|
+
* component that is itself a KIT, a VARIABLE component with no variant
|
|
2419
|
+
* pinned, a variant that does not belong to its product, and the same slot
|
|
2420
|
+
* listed twice.
|
|
2421
|
+
*
|
|
2422
|
+
* @example
|
|
2423
|
+
* ```typescript
|
|
2424
|
+
* await client.setKitComponents('prod_123', {
|
|
2425
|
+
* components: [
|
|
2426
|
+
* { componentProductId: 'prod_bottle', quantity: 1 },
|
|
2427
|
+
* { componentProductId: 'prod_glass', quantity: 2 },
|
|
2428
|
+
* ],
|
|
2429
|
+
* pricingMode: 'SUM_MINUS_PERCENT',
|
|
2430
|
+
* discountValue: 10,
|
|
2431
|
+
* });
|
|
2432
|
+
* ```
|
|
2433
|
+
*/
|
|
2434
|
+
async setKitComponents(productId, body) {
|
|
2435
|
+
return this.request(
|
|
2436
|
+
"PUT",
|
|
2437
|
+
`/api/v1/products/${encodePathSegment(productId)}/kit-components`,
|
|
2438
|
+
body
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2184
2441
|
async convertToVariable(productId) {
|
|
2185
2442
|
return this.request(
|
|
2186
2443
|
"PATCH",
|
|
@@ -9956,71 +10213,88 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
9956
10213
|
}
|
|
9957
10214
|
throw new Error("uploadReviewPhoto requires storefront or vibe-coded mode");
|
|
9958
10215
|
}
|
|
9959
|
-
// -------------------- Team Management (Admin) -
|
|
9960
|
-
//
|
|
9961
|
-
// a `brainerce_*` API key.
|
|
9962
|
-
//
|
|
9963
|
-
//
|
|
9964
|
-
//
|
|
9965
|
-
//
|
|
10216
|
+
// -------------------- Team Management (Admin) - NOT REACHABLE BY API KEY --------------------
|
|
10217
|
+
// ⛔ These seven used to be documented as "the ONLY team endpoints reachable
|
|
10218
|
+
// with a `brainerce_*` API key". That was wrong, and every one of them 403s.
|
|
10219
|
+
//
|
|
10220
|
+
// The routes exist and the guards admit an api_key, but admission is not the
|
|
10221
|
+
// same as success. `external-api.controller.ts` hands `TeamService` a synthetic
|
|
10222
|
+
// principal — `SYSTEM_USER_ID = 'api-key-user'` (`:171`, used at `:4572`,
|
|
10223
|
+
// `:4586`, `:4607`, ...) — and every TeamService method opens with
|
|
10224
|
+
// `verifyAccountAccess` / `verifyAccountOwner`, which is an
|
|
10225
|
+
// `accountUser.findFirst({ where: { userId, ... } })`. No `AccountUser` row has
|
|
10226
|
+
// `userId = 'api-key-user'` anywhere; the sentinel is row-less by construction.
|
|
10227
|
+
// So the lookup misses and every call throws `Access denied to this account`.
|
|
10228
|
+
//
|
|
10229
|
+
// The store-level equivalents (`getStoreTeam`, `inviteStoreMember`, ...) sit
|
|
10230
|
+
// behind `DashboardOnlyGuard`, which rejects api_key principals by design. So
|
|
10231
|
+
// BOTH families are dashboard-only and there is no API-key path to team
|
|
10232
|
+
// management at all.
|
|
10233
|
+
//
|
|
10234
|
+
// ⛔ Do NOT "fix" this by letting api_key through. Team membership is
|
|
10235
|
+
// ACCOUNT-scoped and `inviteMember` grants `Role.ACCOUNT_OWNER`, i.e. ownership
|
|
10236
|
+
// of every store on the account. An API key is bound to exactly ONE store, so
|
|
10237
|
+
// admitting it here converts a single-store credential into account ownership.
|
|
10238
|
+
// That is a privilege-escalation primitive, not a missing feature.
|
|
10239
|
+
//
|
|
10240
|
+
// These throw rather than return a 403 from the wire, so the failure names its
|
|
10241
|
+
// own cause instead of arriving as a bare status code. Same treatment as
|
|
10242
|
+
// `createDonation` above. Agents: use the dashboard, or the `team:*` tools on
|
|
10243
|
+
// the admin MCP server, which are OAuth-authenticated and store-scoped.
|
|
10244
|
+
/** The message every team method throws. One string so they cannot drift. */
|
|
10245
|
+
teamIsDashboardOnly(method) {
|
|
10246
|
+
throw new Error(
|
|
10247
|
+
`${method} is not available with an API key. Team management is account-scoped and every route rejects the api_key principal, so this call can only fail. Manage team members in the dashboard, or use the store-scoped team tools on the admin MCP server (OAuth), which are bound to a single store.`
|
|
10248
|
+
);
|
|
10249
|
+
}
|
|
9966
10250
|
/**
|
|
9967
|
-
* @deprecated Retiring, but
|
|
9968
|
-
* is dashboard-only
|
|
10251
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10252
|
+
* api_key principal and `getStoreTeam` is dashboard-only. Use the dashboard.
|
|
9969
10253
|
*/
|
|
9970
10254
|
async getTeamMembers() {
|
|
9971
|
-
return this.
|
|
10255
|
+
return this.teamIsDashboardOnly("getTeamMembers");
|
|
9972
10256
|
}
|
|
9973
10257
|
/**
|
|
9974
|
-
* @deprecated Retiring, but
|
|
9975
|
-
* is dashboard-only
|
|
10258
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10259
|
+
* api_key principal and `getStoreTeam` is dashboard-only. Use the dashboard.
|
|
9976
10260
|
*/
|
|
9977
10261
|
async getTeamInvitations() {
|
|
9978
|
-
return this.
|
|
10262
|
+
return this.teamIsDashboardOnly("getTeamInvitations");
|
|
9979
10263
|
}
|
|
9980
10264
|
/**
|
|
9981
|
-
* @deprecated Retiring, but
|
|
9982
|
-
*
|
|
10265
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10266
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
9983
10267
|
*/
|
|
9984
|
-
async inviteTeamMember(
|
|
9985
|
-
return this.
|
|
10268
|
+
async inviteTeamMember(_data) {
|
|
10269
|
+
return this.teamIsDashboardOnly("inviteTeamMember");
|
|
9986
10270
|
}
|
|
9987
10271
|
/**
|
|
9988
|
-
* @deprecated Retiring, but
|
|
9989
|
-
*
|
|
10272
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10273
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
9990
10274
|
*/
|
|
9991
|
-
async resendTeamInvitation(
|
|
9992
|
-
return this.
|
|
9993
|
-
"POST",
|
|
9994
|
-
`/api/v1/team/invitations/${encodePathSegment(invitationId)}/resend`
|
|
9995
|
-
);
|
|
10275
|
+
async resendTeamInvitation(_invitationId) {
|
|
10276
|
+
return this.teamIsDashboardOnly("resendTeamInvitation");
|
|
9996
10277
|
}
|
|
9997
10278
|
/**
|
|
9998
|
-
* @deprecated Retiring, but
|
|
9999
|
-
*
|
|
10279
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10280
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10000
10281
|
*/
|
|
10001
|
-
async revokeTeamInvitation(
|
|
10002
|
-
|
|
10003
|
-
"DELETE",
|
|
10004
|
-
`/api/v1/team/invitations/${encodePathSegment(invitationId)}`
|
|
10005
|
-
);
|
|
10282
|
+
async revokeTeamInvitation(_invitationId) {
|
|
10283
|
+
return this.teamIsDashboardOnly("revokeTeamInvitation");
|
|
10006
10284
|
}
|
|
10007
10285
|
/**
|
|
10008
|
-
* @deprecated Retiring, but
|
|
10009
|
-
*
|
|
10286
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10287
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10010
10288
|
*/
|
|
10011
|
-
async updateTeamMemberRole(
|
|
10012
|
-
return this.
|
|
10013
|
-
"PATCH",
|
|
10014
|
-
`/api/v1/team/members/${encodePathSegment(memberId)}/role`,
|
|
10015
|
-
data
|
|
10016
|
-
);
|
|
10289
|
+
async updateTeamMemberRole(_memberId, _data) {
|
|
10290
|
+
return this.teamIsDashboardOnly("updateTeamMemberRole");
|
|
10017
10291
|
}
|
|
10018
10292
|
/**
|
|
10019
|
-
* @deprecated Retiring, but
|
|
10020
|
-
*
|
|
10293
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10294
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10021
10295
|
*/
|
|
10022
|
-
async removeTeamMember(
|
|
10023
|
-
|
|
10296
|
+
async removeTeamMember(_memberId) {
|
|
10297
|
+
return this.teamIsDashboardOnly("removeTeamMember");
|
|
10024
10298
|
}
|
|
10025
10299
|
// -------------------- Store Team Management (Admin) --------------------
|
|
10026
10300
|
// Store-level team management. Each store has its own team with roles and permissions.
|