@neetru/sdk 1.0.0 → 1.1.1

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/dist/auth.cjs +184 -2
  3. package/dist/auth.cjs.map +1 -1
  4. package/dist/auth.d.cts +1 -1
  5. package/dist/auth.d.ts +1 -1
  6. package/dist/auth.mjs +184 -2
  7. package/dist/auth.mjs.map +1 -1
  8. package/dist/catalog.cjs +4 -0
  9. package/dist/catalog.cjs.map +1 -1
  10. package/dist/catalog.d.cts +1 -1
  11. package/dist/catalog.d.ts +1 -1
  12. package/dist/catalog.mjs +4 -0
  13. package/dist/catalog.mjs.map +1 -1
  14. package/dist/checkout.cjs +279 -0
  15. package/dist/checkout.cjs.map +1 -0
  16. package/dist/checkout.d.cts +1 -0
  17. package/dist/checkout.d.ts +1 -0
  18. package/dist/checkout.mjs +276 -0
  19. package/dist/checkout.mjs.map +1 -0
  20. package/dist/db.cjs +4 -0
  21. package/dist/db.cjs.map +1 -1
  22. package/dist/db.d.cts +1 -1
  23. package/dist/db.d.ts +1 -1
  24. package/dist/db.mjs +4 -0
  25. package/dist/db.mjs.map +1 -1
  26. package/dist/entitlements.cjs +4 -0
  27. package/dist/entitlements.cjs.map +1 -1
  28. package/dist/entitlements.d.cts +1 -1
  29. package/dist/entitlements.d.ts +1 -1
  30. package/dist/entitlements.mjs +4 -0
  31. package/dist/entitlements.mjs.map +1 -1
  32. package/dist/index.cjs +187 -3
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +3 -3
  35. package/dist/index.d.ts +3 -3
  36. package/dist/index.mjs +186 -4
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/mocks.d.cts +1 -1
  39. package/dist/mocks.d.ts +1 -1
  40. package/dist/react.cjs +158 -0
  41. package/dist/react.cjs.map +1 -0
  42. package/dist/react.d.cts +112 -0
  43. package/dist/react.d.ts +112 -0
  44. package/dist/react.mjs +134 -0
  45. package/dist/react.mjs.map +1 -0
  46. package/dist/support.cjs +4 -0
  47. package/dist/support.cjs.map +1 -1
  48. package/dist/support.d.cts +1 -1
  49. package/dist/support.d.ts +1 -1
  50. package/dist/support.mjs +4 -0
  51. package/dist/support.mjs.map +1 -1
  52. package/dist/telemetry.cjs +4 -0
  53. package/dist/telemetry.cjs.map +1 -1
  54. package/dist/telemetry.d.cts +1 -1
  55. package/dist/telemetry.d.ts +1 -1
  56. package/dist/telemetry.mjs +4 -0
  57. package/dist/telemetry.mjs.map +1 -1
  58. package/dist/{types-PKUaFtBY.d.cts → types-BA53dd8S.d.cts} +83 -1
  59. package/dist/{types-PKUaFtBY.d.ts → types-BA53dd8S.d.ts} +83 -1
  60. package/dist/usage.cjs +4 -0
  61. package/dist/usage.cjs.map +1 -1
  62. package/dist/usage.d.cts +1 -1
  63. package/dist/usage.d.ts +1 -1
  64. package/dist/usage.mjs +4 -0
  65. package/dist/usage.mjs.map +1 -1
  66. package/package.json +27 -5
package/CHANGELOG.md CHANGED
@@ -7,12 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Planned (post-1.0)
10
+ ### Planned (post-1.1)
11
11
  - Telemetry batching + flush on unload.
12
12
  - LRU cache em entitlements.check (TTL configurável).
13
13
  - size-limit budget no CI (<12KB gz core com db namespace).
14
14
  - CDN distribution (`cdn.neetru.com/sdk/v1/`).
15
15
 
