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.js
CHANGED
|
@@ -204,7 +204,7 @@ function isDevGuardsEnabled() {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// src/version.ts
|
|
207
|
-
var SDK_VERSION = "2.
|
|
207
|
+
var SDK_VERSION = "2.5.0";
|
|
208
208
|
|
|
209
209
|
// src/client.ts
|
|
210
210
|
var DEFAULT_BASE_URL = "https://api.brainerce.com";
|
|
@@ -448,10 +448,20 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
448
448
|
* Rich Text, and Page.
|
|
449
449
|
*
|
|
450
450
|
* Works in all three SDK modes (vibe-coded, storefront, admin):
|
|
451
|
-
* - **Public reads** (`get`, `list`, `getBySlug`):
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
451
|
+
* - **Public reads** (`get`, `list`, `getBySlug`): storefront and
|
|
452
|
+
* vibe-coded mode. There is no admin equivalent of a by-key/by-slug
|
|
453
|
+
* read — in admin mode they throw and point you at `listAdmin()` /
|
|
454
|
+
* `findById()`.
|
|
455
|
+
* - **Admin reads** (`listAdmin`, `findById`) and **writes** (`create`,
|
|
456
|
+
* `update`, `publish`, `unpublish`, `remove`): admin mode only — they
|
|
457
|
+
* call `/api/content/...` with the API key. Calling from storefront /
|
|
458
|
+
* vibe-coded mode throws.
|
|
459
|
+
*
|
|
460
|
+
* **⚠️ Every admin method takes an explicit `storeId`.** Admin mode has no
|
|
461
|
+
* ambient store (`storeId` is only set in storefront mode), and the routes
|
|
462
|
+
* are store-scoped: omitting it is rejected fail-closed by the store scope
|
|
463
|
+
* guard (`403 STORE_SCOPE_REQUIRED`). Pass the id of the store your API key
|
|
464
|
+
* is bound to — naming any other store is rejected as cross-tenant.
|
|
455
465
|
*
|
|
456
466
|
* **Default key:** every type has `'main'` as its universal default key.
|
|
457
467
|
* Pass no argument to fetch the main entry; pass a custom key (e.g.
|
|
@@ -476,12 +486,15 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
476
486
|
* });
|
|
477
487
|
* }
|
|
478
488
|
*
|
|
479
|
-
* // Admin — create a shipping FAQ in DRAFT
|
|
480
|
-
* await client.content.faq.create(
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
* }
|
|
489
|
+
* // Admin — create a shipping FAQ in DRAFT (storeId is required)
|
|
490
|
+
* await client.content.faq.create(
|
|
491
|
+
* {
|
|
492
|
+
* key: 'shipping',
|
|
493
|
+
* name: 'Shipping FAQ',
|
|
494
|
+
* data: { items: [{ question: '…', answer: '…' }] },
|
|
495
|
+
* },
|
|
496
|
+
* 'store_123'
|
|
497
|
+
* );
|
|
485
498
|
* ```
|
|
486
499
|
*/
|
|
487
500
|
this.content = (() => {
|
|
@@ -502,7 +515,7 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
502
515
|
return this.storefrontRequest("GET", path, void 0, query).catch(onNotFound);
|
|
503
516
|
}
|
|
504
517
|
throw new BrainerceError(
|
|
505
|
-
"content.<type>.get(key) is a public-read API. In admin mode, call client.content.
|
|
518
|
+
"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.",
|
|
506
519
|
400
|
|
507
520
|
);
|
|
508
521
|
};
|
|
@@ -515,9 +528,9 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
515
528
|
if (this.storeId && !this.apiKey) {
|
|
516
529
|
return this.storefrontRequest("GET", "/content", void 0, query);
|
|
517
530
|
}
|
|
518
|
-
|
|
519
|
-
"
|
|
520
|
-
|
|
531
|
+
throw new BrainerceError(
|
|
532
|
+
"content.<type>.list() is a public-read API. In admin mode, call client.content.listAdmin({ storeId, type: '" + type + "' }).",
|
|
533
|
+
400
|
|
521
534
|
);
|
|
522
535
|
};
|
|
523
536
|
const requireAdmin = (action) => {
|
|
@@ -528,32 +541,59 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
528
541
|
);
|
|
529
542
|
}
|
|
530
543
|
};
|
|
531
|
-
const
|
|
544
|
+
const requireStoreId = (action, storeId) => {
|
|
545
|
+
if (!storeId) {
|
|
546
|
+
throw new BrainerceError(
|
|
547
|
+
`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.`,
|
|
548
|
+
400
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
return { storeId };
|
|
552
|
+
};
|
|
553
|
+
const createByType = async (type, input, storeId) => {
|
|
532
554
|
requireAdmin("create");
|
|
533
|
-
return this.adminRequest(
|
|
555
|
+
return this.adminRequest(
|
|
556
|
+
"POST",
|
|
557
|
+
adminBase(),
|
|
558
|
+
{ ...input, type },
|
|
559
|
+
requireStoreId("create", storeId)
|
|
560
|
+
);
|
|
534
561
|
};
|
|
535
|
-
const updateById = async (id, input) => {
|
|
562
|
+
const updateById = async (id, input, storeId) => {
|
|
536
563
|
requireAdmin("update");
|
|
537
564
|
return this.adminRequest(
|
|
538
565
|
"PATCH",
|
|
539
566
|
`${adminBase()}/${encodeURIComponent(id)}`,
|
|
540
|
-
input
|
|
567
|
+
input,
|
|
568
|
+
requireStoreId("update", storeId)
|
|
541
569
|
);
|
|
542
570
|
};
|
|
543
|
-
const publishById = async (id) => {
|
|
571
|
+
const publishById = async (id, storeId) => {
|
|
544
572
|
requireAdmin("publish");
|
|
545
|
-
return this.adminRequest(
|
|
573
|
+
return this.adminRequest(
|
|
574
|
+
"POST",
|
|
575
|
+
`${adminBase()}/${encodeURIComponent(id)}/publish`,
|
|
576
|
+
void 0,
|
|
577
|
+
requireStoreId("publish", storeId)
|
|
578
|
+
);
|
|
546
579
|
};
|
|
547
|
-
const unpublishById = async (id) => {
|
|
580
|
+
const unpublishById = async (id, storeId) => {
|
|
548
581
|
requireAdmin("unpublish");
|
|
549
582
|
return this.adminRequest(
|
|
550
583
|
"POST",
|
|
551
|
-
`${adminBase()}/${encodeURIComponent(id)}/unpublish
|
|
584
|
+
`${adminBase()}/${encodeURIComponent(id)}/unpublish`,
|
|
585
|
+
void 0,
|
|
586
|
+
requireStoreId("unpublish", storeId)
|
|
552
587
|
);
|
|
553
588
|
};
|
|
554
|
-
const removeById = async (id) => {
|
|
589
|
+
const removeById = async (id, storeId) => {
|
|
555
590
|
requireAdmin("remove");
|
|
556
|
-
await this.adminRequest(
|
|
591
|
+
await this.adminRequest(
|
|
592
|
+
"DELETE",
|
|
593
|
+
`${adminBase()}/${encodeURIComponent(id)}`,
|
|
594
|
+
void 0,
|
|
595
|
+
requireStoreId("remove", storeId)
|
|
596
|
+
);
|
|
557
597
|
};
|
|
558
598
|
function makeNamespace(type) {
|
|
559
599
|
return {
|
|
@@ -563,10 +603,17 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
563
603
|
* hasn't seeded yet.
|
|
564
604
|
*/
|
|
565
605
|
get: (key = DEFAULT_KEY, locale) => publicGet(type, key, locale),
|
|
566
|
-
/**
|
|
606
|
+
/**
|
|
607
|
+
* List all PUBLISHED entries of this type (storefront / vibe-coded
|
|
608
|
+
* mode). In admin mode this throws — use
|
|
609
|
+
* `client.content.listAdmin({ storeId, type })`.
|
|
610
|
+
*/
|
|
567
611
|
list: (locale) => publicList(type, locale),
|
|
568
|
-
/**
|
|
569
|
-
|
|
612
|
+
/**
|
|
613
|
+
* Create a new entry in DRAFT (admin mode).
|
|
614
|
+
* `storeId` is required — see the namespace docs above.
|
|
615
|
+
*/
|
|
616
|
+
create: (input, storeId) => createByType(type, input, storeId)
|
|
570
617
|
};
|
|
571
618
|
}
|
|
572
619
|
return {
|
|
@@ -606,30 +653,78 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
606
653
|
}
|
|
607
654
|
},
|
|
608
655
|
// ---------- Admin operations (cross-type) ----------
|
|
609
|
-
/**
|
|
610
|
-
|
|
656
|
+
/**
|
|
657
|
+
* Find a single row by its admin id (admin mode).
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```typescript
|
|
661
|
+
* const row = await client.content.findById('cnt_123', 'store_123');
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
findById: async (id, storeId) => {
|
|
611
665
|
requireAdmin("findById");
|
|
612
|
-
return this.adminRequest("GET", `${adminBase()}/${encodeURIComponent(id)}`);
|
|
613
|
-
},
|
|
614
|
-
/** List rows in admin mode with optional filters. */
|
|
615
|
-
listAdmin: async (filters) => {
|
|
616
|
-
requireAdmin("listAdmin");
|
|
617
|
-
const params = new URLSearchParams();
|
|
618
|
-
if (filters?.type) params.set("type", filters.type);
|
|
619
|
-
if (filters?.status) params.set("status", filters.status);
|
|
620
|
-
const qs = params.toString();
|
|
621
666
|
return this.adminRequest(
|
|
622
667
|
"GET",
|
|
623
|
-
|
|
668
|
+
`${adminBase()}/${encodeURIComponent(id)}`,
|
|
669
|
+
void 0,
|
|
670
|
+
requireStoreId("findById", storeId)
|
|
624
671
|
);
|
|
625
672
|
},
|
|
626
|
-
/**
|
|
673
|
+
/**
|
|
674
|
+
* List rows in admin mode. `storeId` is required; `type` and `status`
|
|
675
|
+
* are optional filters.
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```typescript
|
|
679
|
+
* const faqs = await client.content.listAdmin({
|
|
680
|
+
* storeId: 'store_123',
|
|
681
|
+
* type: 'FAQ',
|
|
682
|
+
* status: 'DRAFT',
|
|
683
|
+
* });
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
listAdmin: async (filters) => {
|
|
687
|
+
requireAdmin("listAdmin");
|
|
688
|
+
const query = requireStoreId("listAdmin", filters?.storeId);
|
|
689
|
+
if (filters.type) query.type = filters.type;
|
|
690
|
+
if (filters.status) query.status = filters.status;
|
|
691
|
+
return this.adminRequest("GET", adminBase(), void 0, query);
|
|
692
|
+
},
|
|
693
|
+
/**
|
|
694
|
+
* Replace `data` (and optional metadata) on an existing row.
|
|
695
|
+
*
|
|
696
|
+
* @example
|
|
697
|
+
* ```typescript
|
|
698
|
+
* await client.content.update('cnt_123', { name: 'Shipping FAQ' }, 'store_123');
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
627
701
|
update: updateById,
|
|
628
|
-
/**
|
|
702
|
+
/**
|
|
703
|
+
* Transition status DRAFT → PUBLISHED.
|
|
704
|
+
*
|
|
705
|
+
* @example
|
|
706
|
+
* ```typescript
|
|
707
|
+
* await client.content.publish('cnt_123', 'store_123');
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
629
710
|
publish: publishById,
|
|
630
|
-
/**
|
|
711
|
+
/**
|
|
712
|
+
* Transition status PUBLISHED → DRAFT.
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* ```typescript
|
|
716
|
+
* await client.content.unpublish('cnt_123', 'store_123');
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
631
719
|
unpublish: unpublishById,
|
|
632
|
-
/**
|
|
720
|
+
/**
|
|
721
|
+
* Hard delete the row. Admin mode only.
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```typescript
|
|
725
|
+
* await client.content.remove('cnt_123', 'store_123');
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
633
728
|
remove: removeById
|
|
634
729
|
};
|
|
635
730
|
})();
|
|
@@ -637,15 +732,26 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
637
732
|
/**
|
|
638
733
|
* Read and manage blog posts.
|
|
639
734
|
*
|
|
735
|
+
* **⚠️ Every admin call takes an explicit `storeId`.** Admin mode has no
|
|
736
|
+
* ambient store (`storeId` is only set in storefront mode) and the admin
|
|
737
|
+
* routes are store-scoped: omitting it is rejected fail-closed by the store
|
|
738
|
+
* scope guard (`403 STORE_SCOPE_REQUIRED`). Pass the id of the store your
|
|
739
|
+
* API key is bound to — naming any other store is rejected as cross-tenant.
|
|
740
|
+
*
|
|
741
|
+
* Admin lookups are **by id**, not by slug (`getPost(slug)` is a public read
|
|
742
|
+
* and throws in admin mode — use `findById(id, storeId)`).
|
|
743
|
+
*
|
|
640
744
|
* ```typescript
|
|
641
745
|
* // Storefront / vibe-coded: list published posts
|
|
642
746
|
* const { data: posts } = await brainerce.blog.getPosts({ category: 'news' });
|
|
643
747
|
*
|
|
644
|
-
* // Fetch one by slug
|
|
748
|
+
* // Fetch one by slug (storefront / vibe-coded)
|
|
645
749
|
* const post = await brainerce.blog.getPost('my-first-post');
|
|
646
750
|
*
|
|
647
|
-
* // Admin: create a draft
|
|
648
|
-
* const
|
|
751
|
+
* // Admin: list, read one, and create a draft
|
|
752
|
+
* const all = await brainerce.blog.getPosts({}, 'store_123');
|
|
753
|
+
* const one = await brainerce.blog.findById('post_123', 'store_123');
|
|
754
|
+
* const draft = await brainerce.blog.create({ title: 'Hello World' }, 'store_123');
|
|
649
755
|
* ```
|
|
650
756
|
*/
|
|
651
757
|
this.blog = /* @__PURE__ */ (() => {
|
|
@@ -666,12 +772,30 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
666
772
|
const buildQuery = (params) => Object.fromEntries(
|
|
667
773
|
Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
668
774
|
);
|
|
775
|
+
const requireStoreId = (action, storeId) => {
|
|
776
|
+
if (!storeId) {
|
|
777
|
+
throw new BrainerceError(
|
|
778
|
+
`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.`,
|
|
779
|
+
400
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
return storeId;
|
|
783
|
+
};
|
|
669
784
|
return {
|
|
670
785
|
/**
|
|
671
|
-
* List
|
|
672
|
-
*
|
|
786
|
+
* List posts. Filters: `category`, `tag`, `page`, `limit`.
|
|
787
|
+
*
|
|
788
|
+
* Storefront / vibe-coded mode lists PUBLISHED posts and ignores
|
|
789
|
+
* `storeId` (the store is already in the base URL). Admin mode lists
|
|
790
|
+
* drafts too and REQUIRES `storeId`.
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```typescript
|
|
794
|
+
* const { data } = await client.blog.getPosts({ category: 'news' }); // storefront
|
|
795
|
+
* const { data } = await client.blog.getPosts({}, 'store_123'); // admin
|
|
796
|
+
* ```
|
|
673
797
|
*/
|
|
674
|
-
getPosts: (params = {}) => {
|
|
798
|
+
getPosts: (params = {}, storeId) => {
|
|
675
799
|
const query = buildQuery(params);
|
|
676
800
|
if (this.isVibeCodedMode()) {
|
|
677
801
|
return this.vibeCodedRequest(
|
|
@@ -693,12 +817,23 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
693
817
|
"GET",
|
|
694
818
|
adminBase,
|
|
695
819
|
void 0,
|
|
696
|
-
|
|
820
|
+
{
|
|
821
|
+
...query,
|
|
822
|
+
storeId: requireStoreId("getPosts", storeId)
|
|
823
|
+
}
|
|
697
824
|
);
|
|
698
825
|
},
|
|
699
826
|
/**
|
|
700
|
-
* Fetch one
|
|
701
|
-
*
|
|
827
|
+
* Fetch one PUBLISHED post by its slug. Returns `null` on 404.
|
|
828
|
+
*
|
|
829
|
+
* Storefront / vibe-coded mode only — the admin API has no by-slug
|
|
830
|
+
* lookup (`GET /api/blog/posts/:id` is by id), so this throws in admin
|
|
831
|
+
* mode rather than issuing a request that can only 404.
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```typescript
|
|
835
|
+
* const post = await client.blog.getPost('my-first-post');
|
|
836
|
+
* ```
|
|
702
837
|
*/
|
|
703
838
|
getPost: (slug) => {
|
|
704
839
|
const path = `${publicBase}/${encodeURIComponent(slug)}`;
|
|
@@ -708,42 +843,111 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
708
843
|
if (this.storeId && !this.apiKey) {
|
|
709
844
|
return this.storefrontRequest("GET", path).catch(onNotFound);
|
|
710
845
|
}
|
|
711
|
-
|
|
846
|
+
throw new BrainerceError(
|
|
847
|
+
"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).",
|
|
848
|
+
400
|
|
849
|
+
);
|
|
712
850
|
},
|
|
713
|
-
/**
|
|
714
|
-
|
|
851
|
+
/**
|
|
852
|
+
* Fetch one post by its admin id — drafts included. Admin mode only.
|
|
853
|
+
* Returns `null` on 404.
|
|
854
|
+
*
|
|
855
|
+
* @example
|
|
856
|
+
* ```typescript
|
|
857
|
+
* const post = await client.blog.findById('post_123', 'store_123');
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
860
|
+
findById: (id, storeId) => {
|
|
861
|
+
requireAdmin("findById");
|
|
862
|
+
return this.adminRequest(
|
|
863
|
+
"GET",
|
|
864
|
+
`${adminBase}/${encodeURIComponent(id)}`,
|
|
865
|
+
void 0,
|
|
866
|
+
{ storeId: requireStoreId("findById", storeId) }
|
|
867
|
+
).catch(onNotFound);
|
|
868
|
+
},
|
|
869
|
+
/**
|
|
870
|
+
* Create a blog post in DRAFT status. Admin mode only.
|
|
871
|
+
*
|
|
872
|
+
* @example
|
|
873
|
+
* ```typescript
|
|
874
|
+
* const draft = await client.blog.create({ title: 'Hello World' }, 'store_123');
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
create: (input, storeId) => {
|
|
715
878
|
requireAdmin("create");
|
|
716
|
-
return this.adminRequest("POST", adminBase, input
|
|
879
|
+
return this.adminRequest("POST", adminBase, input, {
|
|
880
|
+
storeId: requireStoreId("create", storeId)
|
|
881
|
+
});
|
|
717
882
|
},
|
|
718
|
-
/**
|
|
719
|
-
|
|
883
|
+
/**
|
|
884
|
+
* Update a blog post by ID. Admin mode only.
|
|
885
|
+
*
|
|
886
|
+
* @example
|
|
887
|
+
* ```typescript
|
|
888
|
+
* await client.blog.update('post_123', { title: 'Renamed' }, 'store_123');
|
|
889
|
+
* ```
|
|
890
|
+
*/
|
|
891
|
+
update: (id, input, storeId) => {
|
|
720
892
|
requireAdmin("update");
|
|
721
893
|
return this.adminRequest(
|
|
722
894
|
"PATCH",
|
|
723
895
|
`${adminBase}/${encodeURIComponent(id)}`,
|
|
724
|
-
input
|
|
896
|
+
input,
|
|
897
|
+
{ storeId: requireStoreId("update", storeId) }
|
|
725
898
|
);
|
|
726
899
|
},
|
|
727
|
-
/**
|
|
728
|
-
|
|
900
|
+
/**
|
|
901
|
+
* Transition status → PUBLISHED (sets publishedAt = now if unset).
|
|
902
|
+
* Admin mode only.
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* ```typescript
|
|
906
|
+
* await client.blog.publish('post_123', 'store_123');
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
909
|
+
publish: (id, storeId) => {
|
|
729
910
|
requireAdmin("publish");
|
|
730
911
|
return this.adminRequest(
|
|
731
912
|
"POST",
|
|
732
|
-
`${adminBase}/${encodeURIComponent(id)}/publish
|
|
913
|
+
`${adminBase}/${encodeURIComponent(id)}/publish`,
|
|
914
|
+
void 0,
|
|
915
|
+
{ storeId: requireStoreId("publish", storeId) }
|
|
733
916
|
);
|
|
734
917
|
},
|
|
735
|
-
/**
|
|
736
|
-
|
|
918
|
+
/**
|
|
919
|
+
* Transition status PUBLISHED → DRAFT. Admin mode only.
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* ```typescript
|
|
923
|
+
* await client.blog.unpublish('post_123', 'store_123');
|
|
924
|
+
* ```
|
|
925
|
+
*/
|
|
926
|
+
unpublish: (id, storeId) => {
|
|
737
927
|
requireAdmin("unpublish");
|
|
738
928
|
return this.adminRequest(
|
|
739
929
|
"POST",
|
|
740
|
-
`${adminBase}/${encodeURIComponent(id)}/unpublish
|
|
930
|
+
`${adminBase}/${encodeURIComponent(id)}/unpublish`,
|
|
931
|
+
void 0,
|
|
932
|
+
{ storeId: requireStoreId("unpublish", storeId) }
|
|
741
933
|
);
|
|
742
934
|
},
|
|
743
|
-
/**
|
|
744
|
-
|
|
935
|
+
/**
|
|
936
|
+
* Hard-delete a blog post. Admin mode only.
|
|
937
|
+
*
|
|
938
|
+
* @example
|
|
939
|
+
* ```typescript
|
|
940
|
+
* await client.blog.remove('post_123', 'store_123');
|
|
941
|
+
* ```
|
|
942
|
+
*/
|
|
943
|
+
remove: async (id, storeId) => {
|
|
745
944
|
requireAdmin("remove");
|
|
746
|
-
await this.adminRequest(
|
|
945
|
+
await this.adminRequest(
|
|
946
|
+
"DELETE",
|
|
947
|
+
`${adminBase}/${encodeURIComponent(id)}`,
|
|
948
|
+
void 0,
|
|
949
|
+
{ storeId: requireStoreId("remove", storeId) }
|
|
950
|
+
);
|
|
747
951
|
}
|
|
748
952
|
};
|
|
749
953
|
})();
|
|
@@ -2270,6 +2474,59 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
2270
2474
|
* console.log('Product type:', product.type); // 'VARIABLE'
|
|
2271
2475
|
* ```
|
|
2272
2476
|
*/
|
|
2477
|
+
/**
|
|
2478
|
+
* Read a kit's contents, with its resolved price and how many can be sold.
|
|
2479
|
+
*
|
|
2480
|
+
* A KIT is one purchasable product assembled from other catalog products. It
|
|
2481
|
+
* holds no inventory of its own: `available` is whichever component runs out
|
|
2482
|
+
* first. Outside `FIXED` pricing the product row's `basePrice` is a
|
|
2483
|
+
* placeholder, so use the `price` returned here.
|
|
2484
|
+
*
|
|
2485
|
+
* Safe to call for any product — a non-KIT returns an empty, unsellable
|
|
2486
|
+
* shape rather than throwing, so callers need not branch on product type.
|
|
2487
|
+
*
|
|
2488
|
+
* @example
|
|
2489
|
+
* ```typescript
|
|
2490
|
+
* const kit = await client.getKitComponents('prod_123');
|
|
2491
|
+
* console.log(kit.price, kit.available, kit.components.length);
|
|
2492
|
+
* ```
|
|
2493
|
+
*/
|
|
2494
|
+
async getKitComponents(productId) {
|
|
2495
|
+
return this.request(
|
|
2496
|
+
"GET",
|
|
2497
|
+
`/api/v1/products/${encodePathSegment(productId)}/kit-components`
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Replace a kit's contents, and optionally how it is priced.
|
|
2502
|
+
*
|
|
2503
|
+
* A full replace, not a patch: send the list you want the kit to end up with.
|
|
2504
|
+
* Omit `pricingMode` to leave the kit's current mode untouched.
|
|
2505
|
+
*
|
|
2506
|
+
* Rejected: a product that is not a KIT, a component from another store, a
|
|
2507
|
+
* component that is itself a KIT, a VARIABLE component with no variant
|
|
2508
|
+
* pinned, a variant that does not belong to its product, and the same slot
|
|
2509
|
+
* listed twice.
|
|
2510
|
+
*
|
|
2511
|
+
* @example
|
|
2512
|
+
* ```typescript
|
|
2513
|
+
* await client.setKitComponents('prod_123', {
|
|
2514
|
+
* components: [
|
|
2515
|
+
* { componentProductId: 'prod_bottle', quantity: 1 },
|
|
2516
|
+
* { componentProductId: 'prod_glass', quantity: 2 },
|
|
2517
|
+
* ],
|
|
2518
|
+
* pricingMode: 'SUM_MINUS_PERCENT',
|
|
2519
|
+
* discountValue: 10,
|
|
2520
|
+
* });
|
|
2521
|
+
* ```
|
|
2522
|
+
*/
|
|
2523
|
+
async setKitComponents(productId, body) {
|
|
2524
|
+
return this.request(
|
|
2525
|
+
"PUT",
|
|
2526
|
+
`/api/v1/products/${encodePathSegment(productId)}/kit-components`,
|
|
2527
|
+
body
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2273
2530
|
async convertToVariable(productId) {
|
|
2274
2531
|
return this.request(
|
|
2275
2532
|
"PATCH",
|
|
@@ -10045,71 +10302,88 @@ var _BrainerceClient = class _BrainerceClient {
|
|
|
10045
10302
|
}
|
|
10046
10303
|
throw new Error("uploadReviewPhoto requires storefront or vibe-coded mode");
|
|
10047
10304
|
}
|
|
10048
|
-
// -------------------- Team Management (Admin) -
|
|
10049
|
-
//
|
|
10050
|
-
// a `brainerce_*` API key.
|
|
10051
|
-
//
|
|
10052
|
-
//
|
|
10053
|
-
//
|
|
10054
|
-
//
|
|
10305
|
+
// -------------------- Team Management (Admin) - NOT REACHABLE BY API KEY --------------------
|
|
10306
|
+
// ⛔ These seven used to be documented as "the ONLY team endpoints reachable
|
|
10307
|
+
// with a `brainerce_*` API key". That was wrong, and every one of them 403s.
|
|
10308
|
+
//
|
|
10309
|
+
// The routes exist and the guards admit an api_key, but admission is not the
|
|
10310
|
+
// same as success. `external-api.controller.ts` hands `TeamService` a synthetic
|
|
10311
|
+
// principal — `SYSTEM_USER_ID = 'api-key-user'` (`:171`, used at `:4572`,
|
|
10312
|
+
// `:4586`, `:4607`, ...) — and every TeamService method opens with
|
|
10313
|
+
// `verifyAccountAccess` / `verifyAccountOwner`, which is an
|
|
10314
|
+
// `accountUser.findFirst({ where: { userId, ... } })`. No `AccountUser` row has
|
|
10315
|
+
// `userId = 'api-key-user'` anywhere; the sentinel is row-less by construction.
|
|
10316
|
+
// So the lookup misses and every call throws `Access denied to this account`.
|
|
10317
|
+
//
|
|
10318
|
+
// The store-level equivalents (`getStoreTeam`, `inviteStoreMember`, ...) sit
|
|
10319
|
+
// behind `DashboardOnlyGuard`, which rejects api_key principals by design. So
|
|
10320
|
+
// BOTH families are dashboard-only and there is no API-key path to team
|
|
10321
|
+
// management at all.
|
|
10322
|
+
//
|
|
10323
|
+
// ⛔ Do NOT "fix" this by letting api_key through. Team membership is
|
|
10324
|
+
// ACCOUNT-scoped and `inviteMember` grants `Role.ACCOUNT_OWNER`, i.e. ownership
|
|
10325
|
+
// of every store on the account. An API key is bound to exactly ONE store, so
|
|
10326
|
+
// admitting it here converts a single-store credential into account ownership.
|
|
10327
|
+
// That is a privilege-escalation primitive, not a missing feature.
|
|
10328
|
+
//
|
|
10329
|
+
// These throw rather than return a 403 from the wire, so the failure names its
|
|
10330
|
+
// own cause instead of arriving as a bare status code. Same treatment as
|
|
10331
|
+
// `createDonation` above. Agents: use the dashboard, or the `team:*` tools on
|
|
10332
|
+
// the admin MCP server, which are OAuth-authenticated and store-scoped.
|
|
10333
|
+
/** The message every team method throws. One string so they cannot drift. */
|
|
10334
|
+
teamIsDashboardOnly(method) {
|
|
10335
|
+
throw new Error(
|
|
10336
|
+
`${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.`
|
|
10337
|
+
);
|
|
10338
|
+
}
|
|
10055
10339
|
/**
|
|
10056
|
-
* @deprecated Retiring, but
|
|
10057
|
-
* is dashboard-only
|
|
10340
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10341
|
+
* api_key principal and `getStoreTeam` is dashboard-only. Use the dashboard.
|
|
10058
10342
|
*/
|
|
10059
10343
|
async getTeamMembers() {
|
|
10060
|
-
return this.
|
|
10344
|
+
return this.teamIsDashboardOnly("getTeamMembers");
|
|
10061
10345
|
}
|
|
10062
10346
|
/**
|
|
10063
|
-
* @deprecated Retiring, but
|
|
10064
|
-
* is dashboard-only
|
|
10347
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10348
|
+
* api_key principal and `getStoreTeam` is dashboard-only. Use the dashboard.
|
|
10065
10349
|
*/
|
|
10066
10350
|
async getTeamInvitations() {
|
|
10067
|
-
return this.
|
|
10351
|
+
return this.teamIsDashboardOnly("getTeamInvitations");
|
|
10068
10352
|
}
|
|
10069
10353
|
/**
|
|
10070
|
-
* @deprecated Retiring, but
|
|
10071
|
-
*
|
|
10354
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10355
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10072
10356
|
*/
|
|
10073
|
-
async inviteTeamMember(
|
|
10074
|
-
return this.
|
|
10357
|
+
async inviteTeamMember(_data) {
|
|
10358
|
+
return this.teamIsDashboardOnly("inviteTeamMember");
|
|
10075
10359
|
}
|
|
10076
10360
|
/**
|
|
10077
|
-
* @deprecated Retiring, but
|
|
10078
|
-
*
|
|
10361
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10362
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10079
10363
|
*/
|
|
10080
|
-
async resendTeamInvitation(
|
|
10081
|
-
return this.
|
|
10082
|
-
"POST",
|
|
10083
|
-
`/api/v1/team/invitations/${encodePathSegment(invitationId)}/resend`
|
|
10084
|
-
);
|
|
10364
|
+
async resendTeamInvitation(_invitationId) {
|
|
10365
|
+
return this.teamIsDashboardOnly("resendTeamInvitation");
|
|
10085
10366
|
}
|
|
10086
10367
|
/**
|
|
10087
|
-
* @deprecated Retiring, but
|
|
10088
|
-
*
|
|
10368
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10369
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10089
10370
|
*/
|
|
10090
|
-
async revokeTeamInvitation(
|
|
10091
|
-
|
|
10092
|
-
"DELETE",
|
|
10093
|
-
`/api/v1/team/invitations/${encodePathSegment(invitationId)}`
|
|
10094
|
-
);
|
|
10371
|
+
async revokeTeamInvitation(_invitationId) {
|
|
10372
|
+
return this.teamIsDashboardOnly("revokeTeamInvitation");
|
|
10095
10373
|
}
|
|
10096
10374
|
/**
|
|
10097
|
-
* @deprecated Retiring, but
|
|
10098
|
-
*
|
|
10375
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10376
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10099
10377
|
*/
|
|
10100
|
-
async updateTeamMemberRole(
|
|
10101
|
-
return this.
|
|
10102
|
-
"PATCH",
|
|
10103
|
-
`/api/v1/team/members/${encodePathSegment(memberId)}/role`,
|
|
10104
|
-
data
|
|
10105
|
-
);
|
|
10378
|
+
async updateTeamMemberRole(_memberId, _data) {
|
|
10379
|
+
return this.teamIsDashboardOnly("updateTeamMemberRole");
|
|
10106
10380
|
}
|
|
10107
10381
|
/**
|
|
10108
|
-
* @deprecated Retiring, but
|
|
10109
|
-
*
|
|
10382
|
+
* @deprecated Retiring, but this ALWAYS throws: `/v1/team/*` rejects the
|
|
10383
|
+
* api_key principal and the store-level route is dashboard-only. Use the dashboard.
|
|
10110
10384
|
*/
|
|
10111
|
-
async removeTeamMember(
|
|
10112
|
-
|
|
10385
|
+
async removeTeamMember(_memberId) {
|
|
10386
|
+
return this.teamIsDashboardOnly("removeTeamMember");
|
|
10113
10387
|
}
|
|
10114
10388
|
// -------------------- Store Team Management (Admin) --------------------
|
|
10115
10389
|
// Store-level team management. Each store has its own team with roles and permissions.
|