brainerce 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -204,7 +204,7 @@ function isDevGuardsEnabled() {
204
204
  }
205
205
 
206
206
  // src/version.ts
207
- var SDK_VERSION = "2.5.0";
207
+ var SDK_VERSION = "2.7.0";
208
208
 
209
209
  // src/client.ts
210
210
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -381,10 +381,14 @@ var _BrainerceClient = class _BrainerceClient {
381
381
  * Storefront (public) and vibe-coded modes only. Rate-limited server-side to
382
382
  * 3 requests / 60s per IP, plus one confirmation email per address per 24h.
383
383
  *
384
- * **Where the discount goes.** A "10% off your first order" popup needs a
385
- * coupon from the dashboard create one with the `customer_first_order`
386
- * condition and show the code after a successful call. Subscribing does not
387
- * mint a code on its own.
384
+ * **Where the discount goes.** Configure the newsletter welcome offer and the
385
+ * platform issues the coupon itself: read it with `marketing.getBenefit()`,
386
+ * show those terms beside the field, and stop there.
387
+ *
388
+ * ⛔ DO NOT SHOW A CODE AFTER THIS CALL RESOLVES. No coupon exists yet. It is
389
+ * minted when the recipient clicks the confirmation link, and it is mailed to
390
+ * them at that moment — a code rendered here is a code that was never issued.
391
+ * Say "check your email", the same as for the subscription itself.
388
392
  *
389
393
  * @example
390
394
  * ```typescript
@@ -412,6 +416,163 @@ var _BrainerceClient = class _BrainerceClient {
412
416
  "/marketing/subscribe",
413
417
  input
414
418
  );
419
+ },
420
+ /**
421
+ * The welcome offer to render beside the signup field, or `null` when this
422
+ * store offers none.
423
+ *
424
+ * Show the discount, how long the coupon lasts, any minimum order, whether
425
+ * it is first-order only, and the merchant's own headline and terms. Then
426
+ * post to `marketing.subscribe()` and tell the shopper to check their
427
+ * inbox.
428
+ *
429
+ * ⛔ THE COUPON DOES NOT EXIST YET at any point in that sequence. It is
430
+ * created when the recipient clicks the confirmation link in their email,
431
+ * and it is mailed to them there. Rendering a code on this screen renders a
432
+ * code nobody was issued.
433
+ *
434
+ * ⛔ Takes no email address and returns nothing about any individual, on
435
+ * purpose. There is no "has this person already claimed" call, because an
436
+ * unauthenticated one would be an oracle for who shops here. If you need to
437
+ * discourage a repeat signup, say the offer is one per address; do not try
438
+ * to detect it.
439
+ *
440
+ * `null` is the common case on a store that never set this up, so handle it
441
+ * rather than assuming the object. Cache it per page load: it belongs to
442
+ * the store, not to the visitor.
443
+ *
444
+ * Storefront (public) and vibe-coded modes.
445
+ *
446
+ * @param locale - Storefront locale, e.g. `"he"`. Picks the language of the
447
+ * headline and terms; falls back to the store language when omitted.
448
+ *
449
+ * @example
450
+ * ```typescript
451
+ * const offer = await brainerce.marketing.getBenefit('he');
452
+ * if (offer) {
453
+ * // "10% הנחה על ההזמנה הראשונה"
454
+ * render(offer.headline ?? defaultHeadline(offer), offer.terms);
455
+ * }
456
+ * await brainerce.marketing.subscribe({ email, locale: 'he', honeypot });
457
+ * // → "בדקו את המייל שלכם" — never a coupon code
458
+ * ```
459
+ */
460
+ getBenefit: async (locale) => {
461
+ const query = locale ? { locale } : void 0;
462
+ if (this.isVibeCodedMode()) {
463
+ return this.vibeCodedRequest(
464
+ "GET",
465
+ "/newsletter-benefit",
466
+ void 0,
467
+ query
468
+ );
469
+ }
470
+ return this.storefrontRequest(
471
+ "GET",
472
+ "/newsletter-benefit",
473
+ void 0,
474
+ query
475
+ );
476
+ }
477
+ };
478
+ // -------------------- Newsletter signup benefit (Admin) --------------------
479
+ /**
480
+ * Manage the newsletter welcome offer: the terms merchants configure, and the
481
+ * benefits that offer has produced.
482
+ *
483
+ * Admin mode (`apiKey`) only, on the `coupons:read` / `coupons:write` scopes.
484
+ * The benefit IS a coupon feature — it mints a Coupon row and the coupon
485
+ * machinery enforces it — so it carries no scope of its own.
486
+ *
487
+ * ⛔ THERE IS NO "ISSUE A BENEFIT TO THIS ADDRESS" CALL, and there will not
488
+ * be one. A benefit exists because someone submitted the signup form AND
489
+ * clicked the confirmation link; handing one out directly would skip the
490
+ * consent the double opt-in exists to collect and break the one-per-address
491
+ * guarantee that the grant's unique constraint provides. `resend` re-sends a
492
+ * code that already exists; it never creates one.
493
+ */
494
+ this.newsletterBenefit = {
495
+ /**
496
+ * The store's configuration, or `null` when none was ever saved.
497
+ *
498
+ * `null` and `{ enabled: false }` are different: never configured, versus
499
+ * configured and switched off. Both mean "offer nothing" to a storefront.
500
+ */
501
+ getSettings: async () => {
502
+ return this.adminRequest(
503
+ "GET",
504
+ "/api/v1/newsletter-benefit/settings"
505
+ );
506
+ },
507
+ /**
508
+ * Create or replace the offer.
509
+ *
510
+ * ⛔ A FULL REPLACEMENT, not a patch. Every field is written, so a field you
511
+ * omit is cleared rather than kept.
512
+ *
513
+ * Saving never rewrites a promise already made: signups still waiting for a
514
+ * confirmation click keep the terms they were shown, and coupons already
515
+ * issued are untouched. Switching `enabled` off stops new offers and leaves
516
+ * every issued coupon working until it expires.
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * await brainerce.newsletterBenefit.updateSettings({
521
+ * enabled: true,
522
+ * discountType: 'PERCENTAGE',
523
+ * discountValue: 10,
524
+ * minimumOrderAmount: 200,
525
+ * combinesWithOther: false,
526
+ * validityDays: 7,
527
+ * eligibilityTtlHours: 168,
528
+ * firstOrderOnly: true,
529
+ * content: { he: { headline: '10% הנחה על ההזמנה הראשונה' } },
530
+ * });
531
+ * ```
532
+ */
533
+ updateSettings: async (input) => {
534
+ return this.adminRequest(
535
+ "PUT",
536
+ "/api/v1/newsletter-benefit/settings",
537
+ input
538
+ );
539
+ },
540
+ /**
541
+ * Issued benefits, newest first, as `{ data, meta }`.
542
+ *
543
+ * ⛔ NO EMAIL FILTER — the API refuses the parameter. Filter the page you
544
+ * get back rather than asking the server about one address.
545
+ */
546
+ listGrants: async (params = {}) => {
547
+ return this.adminRequest(
548
+ "GET",
549
+ "/api/v1/newsletter-benefit/grants",
550
+ void 0,
551
+ {
552
+ page: params.page,
553
+ limit: params.limit,
554
+ status: params.status,
555
+ from: params.from,
556
+ to: params.to
557
+ }
558
+ );
559
+ },
560
+ /**
561
+ * Re-send one benefit that went astray.
562
+ *
563
+ * ⛔ SENDS THE SAME CODE. It never mints a second coupon, so a support
564
+ * ticket cannot become two discounts. For a benefit whose issuance failed
565
+ * before any coupon existed, this retries the issuance and mails the result.
566
+ *
567
+ * Rejects a signup that has not been confirmed and one that lapsed before a
568
+ * coupon was minted: there is nothing to re-send in either case, and
569
+ * nothing that may be created.
570
+ */
571
+ resend: async (grantId) => {
572
+ return this.adminRequest(
573
+ "POST",
574
+ `/api/v1/newsletter-benefit/grants/${encodePathSegment(grantId)}/resend`
575
+ );
415
576
  }
416
577
  };