16
+ ## [1.1.0] - 2026-05-08 — **Checkout namespace + React helpers**
17
+
18
+ Adiciona o namespace `client.checkout` (start/get/cancel intent) com auto-redirect
19
+ em browser. Suporte ao fluxo Sprint 13 onde produtos SaaS delegam checkout pro
20
+ portal `minhaconta.neetru.com` via `POST /api/v1/checkout/intents`.
21
+
22
+ ### Added
23
+ - **`client.checkout.start({productId, planId, callbackUrl, tenantType?, tenantId?})`** — cria
24
+ `checkout_intents/{intentId}` no Core e (em browser) redireciona automaticamente
25
+ pra `minhaconta.neetru.com/portal/checkout/{intentId}`. Em Node/SSR retorna
26
+ `{intentId, redirectUrl}` sem efeito colateral.
27
+ - **`client.checkout.get(intentId)`** — lê estado atual do intent.
28
+ - **`client.checkout.cancel(intentId)`** — marca intent como `cancelled`.
29
+ - **Subpath `@neetru/sdk/react`** — componente `<CheckoutLink>` (wrapper de `<a>`
30
+ que dispara `start()` no click) + `<EntitlementGate mode="block|readonly">`
31
+ + hook `useEntitlementContext()` pra desabilitar escritas quando free tier
32
+ estoura limite (decisão CEO §5 — read-only sempre, não hard-block).
33
+ - **`MockCheckout`** — implementação dev (`NEETRU_ENV=dev`) sem network. Retorna
34
+ URL fake `https://localhost:9003/portal/checkout/{intentId}` pra dev externo
35
+ testar UI sem provisionar conta Neetru.
36
+ - **Peer dep `react ^18 || ^19`** marcado opcional (só carregado se import `/react`).
37
+
38
+ ### Changed
39
+ - `VERSION` const bump `1.0.0 → 1.1.0`.
40
+ - `NeetruClient.checkout` adicionado — backward-compatible (caller que não usa
41
+ o namespace não é afetado).
42
+
43
+ ### Notes
44
+ - API stability test (`api-surface-stability.test.ts`) atualizado pra incluir
45
+ `checkout` no inventário canônico.
46
+
16
47
  ## [1.0.0] - 2026-05-06 — **GA (General Availability)**
17
48
 
18
49
  Marco de estabilidade. A partir desta versão, **a superfície pública do SDK
package/dist/auth.cjs CHANGED
@@ -70,10 +70,14 @@ async function httpRequest(config, opts) {
70
70
  headers["content-type"] = "application/json";
71
71
  init.body = JSON.stringify(opts.body);
72
72
  }
73
+ init.signal = AbortSignal.timeout(3e4);
73
74
  let res;
74
75
  try {
75
76
  res = await config.fetch(url, init);
76
77
  } catch (err) {
78
+ if (err instanceof DOMException && err.name === "TimeoutError") {
79
+ throw new NeetruError("network_error", "Network error: timeout after 30s");
80
+ }
77
81
  const message = err instanceof Error ? err.message : "fetch failed";
78
82
  throw new NeetruError("network_error", `Network error: ${message}`);
79
83
  }
@@ -692,6 +696,181 @@ function createDbNamespace(config) {
692
696
  };
693
697
  }
694
698
 