417
578
  // -------------------- Stock alerts --------------------
@@ -2505,8 +2666,13 @@ var _BrainerceClient = class _BrainerceClient {
2505
2666
  *
2506
2667
  * Rejected: a product that is not a KIT, a component from another store, a
2507
2668
  * 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.
2669
+ * pinned, a variant that does not belong to its product, the same slot
2670
+ * listed twice, and a component whose product or pinned variant is not
2671
+ * published.
2672
+ *
2673
+ * That last one is checked over the WHOLE list you send, not just the rows
2674
+ * you changed. Once a product already inside a kit is unpublished, no edit
2675
+ * to that kit saves until you publish it again or drop it from the list.
2510
2676
  *
2511
2677
  * @example
2512
2678
  * ```typescript
@@ -3111,6 +3277,13 @@ var _BrainerceClient = class _BrainerceClient {
3111
3277
  * exist and 404'd silently. The live route is product-scoped:
3112
3278
  * `GET /api/v1/products/:id/inventory`. A product with no inventory row
3113
3279
  * reads back as all zeroes rather than 404ing.
3280
+ *
3281
+ * The response carries the whole {@link ProductInventoryResponse} — the
3282
+ * `InventoryItem` id, `trackingMode`, `backorderMode`, `backorderLimit`,
3283
+ * `lowStockThreshold`, `lastInventorySyncAt` and `updatedAt` alongside the
3284
+ * three counters. It always did; only the three counters were declared.
3285
+ * On the all-zeroes no-row branch everything but the counters is absent,
3286
+ * so test `id` rather than expecting a 404.
3114
3287
  */
3115
3288
  async getInventory(productId) {
3116
3289
  return this.adminRequest(
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.5.0";
118
+ var SDK_VERSION = "2.7.0";
119
119
 
120
120
  // src/client.ts
121
121
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -292,10 +292,14 @@ var _BrainerceClient = class _BrainerceClient {
292
292
  * Storefront (public) and vibe-coded modes only. Rate-limited server-side to
293
293
  * 3 requests / 60s per IP, plus one confirmation email per address per 24h.
294
294
  *
295
- * **Where the discount goes.** A "10% off your first order" popup needs a
296
- * coupon from the dashboard create one with the `customer_first_order`
297
- * condition and show the code after a successful call. Subscribing does not
298
- * mint a code on its own.
295
+ * **Where the discount goes.** Configure the newsletter welcome offer and the
296
+ * platform issues the coupon itself: read it with `marketing.getBenefit()`,
297
+ * show those terms beside the field, and stop there.
298
+ *
299
+ * ⛔ DO NOT SHOW A CODE AFTER THIS CALL RESOLVES. No coupon exists yet. It is
300
+ * minted when the recipient clicks the confirmation link, and it is mailed to
301
+ * them at that moment — a code rendered here is a code that was never issued.
302
+ * Say "check your email", the same as for the subscription itself.
299
303
  *
300
304
  * @example
301
305
  * ```typescript
@@ -323,6 +327,163 @@ var _BrainerceClient = class _BrainerceClient {
323
327
  "/marketing/subscribe",
324
328
  input
325
329
  );
330
+ },
331
+ /**
332
+ * The welcome offer to render beside the signup field, or `null` when this
333
+ * store offers none.
334
+ *
335
+ * Show the discount, how long the coupon lasts, any minimum order, whether
336
+ * it is first-order only, and the merchant's own headline and terms. Then
337
+ * post to `marketing.subscribe()` and tell the shopper to check their
338
+ * inbox.
339
+ *
340
+ * ⛔ THE COUPON DOES NOT EXIST YET at any point in that sequence. It is
341
+ * created when the recipient clicks the confirmation link in their email,
342
+ * and it is mailed to them there. Rendering a code on this screen renders a
343
+ * code nobody was issued.
344
+ *
345
+ * ⛔ Takes no email address and returns nothing about any individual, on
346
+ * purpose. There is no "has this person already claimed" call, because an
347
+ * unauthenticated one would be an oracle for who shops here. If you need to
348
+ * discourage a repeat signup, say the offer is one per address; do not try
349
+ * to detect it.
350
+ *
351
+ * `null` is the common case on a store that never set this up, so handle it
352
+ * rather than assuming the object. Cache it per page load: it belongs to
353
+ * the store, not to the visitor.
354
+ *
355
+ * Storefront (public) and vibe-coded modes.
356
+ *
357
+ * @param locale - Storefront locale, e.g. `"he"`. Picks the language of the
358
+ * headline and terms; falls back to the store language when omitted.
359
+ *
360
+ * @example
361
+ * ```typescript
362
+ * const offer = await brainerce.marketing.getBenefit('he');
363
+ * if (offer) {
364
+ * // "10% הנחה על ההזמנה הראשונה"
365
+ * render(offer.headline ?? defaultHeadline(offer), offer.terms);
366
+ * }
367
+ * await brainerce.marketing.subscribe({ email, locale: 'he', honeypot });
368
+ * // → "בדקו את המייל שלכם" — never a coupon code
369
+ * ```
370
+ */
371
+ getBenefit: async (locale) => {
372
+ const query = locale ? { locale } : void 0;
373
+ if (this.isVibeCodedMode()) {
374
+ return this.vibeCodedRequest(
375
+ "GET",
376
+ "/newsletter-benefit",
377
+ void 0,
378
+ query
379
+ );
380
+ }
381
+ return this.storefrontRequest(
382
+ "GET",
383
+ "/newsletter-benefit",
384
+ void 0,
385
+ query
386
+ );
387
+ }
388
+ };
389
+ // -------------------- Newsletter signup benefit (Admin) --------------------
390
+ /**
391
+ * Manage the newsletter welcome offer: the terms merchants configure, and the
392
+ * benefits that offer has produced.
393
+ *
394
+ * Admin mode (`apiKey`) only, on the `coupons:read` / `coupons:write` scopes.
395
+ * The benefit IS a coupon feature — it mints a Coupon row and the coupon
396
+ * machinery enforces it — so it carries no scope of its own.
397
+ *
398
+ * ⛔ THERE IS NO "ISSUE A BENEFIT TO THIS ADDRESS" CALL, and there will not
399
+ * be one. A benefit exists because someone submitted the signup form AND
400
+ * clicked the confirmation link; handing one out directly would skip the
401
+ * consent the double opt-in exists to collect and break the one-per-address
402
+ * guarantee that the grant's unique constraint provides. `resend` re-sends a
403
+ * code that already exists; it never creates one.
404
+ */
405
+ this.newsletterBenefit = {
406
+ /**
407
+ * The store's configuration, or `null` when none was ever saved.
408
+ *
409
+ * `null` and `{ enabled: false }` are different: never configured, versus
410
+ * configured and switched off. Both mean "offer nothing" to a storefront.
411
+ */
412
+ getSettings: async () => {
413
+ return this.adminRequest(
414
+ "GET",
415
+ "/api/v1/newsletter-benefit/settings"
416
+ );
417
+ },
418
+ /**
419
+ * Create or replace the offer.
420
+ *
421
+ * ⛔ A FULL REPLACEMENT, not a patch. Every field is written, so a field you
422
+ * omit is cleared rather than kept.
423
+ *
424
+ * Saving never rewrites a promise already made: signups still waiting for a
425
+ * confirmation click keep the terms they were shown, and coupons already
426
+ * issued are untouched. Switching `enabled` off stops new offers and leaves
427
+ * every issued coupon working until it expires.
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * await brainerce.newsletterBenefit.updateSettings({
432
+ * enabled: true,
433
+ * discountType: 'PERCENTAGE',
434
+ * discountValue: 10,
435
+ * minimumOrderAmount: 200,
436
+ * combinesWithOther: false,
437
+ * validityDays: 7,
438
+ * eligibilityTtlHours: 168,
439
+ * firstOrderOnly: true,
440
+ * content: { he: { headline: '10% הנחה על ההזמנה הראשונה' } },
441
+ * });
442
+ * ```
443
+ */
444
+ updateSettings: async (input) => {
445
+ return this.adminRequest(
446
+ "PUT",
447
+ "/api/v1/newsletter-benefit/settings",
448
+ input
449
+ );
450
+ },
451
+ /**
452
+ * Issued benefits, newest first, as `{ data, meta }`.
453
+ *
454
+ * ⛔ NO EMAIL FILTER — the API refuses the parameter. Filter the page you
455
+ * get back rather than asking the server about one address.
456
+ */
457
+ listGrants: async (params = {}) => {
458
+ return this.adminRequest(
459
+ "GET",
460
+ "/api/v1/newsletter-benefit/grants",
461
+ void 0,
462
+ {
463
+ page: params.page,
464
+ limit: params.limit,
465
+ status: params.status,
466
+ from: params.from,
467
+ to: params.to
468
+ }
469
+ );
470
+ },
471
+ /**
472
+ * Re-send one benefit that went astray.
473
+ *
474
+ * ⛔ SENDS THE SAME CODE. It never mints a second coupon, so a support
475
+ * ticket cannot become two discounts. For a benefit whose issuance failed
476
+ * before any coupon existed, this retries the issuance and mails the result.
477
+ *
478
+ * Rejects a signup that has not been confirmed and one that lapsed before a
479
+ * coupon was minted: there is nothing to re-send in either case, and
480
+ * nothing that may be created.
481
+ */
482
+ resend: async (grantId) => {
483
+ return this.adminRequest(
484
+ "POST",
485
+ `/api/v1/newsletter-benefit/grants/${encodePathSegment(grantId)}/resend`
486
+ );
326
487
  }
327
488
  };
328
489
  // -------------------- Stock alerts --------------------
@@ -2416,8 +2577,13 @@ var _BrainerceClient = class _BrainerceClient {
2416
2577
  *
2417
2578
  * Rejected: a product that is not a KIT, a component from another store, a
2418
2579
  * 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.
2580
+ * pinned, a variant that does not belong to its product, the same slot
2581
+ * listed twice, and a component whose product or pinned variant is not
2582
+ * published.
2583
+ *
2584
+ * That last one is checked over the WHOLE list you send, not just the rows
2585
+ * you changed. Once a product already inside a kit is unpublished, no edit
2586
+ * to that kit saves until you publish it again or drop it from the list.
2421
2587
  *
2422
2588
  * @example
2423
2589
  * ```typescript
@@ -3022,6 +3188,13 @@ var _BrainerceClient = class _BrainerceClient {
3022
3188
  * exist and 404'd silently. The live route is product-scoped:
3023
3189
  * `GET /api/v1/products/:id/inventory`. A product with no inventory row
3024
3190
  * reads back as all zeroes rather than 404ing.
3191
+ *
3192
+ * The response carries the whole {@link ProductInventoryResponse} — the
3193
+ * `InventoryItem` id, `trackingMode`, `backorderMode`, `backorderLimit`,
3194
+ * `lowStockThreshold`, `lastInventorySyncAt` and `updatedAt` alongside the
3195
+ * three counters. It always did; only the three counters were declared.
3196
+ * On the all-zeroes no-row branch everything but the counters is absent,
3197
+ * so test `id` rather than expecting a 404.
3025
3198
  */
3026
3199
  async getInventory(productId) {
3027
3200
  return this.adminRequest(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainerce",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",