699
+ // src/checkout.ts
700
+ function parseStartResponse(raw) {
701
+ if (!raw || typeof raw !== "object") {
702
+ throw new NeetruError("invalid_response", "checkout.start response is not an object");
703
+ }
704
+ const r = raw;
705
+ if (typeof r.intentId !== "string" || !r.intentId) {
706
+ throw new NeetruError("invalid_response", "checkout.start response missing intentId");
707
+ }
708
+ if (typeof r.redirectUrl !== "string" || !r.redirectUrl) {
709
+ throw new NeetruError("invalid_response", "checkout.start response missing redirectUrl");
710
+ }
711
+ return {
712
+ intentId: r.intentId,
713
+ redirectUrl: r.redirectUrl,
714
+ status: r.status ?? "pending",
715
+ expiresAt: typeof r.expiresAt === "string" ? r.expiresAt : (/* @__PURE__ */ new Date()).toISOString(),
716
+ requiresKyc: r.requiresKyc === true
717
+ };
718
+ }
719
+ function parseGetResponse(raw) {
720
+ if (!raw || typeof raw !== "object") {
721
+ throw new NeetruError("invalid_response", "checkout.get response is not an object");
722
+ }
723
+ const r = raw;
724
+ const intent = r.intent;
725
+ if (!intent || typeof intent !== "object") {
726
+ throw new NeetruError("invalid_response", "checkout.get response missing intent");
727
+ }
728
+ if (typeof intent.intentId !== "string") {
729
+ throw new NeetruError("invalid_response", "checkout.get response missing intentId");
730
+ }
731
+ return {
732
+ intentId: intent.intentId,
733
+ uid: intent.uid ?? "",
734
+ targetTenantId: intent.targetTenantId ?? "",
735
+ targetTenantType: intent.targetTenantType ?? "pf",
736
+ productId: intent.productId ?? "",
737
+ planId: intent.planId ?? "",
738
+ callbackUrl: intent.callbackUrl ?? "",
739
+ status: intent.status ?? "pending",
740
+ stripeSessionId: intent.stripeSessionId ?? null,
741
+ expiresAt: intent.expiresAt ?? (/* @__PURE__ */ new Date()).toISOString(),
742
+ isStale: r.isStale === true
743
+ };
744
+ }
745
+ function inBrowser() {
746
+ try {
747
+ return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined" && typeof globalThis.location !== "undefined" && typeof globalThis.location.assign === "function";
748
+ } catch {
749
+ return false;
750
+ }
751
+ }
752
+ function performRedirect(url) {
753
+ try {
754
+ globalThis.location.assign(url);
755
+ } catch {
756
+ }
757
+ }
758
+ function createHttpCheckoutNamespace(config) {
759
+ return {
760
+ async start(input) {
761
+ if (!input?.productId) {
762
+ throw new NeetruError("validation_failed", "checkout.start: productId is required");
763
+ }
764
+ if (!input?.planId) {
765
+ throw new NeetruError("validation_failed", "checkout.start: planId is required");
766
+ }
767
+ if (!input?.callbackUrl) {
768
+ throw new NeetruError("validation_failed", "checkout.start: callbackUrl is required");
769
+ }
770
+ const body = {
771
+ productId: input.productId,
772
+ planId: input.planId,
773
+ callbackUrl: input.callbackUrl
774
+ };
775
+ if (input.tenantType) body.targetTenantType = input.tenantType;
776
+ if (input.tenantId) body.targetTenantId = input.tenantId;
777
+ const raw = await httpRequest(config, {
778
+ method: "POST",
779
+ path: "/api/v1/checkout/intents",
780
+ body,
781
+ requireAuth: true
782
+ });
783
+ const result = parseStartResponse(raw);
784
+ const shouldRedirect = input.autoRedirect !== false;
785
+ if (shouldRedirect && inBrowser()) {
786
+ performRedirect(result.redirectUrl);
787
+ }
788
+ return result;
789
+ },
790
+ async get(intentId) {
791
+ if (!intentId || typeof intentId !== "string") {
792
+ throw new NeetruError("validation_failed", "checkout.get: intentId is required");
793
+ }
794
+ const raw = await httpRequest(config, {
795
+ method: "GET",
796
+ path: `/api/v1/checkout/intents/${encodeURIComponent(intentId)}`,
797
+ requireAuth: true
798
+ });
799
+ return parseGetResponse(raw);
800
+ },
801
+ async cancel(intentId) {
802
+ if (!intentId || typeof intentId !== "string") {
803
+ throw new NeetruError("validation_failed", "checkout.cancel: intentId is required");
804
+ }
805
+ const raw = await httpRequest(config, {
806
+ method: "DELETE",
807
+ path: `/api/v1/checkout/intents/${encodeURIComponent(intentId)}`,
808
+ requireAuth: true
809
+ });
810
+ return {
811
+ ok: true,
812
+ alreadyCancelled: raw?.alreadyCancelled === true
813
+ };
814
+ }
815
+ };
816
+ }
817
+ var MockCheckout = class {
818
+ intents = /* @__PURE__ */ new Map();
819
+ async start(input) {
820
+ if (!input?.productId) {
821
+ throw new NeetruError("validation_failed", "checkout.start: productId is required");
822
+ }
823
+ if (!input?.planId) {
824
+ throw new NeetruError("validation_failed", "checkout.start: planId is required");
825
+ }
826
+ if (!input?.callbackUrl) {
827
+ throw new NeetruError("validation_failed", "checkout.start: callbackUrl is required");
828
+ }
829
+ const intentId = `chk_mock_${Math.random().toString(36).slice(2, 10)}`;
830
+ const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
831
+ const redirectUrl = `https://localhost:9003/portal/checkout/${intentId}`;
832
+ this.intents.set(intentId, {
833
+ intentId,
834
+ uid: "dev-fixture-uid",
835
+ targetTenantId: input.tenantId ?? "dev-fixture-uid",
836
+ targetTenantType: input.tenantType ?? "pf",
837
+ productId: input.productId,
838
+ planId: input.planId,
839
+ callbackUrl: input.callbackUrl,
840
+ status: "pending",
841
+ stripeSessionId: null,
842
+ expiresAt
843
+ });
844
+ return {
845
+ intentId,
846
+ redirectUrl,
847
+ status: "pending",
848
+ expiresAt,
849
+ requiresKyc: false
850
+ };
851
+ }
852
+ async get(intentId) {
853
+ const found = this.intents.get(intentId);
854
+ if (!found) {
855
+ throw new NeetruError("not_found", `Mock intent ${intentId} not found`);
856
+ }
857
+ return { ...found };
858
+ }
859
+ async cancel(intentId) {
860
+ const found = this.intents.get(intentId);
861
+ if (!found) {
862
+ throw new NeetruError("not_found", `Mock intent ${intentId} not found`);
863
+ }
864
+ const alreadyCancelled = found.status === "cancelled";
865
+ this.intents.set(intentId, { ...found, status: "cancelled" });
866
+ return { ok: true, alreadyCancelled };
867
+ }
868
+ };
869
+ function createCheckoutNamespace(config) {
870
+ if (config.env === "dev") return new MockCheckout();
871
+ return createHttpCheckoutNamespace(config);
872
+ }
873
+
695
874
  // src/mocks.ts
696
875
  var DEV_FIXTURE_USER = Object.freeze({
697
876
  uid: "dev-fixture-uid-0001",
@@ -1049,7 +1228,9 @@ function createOidcAuthNamespace(config) {
1049
1228
  const redirectUri = options?.redirectUri ?? globalThis.location.origin;
1050
1229
  const scope = options?.scope ?? "openid profile email";
1051
1230
  const state = options?.postLoginRedirect ?? globalThis.location.href;
1052
- const url = new URL("/oauth/authorize", config.baseUrl.replace(/\/api$/, ""));
1231
+ const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
1232
+ const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
1233
+ const url = new URL("/api/v1/oauth/authorize", idpOrigin);
1053
1234
  url.searchParams.set("response_type", "code");
1054
1235
  url.searchParams.set("redirect_uri", redirectUri);
1055
1236
  url.searchParams.set("scope", scope);
@@ -1127,7 +1308,8 @@ function createNeetruClient(config = {}) {
1127
1308
  telemetry: createTelemetryNamespace(resolved),
1128
1309
  usage,
1129
1310
  support,
1130
- db
1311
+ db,
1312
+ checkout: createCheckoutNamespace(resolved)
1131
1313
  });
1132
1314
  return client;
1133
1315
  }