@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/dist/mocks.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { j as NeetruUser, A as AuthNamespace, S as SignInOptions, b as AuthStateListener, g as DbNamespace, e as DbCollectionRef, E as EntitlementCheck, m as SupportNamespace, d as CreateTicketInput, p as SupportTicket, r as UsageNamespace, s as UsageQuota } from './types-PKUaFtBY.cjs';
1
+ import { p as NeetruUser, A as AuthNamespace, S as SignInOptions, b as AuthStateListener, m as DbNamespace, k as DbCollectionRef, E as EntitlementCheck, s as SupportNamespace, j as CreateTicketInput, v as SupportTicket, x as UsageNamespace, y as UsageQuota } from './types-BA53dd8S.cjs';
2
2
 
3
3
  /**
4
4
  * Mocks determinísticos do SDK — overridáveis em tests do consumer ou
package/dist/mocks.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { j as NeetruUser, A as AuthNamespace, S as SignInOptions, b as AuthStateListener, g as DbNamespace, e as DbCollectionRef, E as EntitlementCheck, m as SupportNamespace, d as CreateTicketInput, p as SupportTicket, r as UsageNamespace, s as UsageQuota } from './types-PKUaFtBY.js';
1
+ import { p as NeetruUser, A as AuthNamespace, S as SignInOptions, b as AuthStateListener, m as DbNamespace, k as DbCollectionRef, E as EntitlementCheck, s as SupportNamespace, j as CreateTicketInput, v as SupportTicket, x as UsageNamespace, y as UsageQuota } from './types-BA53dd8S.js';
2
2
 
3
3
  /**
4
4
  * Mocks determinísticos do SDK — overridáveis em tests do consumer ou
package/dist/react.cjs ADDED
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ function _interopNamespace(e) {
6
+ if (e && e.__esModule) return e;
7
+ var n = Object.create(null);
8
+ if (e) {
9
+ Object.keys(e).forEach(function (k) {
10
+ if (k !== 'default') {
11
+ var d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: function () { return e[k]; }
15
+ });
16
+ }
17
+ });
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+
23
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
24
+
25
+ // src/react.ts
26
+ function CheckoutLink({
27
+ client,
28
+ productId,
29
+ planId,
30
+ callbackUrl,
31
+ tenantType,
32
+ tenantId,
33
+ children,
34
+ onIntentCreated,
35
+ onError,
36
+ noAutoRedirect,
37
+ ...rest
38
+ }) {
39
+ const [submitting, setSubmitting] = React__namespace.useState(false);
40
+ const checkout = client.checkout;
41
+ const handleClick = React__namespace.useCallback(
42
+ async (event) => {
43
+ event.preventDefault();
44
+ if (submitting) return;
45
+ if (!checkout || typeof checkout.start !== "function") {
46
+ onError?.(new Error("SDK client missing `checkout` namespace. Upgrade @neetru/sdk to 1.1+."));
47
+ return;
48
+ }
49
+ setSubmitting(true);
50
+ try {
51
+ const result = await checkout.start({
52
+ productId,
53
+ planId,
54
+ callbackUrl,
55
+ tenantType,
56
+ tenantId,
57
+ autoRedirect: !noAutoRedirect
58
+ });
59
+ onIntentCreated?.(result);
60
+ } catch (err) {
61
+ onError?.(err instanceof Error ? err : new Error(String(err)));
62
+ } finally {
63
+ setSubmitting(false);
64
+ }
65
+ },
66
+ [submitting, checkout, productId, planId, callbackUrl, tenantType, tenantId, noAutoRedirect, onIntentCreated, onError]
67
+ );
68
+ return React__namespace.createElement(
69
+ "a",
70
+ {
71
+ ...rest,
72
+ href: rest.href ?? "#",
73
+ onClick: handleClick,
74
+ "aria-busy": submitting || void 0,
75
+ "data-neetru-checkout-link": "true"
76
+ },
77
+ children
78
+ );
79
+ }
80
+ var EntitlementContext = React__namespace.createContext({
81
+ readonly: false
82
+ });
83
+ function useEntitlementContext() {
84
+ return React__namespace.useContext(EntitlementContext);
85
+ }
86
+ function EntitlementGate({
87
+ client,
88
+ feature,
89
+ productSlug,
90
+ mode = "block",
91
+ fallback,
92
+ loading,
93
+ children
94
+ }) {
95
+ const [state, setState] = React__namespace.useState({
96
+ status: "loading"
97
+ });
98
+ const resolvedSlug = productSlug ?? client.config.productId ?? "";
99
+ React__namespace.useEffect(() => {
100
+ let cancelled = false;
101
+ const run = async () => {
102
+ try {
103
+ let result2;
104
+ const usage = client.usage;
105
+ if (usage && typeof usage.check === "function") {
106
+ const res = await usage.check(feature, { productId: resolvedSlug });
107
+ result2 = {
108
+ allowed: res.allowed === true,
109
+ reason: res.reason,
110
+ limit: res.limit,
111
+ currentUsage: res.currentUsage,
112
+ behavior: res.behavior
113
+ };
114
+ } else {
115
+ const allowed2 = await client.entitlements.check(resolvedSlug, feature);
116
+ result2 = { allowed: allowed2 };
117
+ }
118
+ if (!cancelled) setState({ status: "ready", result: result2 });
119
+ } catch (err) {
120
+ if (!cancelled) {
121
+ setState({ status: "ready", result: { allowed: false, reason: "check_failed" } });
122
+ }
123
+ if (typeof console !== "undefined") console.warn("[EntitlementGate] check failed", err);
124
+ }
125
+ };
126
+ run();
127
+ return () => {
128
+ cancelled = true;
129
+ };
130
+ }, [client, feature, resolvedSlug]);
131
+ if (state.status === "loading") {
132
+ return React__namespace.createElement(React__namespace.Fragment, null, loading ?? null);
133
+ }
134
+ const result = state.result;
135
+ const allowed = result?.allowed === true;
136
+ const isReadonly = !allowed && (mode === "readonly" || result?.behavior === "readonly");
137
+ if (!allowed && mode === "block" && !isReadonly) {
138
+ return React__namespace.createElement(React__namespace.Fragment, null, fallback ?? null);
139
+ }
140
+ return React__namespace.createElement(
141
+ EntitlementContext.Provider,
142
+ {
143
+ value: {
144
+ readonly: isReadonly,
145
+ reason: result?.reason,
146
+ limit: result?.limit,
147
+ currentUsage: result?.currentUsage
148
+ }
149
+ },
150
+ children
151
+ );
152
+ }
153
+
154
+ exports.CheckoutLink = CheckoutLink;
155
+ exports.EntitlementGate = EntitlementGate;
156
+ exports.useEntitlementContext = useEntitlementContext;
157
+ //# sourceMappingURL=react.cjs.map
158
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts"],"names":["React","result","allowed"],"mappings":"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","file":"react.cjs","sourcesContent":["/**\n * SDK React helpers (v1.1) — subpath import `@neetru/sdk/react`.\n *\n * Helpers leves pra React 18+/19. Não puxa `react` como dep direta — caller\n * fornece via peer (alinhado com `@stripe/react-stripe-js` / `firebase`).\n *\n * Exports:\n * - `<CheckoutLink>` — wrapper de `<a>` que chama `client.checkout.start()` no click.\n * - `<EntitlementGate>` — wrapper que gateia children por entitlement check\n * (suporte a `mode='readonly'` pro decisão CEO §5).\n * - `useEntitlementContext()` — hook pra ler estado de readonly do contexto.\n *\n * **Bundle**: este file SÓ é carregado por quem importa `@neetru/sdk/react`.\n * Tree-shake garante que SDK consumers Node-only (CLI, edge handlers) não\n * puxam React mesmo via transitive.\n */\nimport * as React from 'react';\nimport type { NeetruClient } from './types';\nimport type { CheckoutStartInput, CheckoutStartResult } from './checkout';\n\n// ─── <CheckoutLink> ─────────────────────────────────────────────────────────\n\nexport interface CheckoutLinkProps\n extends Omit<\n React.AnchorHTMLAttributes<HTMLAnchorElement>,\n 'href' | 'onClick' | 'onError'\n > {\n /** Cliente SDK Neetru. Passado explicitamente pra evitar context provider. */\n client: NeetruClient;\n /** Slug do produto (ex: `neetru-pulse`). */\n productId: string;\n /** Plan id (ex: `pro_monthly`). */\n planId: string;\n /** Pra onde o produto SaaS quer voltar pós-checkout. */\n callbackUrl: string;\n /** Override pra checkout em nome de uma org. Default: PF do uid logado. */\n tenantType?: 'pf' | 'pj';\n tenantId?: string;\n /** Children — texto/JSX do CTA. */\n children: React.ReactNode;\n /** Callback quando intent foi criada (antes de redirect). */\n onIntentCreated?: (result: CheckoutStartResult) => void;\n /** Callback de erro. */\n onError?: (err: Error) => void;\n /** Quando true, NÃO redireciona automaticamente — caller controla. */\n noAutoRedirect?: boolean;\n /** Override de href (default: `#`). */\n href?: string;\n}\n\n/**\n * Wrapper de `<a>` que dispara `client.checkout.start()` no click.\n *\n * Uso típico:\n * ```tsx\n * <CheckoutLink\n * client={client}\n * productId=\"meu-saas\"\n * planId=\"pro_monthly\"\n * callbackUrl=\"https://meu-saas.com/billing/success\"\n * >\n * Assinar Pro\n * </CheckoutLink>\n * ```\n *\n * `href` é stub (`#`) — preventDefault no click. Mantido pra a11y / preview\n * de URL no hover (pode ser sobrescrito via prop spread).\n */\nexport function CheckoutLink({\n client,\n productId,\n planId,\n callbackUrl,\n tenantType,\n tenantId,\n children,\n onIntentCreated,\n onError,\n noAutoRedirect,\n ...rest\n}: CheckoutLinkProps): React.ReactElement {\n const [submitting, setSubmitting] = React.useState(false);\n\n // Lazy access — `client.checkout` foi adicionado em SDK 1.1, fall back gracioso\n // se caller passar um client antigo (mesmo que types resolvam, runtime check).\n const checkout = (client as { checkout?: { start: (input: CheckoutStartInput) => Promise<CheckoutStartResult> } }).checkout;\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n if (submitting) return;\n if (!checkout || typeof checkout.start !== 'function') {\n onError?.(new Error('SDK client missing `checkout` namespace. Upgrade @neetru/sdk to 1.1+.'));\n return;\n }\n setSubmitting(true);\n try {\n const result = await checkout.start({\n productId,\n planId,\n callbackUrl,\n tenantType,\n tenantId,\n autoRedirect: !noAutoRedirect,\n });\n onIntentCreated?.(result);\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setSubmitting(false);\n }\n },\n [submitting, checkout, productId, planId, callbackUrl, tenantType, tenantId, noAutoRedirect, onIntentCreated, onError],\n );\n\n return React.createElement(\n 'a',\n {\n ...rest,\n href: rest.href ?? '#',\n onClick: handleClick,\n 'aria-busy': submitting || undefined,\n 'data-neetru-checkout-link': 'true',\n },\n children,\n );\n}\n\n// ─── <EntitlementGate> + useEntitlementContext ────────────────────────────\n\n/** Modo de gate quando entitlement não é permitido. */\nexport type EntitlementGateMode = 'block' | 'readonly';\n\ninterface EntitlementContextValue {\n /** True quando feature está em modo read-only (limit exceeded). */\n readonly: boolean;\n /** Reason code da última check. */\n reason?: string;\n /** Limite atual (free tier). */\n limit?: number;\n /** Uso atual. */\n currentUsage?: number;\n}\n\nconst EntitlementContext = React.createContext<EntitlementContextValue>({\n readonly: false,\n});\n\n/** Hook pra ler estado de readonly. Componentes filhos consomem isso pra\n * desabilitar inputs/botões de escrita. */\nexport function useEntitlementContext(): EntitlementContextValue {\n return React.useContext(EntitlementContext);\n}\n\nexport interface EntitlementGateProps {\n /** Cliente SDK Neetru. */\n client: NeetruClient;\n /** Feature key a checar (ex: `export.csv`). */\n feature: string;\n /** Slug do produto. Default: lê do client.config se ausente. */\n productSlug?: string;\n /** Modo:\n * - `block` (default): renderiza fallback quando não permitido.\n * - `readonly`: sempre renderiza children mas injeta `readonly: true` no contexto. */\n mode?: EntitlementGateMode;\n /** Fallback UI quando `mode='block'` e check retorna `allowed=false`. */\n fallback?: React.ReactNode;\n /** Loading UI enquanto check resolve (Suspense-like). */\n loading?: React.ReactNode;\n children: React.ReactNode;\n}\n\ninterface EntitlementCheckResult {\n allowed: boolean;\n reason?: string;\n limit?: number;\n currentUsage?: number;\n behavior?: 'readonly' | null;\n}\n\n/**\n * Wrapper que gateia children por entitlement check.\n *\n * Quando `mode='readonly'` + check retorna `behavior='readonly'`:\n * - Children renderizam normalmente.\n * - Context injeta `{readonly: true}`.\n * - Componentes filhos chamam `useEntitlementContext()` pra desabilitar\n * escritas (botões, forms, inputs).\n *\n * Quando `mode='block'` + check retorna `allowed=false`:\n * - `fallback` é renderizado (ex: paywall, upsell modal).\n *\n * Decisão CEO §5 (overage): plataforma usa `readonly` sempre — produto SaaS\n * deve preferir `mode='readonly'` pra preservar engajamento (não hard-block).\n */\nexport function EntitlementGate({\n client,\n feature,\n productSlug,\n mode = 'block',\n fallback,\n loading,\n children,\n}: EntitlementGateProps): React.ReactElement {\n const [state, setState] = React.useState<{ status: 'loading' | 'ready'; result?: EntitlementCheckResult }>({\n status: 'loading',\n });\n\n // Resolve productSlug from prop or client.config\n const resolvedSlug =\n productSlug ?? (client.config as { productId?: string }).productId ?? '';\n\n React.useEffect(() => {\n let cancelled = false;\n const run = async () => {\n try {\n // Try usage.check first (returns full {allowed, reason, behavior, ...})\n // Falls back to entitlements.check (just boolean) if usage.check unavailable.\n let result: EntitlementCheckResult;\n const usage = (client as { usage?: { check: (resource: string, opts?: { productId?: string }) => Promise<EntitlementCheckResult> } }).usage;\n if (usage && typeof usage.check === 'function') {\n const res = await usage.check(feature, { productId: resolvedSlug });\n result = {\n allowed: res.allowed === true,\n reason: res.reason,\n limit: res.limit,\n currentUsage: res.currentUsage,\n behavior: res.behavior,\n };\n } else {\n const allowed = await client.entitlements.check(resolvedSlug, feature);\n result = { allowed };\n }\n if (!cancelled) setState({ status: 'ready', result });\n } catch (err) {\n // Fail open in dev/prod — caller pode decidir via onError pattern futuro.\n if (!cancelled) {\n setState({ status: 'ready', result: { allowed: false, reason: 'check_failed' } });\n }\n if (typeof console !== 'undefined') console.warn('[EntitlementGate] check failed', err);\n }\n };\n run();\n return () => {\n cancelled = true;\n };\n }, [client, feature, resolvedSlug]);\n\n if (state.status === 'loading') {\n return React.createElement(React.Fragment, null, loading ?? null);\n }\n\n const result = state.result;\n const allowed = result?.allowed === true;\n const isReadonly = !allowed && (mode === 'readonly' || result?.behavior === 'readonly');\n\n if (!allowed && mode === 'block' && !isReadonly) {\n return React.createElement(React.Fragment, null, fallback ?? null);\n }\n\n return React.createElement(\n EntitlementContext.Provider,\n {\n value: {\n readonly: isReadonly,\n reason: result?.reason,\n limit: result?.limit,\n currentUsage: result?.currentUsage,\n },\n },\n children,\n );\n}\n"]}
@@ -0,0 +1,112 @@
1
+ import * as React from 'react';
2
+ import { a as NeetruClient, h as CheckoutStartResult } from './types-BA53dd8S.cjs';
3
+
4
+ /**
5
+ * SDK React helpers (v1.1) — subpath import `@neetru/sdk/react`.
6
+ *
7
+ * Helpers leves pra React 18+/19. Não puxa `react` como dep direta — caller
8
+ * fornece via peer (alinhado com `@stripe/react-stripe-js` / `firebase`).
9
+ *
10
+ * Exports:
11
+ * - `<CheckoutLink>` — wrapper de `<a>` que chama `client.checkout.start()` no click.
12
+ * - `<EntitlementGate>` — wrapper que gateia children por entitlement check
13
+ * (suporte a `mode='readonly'` pro decisão CEO §5).
14
+ * - `useEntitlementContext()` — hook pra ler estado de readonly do contexto.
15
+ *
16
+ * **Bundle**: este file SÓ é carregado por quem importa `@neetru/sdk/react`.
17
+ * Tree-shake garante que SDK consumers Node-only (CLI, edge handlers) não
18
+ * puxam React mesmo via transitive.
19
+ */
20
+
21
+ interface CheckoutLinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'onClick' | 'onError'> {
22
+ /** Cliente SDK Neetru. Passado explicitamente pra evitar context provider. */
23
+ client: NeetruClient;
24
+ /** Slug do produto (ex: `neetru-pulse`). */
25
+ productId: string;
26
+ /** Plan id (ex: `pro_monthly`). */
27
+ planId: string;
28
+ /** Pra onde o produto SaaS quer voltar pós-checkout. */
29
+ callbackUrl: string;
30
+ /** Override pra checkout em nome de uma org. Default: PF do uid logado. */
31
+ tenantType?: 'pf' | 'pj';
32
+ tenantId?: string;
33
+ /** Children — texto/JSX do CTA. */
34
+ children: React.ReactNode;
35
+ /** Callback quando intent foi criada (antes de redirect). */
36
+ onIntentCreated?: (result: CheckoutStartResult) => void;
37
+ /** Callback de erro. */
38
+ onError?: (err: Error) => void;
39
+ /** Quando true, NÃO redireciona automaticamente — caller controla. */
40
+ noAutoRedirect?: boolean;
41
+ /** Override de href (default: `#`). */
42
+ href?: string;
43
+ }
44
+ /**
45
+ * Wrapper de `<a>` que dispara `client.checkout.start()` no click.
46
+ *
47
+ * Uso típico:
48
+ * ```tsx
49
+ * <CheckoutLink
50
+ * client={client}
51
+ * productId="meu-saas"
52
+ * planId="pro_monthly"
53
+ * callbackUrl="https://meu-saas.com/billing/success"
54
+ * >
55
+ * Assinar Pro
56
+ * </CheckoutLink>
57
+ * ```
58
+ *
59
+ * `href` é stub (`#`) — preventDefault no click. Mantido pra a11y / preview
60
+ * de URL no hover (pode ser sobrescrito via prop spread).
61
+ */
62
+ declare function CheckoutLink({ client, productId, planId, callbackUrl, tenantType, tenantId, children, onIntentCreated, onError, noAutoRedirect, ...rest }: CheckoutLinkProps): React.ReactElement;
63
+ /** Modo de gate quando entitlement não é permitido. */
64
+ type EntitlementGateMode = 'block' | 'readonly';
65
+ interface EntitlementContextValue {
66
+ /** True quando feature está em modo read-only (limit exceeded). */
67
+ readonly: boolean;
68
+ /** Reason code da última check. */
69
+ reason?: string;
70
+ /** Limite atual (free tier). */
71
+ limit?: number;
72
+ /** Uso atual. */
73
+ currentUsage?: number;
74
+ }
75
+ /** Hook pra ler estado de readonly. Componentes filhos consomem isso pra
76
+ * desabilitar inputs/botões de escrita. */
77
+ declare function useEntitlementContext(): EntitlementContextValue;
78
+ interface EntitlementGateProps {
79
+ /** Cliente SDK Neetru. */
80
+ client: NeetruClient;
81
+ /** Feature key a checar (ex: `export.csv`). */
82
+ feature: string;
83
+ /** Slug do produto. Default: lê do client.config se ausente. */
84
+ productSlug?: string;
85
+ /** Modo:
86
+ * - `block` (default): renderiza fallback quando não permitido.
87
+ * - `readonly`: sempre renderiza children mas injeta `readonly: true` no contexto. */
88
+ mode?: EntitlementGateMode;
89
+ /** Fallback UI quando `mode='block'` e check retorna `allowed=false`. */
90
+ fallback?: React.ReactNode;
91
+ /** Loading UI enquanto check resolve (Suspense-like). */
92
+ loading?: React.ReactNode;
93
+ children: React.ReactNode;
94
+ }
95
+ /**
96
+ * Wrapper que gateia children por entitlement check.
97
+ *
98
+ * Quando `mode='readonly'` + check retorna `behavior='readonly'`:
99
+ * - Children renderizam normalmente.
100
+ * - Context injeta `{readonly: true}`.
101
+ * - Componentes filhos chamam `useEntitlementContext()` pra desabilitar
102
+ * escritas (botões, forms, inputs).
103
+ *
104
+ * Quando `mode='block'` + check retorna `allowed=false`:
105
+ * - `fallback` é renderizado (ex: paywall, upsell modal).
106
+ *
107
+ * Decisão CEO §5 (overage): plataforma usa `readonly` sempre — produto SaaS
108
+ * deve preferir `mode='readonly'` pra preservar engajamento (não hard-block).
109
+ */
110
+ declare function EntitlementGate({ client, feature, productSlug, mode, fallback, loading, children, }: EntitlementGateProps): React.ReactElement;
111
+
112
+ export { CheckoutLink, type CheckoutLinkProps, EntitlementGate, type EntitlementGateMode, type EntitlementGateProps, useEntitlementContext };
@@ -0,0 +1,112 @@
1
+ import * as React from 'react';
2
+ import { a as NeetruClient, h as CheckoutStartResult } from './types-BA53dd8S.js';
3
+
4
+ /**
5
+ * SDK React helpers (v1.1) — subpath import `@neetru/sdk/react`.
6
+ *
7
+ * Helpers leves pra React 18+/19. Não puxa `react` como dep direta — caller
8
+ * fornece via peer (alinhado com `@stripe/react-stripe-js` / `firebase`).
9
+ *
10
+ * Exports:
11
+ * - `<CheckoutLink>` — wrapper de `<a>` que chama `client.checkout.start()` no click.
12
+ * - `<EntitlementGate>` — wrapper que gateia children por entitlement check
13
+ * (suporte a `mode='readonly'` pro decisão CEO §5).
14
+ * - `useEntitlementContext()` — hook pra ler estado de readonly do contexto.
15
+ *
16
+ * **Bundle**: este file SÓ é carregado por quem importa `@neetru/sdk/react`.
17
+ * Tree-shake garante que SDK consumers Node-only (CLI, edge handlers) não
18
+ * puxam React mesmo via transitive.
19
+ */
20
+
21
+ interface CheckoutLinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'onClick' | 'onError'> {
22
+ /** Cliente SDK Neetru. Passado explicitamente pra evitar context provider. */
23
+ client: NeetruClient;
24
+ /** Slug do produto (ex: `neetru-pulse`). */
25
+ productId: string;
26
+ /** Plan id (ex: `pro_monthly`). */
27
+ planId: string;
28
+ /** Pra onde o produto SaaS quer voltar pós-checkout. */
29
+ callbackUrl: string;
30
+ /** Override pra checkout em nome de uma org. Default: PF do uid logado. */
31
+ tenantType?: 'pf' | 'pj';
32
+ tenantId?: string;
33
+ /** Children — texto/JSX do CTA. */
34
+ children: React.ReactNode;
35
+ /** Callback quando intent foi criada (antes de redirect). */
36
+ onIntentCreated?: (result: CheckoutStartResult) => void;
37
+ /** Callback de erro. */
38
+ onError?: (err: Error) => void;
39
+ /** Quando true, NÃO redireciona automaticamente — caller controla. */
40
+ noAutoRedirect?: boolean;
41
+ /** Override de href (default: `#`). */
42
+ href?: string;
43
+ }
44
+ /**
45
+ * Wrapper de `<a>` que dispara `client.checkout.start()` no click.
46
+ *
47
+ * Uso típico:
48
+ * ```tsx
49
+ * <CheckoutLink
50
+ * client={client}
51
+ * productId="meu-saas"
52
+ * planId="pro_monthly"
53
+ * callbackUrl="https://meu-saas.com/billing/success"
54
+ * >
55
+ * Assinar Pro
56
+ * </CheckoutLink>
57
+ * ```
58
+ *
59
+ * `href` é stub (`#`) — preventDefault no click. Mantido pra a11y / preview
60
+ * de URL no hover (pode ser sobrescrito via prop spread).
61
+ */
62
+ declare function CheckoutLink({ client, productId, planId, callbackUrl, tenantType, tenantId, children, onIntentCreated, onError, noAutoRedirect, ...rest }: CheckoutLinkProps): React.ReactElement;
63
+ /** Modo de gate quando entitlement não é permitido. */
64
+ type EntitlementGateMode = 'block' | 'readonly';
65
+ interface EntitlementContextValue {
66
+ /** True quando feature está em modo read-only (limit exceeded). */
67
+ readonly: boolean;
68
+ /** Reason code da última check. */
69
+ reason?: string;
70
+ /** Limite atual (free tier). */
71
+ limit?: number;
72
+ /** Uso atual. */
73
+ currentUsage?: number;
74
+ }
75
+ /** Hook pra ler estado de readonly. Componentes filhos consomem isso pra
76
+ * desabilitar inputs/botões de escrita. */
77
+ declare function useEntitlementContext(): EntitlementContextValue;
78
+ interface EntitlementGateProps {
79
+ /** Cliente SDK Neetru. */
80
+ client: NeetruClient;
81
+ /** Feature key a checar (ex: `export.csv`). */
82
+ feature: string;
83
+ /** Slug do produto. Default: lê do client.config se ausente. */
84
+ productSlug?: string;
85
+ /** Modo:
86
+ * - `block` (default): renderiza fallback quando não permitido.
87
+ * - `readonly`: sempre renderiza children mas injeta `readonly: true` no contexto. */
88
+ mode?: EntitlementGateMode;
89
+ /** Fallback UI quando `mode='block'` e check retorna `allowed=false`. */
90
+ fallback?: React.ReactNode;
91
+ /** Loading UI enquanto check resolve (Suspense-like). */
92
+ loading?: React.ReactNode;
93
+ children: React.ReactNode;
94
+ }
95
+ /**
96
+ * Wrapper que gateia children por entitlement check.
97
+ *
98
+ * Quando `mode='readonly'` + check retorna `behavior='readonly'`:
99
+ * - Children renderizam normalmente.
100
+ * - Context injeta `{readonly: true}`.
101
+ * - Componentes filhos chamam `useEntitlementContext()` pra desabilitar
102
+ * escritas (botões, forms, inputs).
103
+ *
104
+ * Quando `mode='block'` + check retorna `allowed=false`:
105
+ * - `fallback` é renderizado (ex: paywall, upsell modal).
106
+ *
107
+ * Decisão CEO §5 (overage): plataforma usa `readonly` sempre — produto SaaS
108
+ * deve preferir `mode='readonly'` pra preservar engajamento (não hard-block).
109
+ */
110
+ declare function EntitlementGate({ client, feature, productSlug, mode, fallback, loading, children, }: EntitlementGateProps): React.ReactElement;
111
+
112
+ export { CheckoutLink, type CheckoutLinkProps, EntitlementGate, type EntitlementGateMode, type EntitlementGateProps, useEntitlementContext };
package/dist/react.mjs ADDED
@@ -0,0 +1,134 @@
1
+ import * as React from 'react';
2
+
3
+ // src/react.ts
4
+ function CheckoutLink({
5
+ client,
6
+ productId,
7
+ planId,
8
+ callbackUrl,
9
+ tenantType,
10
+ tenantId,
11
+ children,
12
+ onIntentCreated,
13
+ onError,
14
+ noAutoRedirect,
15
+ ...rest
16
+ }) {
17
+ const [submitting, setSubmitting] = React.useState(false);
18
+ const checkout = client.checkout;
19
+ const handleClick = React.useCallback(
20
+ async (event) => {
21
+ event.preventDefault();
22
+ if (submitting) return;
23
+ if (!checkout || typeof checkout.start !== "function") {
24
+ onError?.(new Error("SDK client missing `checkout` namespace. Upgrade @neetru/sdk to 1.1+."));
25
+ return;
26
+ }
27
+ setSubmitting(true);
28
+ try {
29
+ const result = await checkout.start({
30
+ productId,
31
+ planId,
32
+ callbackUrl,
33
+ tenantType,
34
+ tenantId,
35
+ autoRedirect: !noAutoRedirect
36
+ });
37
+ onIntentCreated?.(result);
38
+ } catch (err) {
39
+ onError?.(err instanceof Error ? err : new Error(String(err)));
40
+ } finally {
41
+ setSubmitting(false);
42
+ }
43
+ },
44
+ [submitting, checkout, productId, planId, callbackUrl, tenantType, tenantId, noAutoRedirect, onIntentCreated, onError]
45
+ );
46
+ return React.createElement(
47
+ "a",
48
+ {
49
+ ...rest,
50
+ href: rest.href ?? "#",
51
+ onClick: handleClick,
52
+ "aria-busy": submitting || void 0,
53
+ "data-neetru-checkout-link": "true"
54
+ },
55
+ children
56
+ );
57
+ }
58
+ var EntitlementContext = React.createContext({
59
+ readonly: false
60
+ });
61
+ function useEntitlementContext() {
62
+ return React.useContext(EntitlementContext);
63
+ }
64
+ function EntitlementGate({
65
+ client,
66
+ feature,
67
+ productSlug,
68
+ mode = "block",
69
+ fallback,
70
+ loading,
71
+ children
72
+ }) {
73
+ const [state, setState] = React.useState({
74
+ status: "loading"
75
+ });
76
+ const resolvedSlug = productSlug ?? client.config.productId ?? "";
77
+ React.useEffect(() => {
78
+ let cancelled = false;
79
+ const run = async () => {
80
+ try {
81
+ let result2;
82
+ const usage = client.usage;
83
+ if (usage && typeof usage.check === "function") {
84
+ const res = await usage.check(feature, { productId: resolvedSlug });
85
+ result2 = {
86
+ allowed: res.allowed === true,
87
+ reason: res.reason,
88
+ limit: res.limit,
89
+ currentUsage: res.currentUsage,
90
+ behavior: res.behavior
91
+ };
92
+ } else {
93
+ const allowed2 = await client.entitlements.check(resolvedSlug, feature);
94
+ result2 = { allowed: allowed2 };
95
+ }
96
+ if (!cancelled) setState({ status: "ready", result: result2 });
97
+ } catch (err) {
98
+ if (!cancelled) {
99
+ setState({ status: "ready", result: { allowed: false, reason: "check_failed" } });
100
+ }
101
+ if (typeof console !== "undefined") console.warn("[EntitlementGate] check failed", err);
102
+ }
103
+ };
104
+ run();
105
+ return () => {
106
+ cancelled = true;
107
+ };
108
+ }, [client, feature, resolvedSlug]);
109
+ if (state.status === "loading") {
110
+ return React.createElement(React.Fragment, null, loading ?? null);
111
+ }
112
+ const result = state.result;
113
+ const allowed = result?.allowed === true;
114
+ const isReadonly = !allowed && (mode === "readonly" || result?.behavior === "readonly");
115
+ if (!allowed && mode === "block" && !isReadonly) {
116
+ return React.createElement(React.Fragment, null, fallback ?? null);
117
+ }
118
+ return React.createElement(
119
+ EntitlementContext.Provider,
120
+ {
121
+ value: {
122
+ readonly: isReadonly,
123
+ reason: result?.reason,
124
+ limit: result?.limit,
125
+ currentUsage: result?.currentUsage
126
+ }
127
+ },
128
+ children
129
+ );
130
+ }
131
+
132
+ export { CheckoutLink, EntitlementGate, useEntitlementContext };
133
+ //# sourceMappingURL=react.mjs.map
134
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts"],"names":["result","allowed"],"mappings":"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","file":"react.mjs","sourcesContent":["/**\n * SDK React helpers (v1.1) — subpath import `@neetru/sdk/react`.\n *\n * Helpers leves pra React 18+/19. Não puxa `react` como dep direta — caller\n * fornece via peer (alinhado com `@stripe/react-stripe-js` / `firebase`).\n *\n * Exports:\n * - `<CheckoutLink>` — wrapper de `<a>` que chama `client.checkout.start()` no click.\n * - `<EntitlementGate>` — wrapper que gateia children por entitlement check\n * (suporte a `mode='readonly'` pro decisão CEO §5).\n * - `useEntitlementContext()` — hook pra ler estado de readonly do contexto.\n *\n * **Bundle**: este file SÓ é carregado por quem importa `@neetru/sdk/react`.\n * Tree-shake garante que SDK consumers Node-only (CLI, edge handlers) não\n * puxam React mesmo via transitive.\n */\nimport * as React from 'react';\nimport type { NeetruClient } from './types';\nimport type { CheckoutStartInput, CheckoutStartResult } from './checkout';\n\n// ─── <CheckoutLink> ─────────────────────────────────────────────────────────\n\nexport interface CheckoutLinkProps\n extends Omit<\n React.AnchorHTMLAttributes<HTMLAnchorElement>,\n 'href' | 'onClick' | 'onError'\n > {\n /** Cliente SDK Neetru. Passado explicitamente pra evitar context provider. */\n client: NeetruClient;\n /** Slug do produto (ex: `neetru-pulse`). */\n productId: string;\n /** Plan id (ex: `pro_monthly`). */\n planId: string;\n /** Pra onde o produto SaaS quer voltar pós-checkout. */\n callbackUrl: string;\n /** Override pra checkout em nome de uma org. Default: PF do uid logado. */\n tenantType?: 'pf' | 'pj';\n tenantId?: string;\n /** Children — texto/JSX do CTA. */\n children: React.ReactNode;\n /** Callback quando intent foi criada (antes de redirect). */\n onIntentCreated?: (result: CheckoutStartResult) => void;\n /** Callback de erro. */\n onError?: (err: Error) => void;\n /** Quando true, NÃO redireciona automaticamente — caller controla. */\n noAutoRedirect?: boolean;\n /** Override de href (default: `#`). */\n href?: string;\n}\n\n/**\n * Wrapper de `<a>` que dispara `client.checkout.start()` no click.\n *\n * Uso típico:\n * ```tsx\n * <CheckoutLink\n * client={client}\n * productId=\"meu-saas\"\n * planId=\"pro_monthly\"\n * callbackUrl=\"https://meu-saas.com/billing/success\"\n * >\n * Assinar Pro\n * </CheckoutLink>\n * ```\n *\n * `href` é stub (`#`) — preventDefault no click. Mantido pra a11y / preview\n * de URL no hover (pode ser sobrescrito via prop spread).\n */\nexport function CheckoutLink({\n client,\n productId,\n planId,\n callbackUrl,\n tenantType,\n tenantId,\n children,\n onIntentCreated,\n onError,\n noAutoRedirect,\n ...rest\n}: CheckoutLinkProps): React.ReactElement {\n const [submitting, setSubmitting] = React.useState(false);\n\n // Lazy access — `client.checkout` foi adicionado em SDK 1.1, fall back gracioso\n // se caller passar um client antigo (mesmo que types resolvam, runtime check).\n const checkout = (client as { checkout?: { start: (input: CheckoutStartInput) => Promise<CheckoutStartResult> } }).checkout;\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n if (submitting) return;\n if (!checkout || typeof checkout.start !== 'function') {\n onError?.(new Error('SDK client missing `checkout` namespace. Upgrade @neetru/sdk to 1.1+.'));\n return;\n }\n setSubmitting(true);\n try {\n const result = await checkout.start({\n productId,\n planId,\n callbackUrl,\n tenantType,\n tenantId,\n autoRedirect: !noAutoRedirect,\n });\n onIntentCreated?.(result);\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setSubmitting(false);\n }\n },\n [submitting, checkout, productId, planId, callbackUrl, tenantType, tenantId, noAutoRedirect, onIntentCreated, onError],\n );\n\n return React.createElement(\n 'a',\n {\n ...rest,\n href: rest.href ?? '#',\n onClick: handleClick,\n 'aria-busy': submitting || undefined,\n 'data-neetru-checkout-link': 'true',\n },\n children,\n );\n}\n\n// ─── <EntitlementGate> + useEntitlementContext ────────────────────────────\n\n/** Modo de gate quando entitlement não é permitido. */\nexport type EntitlementGateMode = 'block' | 'readonly';\n\ninterface EntitlementContextValue {\n /** True quando feature está em modo read-only (limit exceeded). */\n readonly: boolean;\n /** Reason code da última check. */\n reason?: string;\n /** Limite atual (free tier). */\n limit?: number;\n /** Uso atual. */\n currentUsage?: number;\n}\n\nconst EntitlementContext = React.createContext<EntitlementContextValue>({\n readonly: false,\n});\n\n/** Hook pra ler estado de readonly. Componentes filhos consomem isso pra\n * desabilitar inputs/botões de escrita. */\nexport function useEntitlementContext(): EntitlementContextValue {\n return React.useContext(EntitlementContext);\n}\n\nexport interface EntitlementGateProps {\n /** Cliente SDK Neetru. */\n client: NeetruClient;\n /** Feature key a checar (ex: `export.csv`). */\n feature: string;\n /** Slug do produto. Default: lê do client.config se ausente. */\n productSlug?: string;\n /** Modo:\n * - `block` (default): renderiza fallback quando não permitido.\n * - `readonly`: sempre renderiza children mas injeta `readonly: true` no contexto. */\n mode?: EntitlementGateMode;\n /** Fallback UI quando `mode='block'` e check retorna `allowed=false`. */\n fallback?: React.ReactNode;\n /** Loading UI enquanto check resolve (Suspense-like). */\n loading?: React.ReactNode;\n children: React.ReactNode;\n}\n\ninterface EntitlementCheckResult {\n allowed: boolean;\n reason?: string;\n limit?: number;\n currentUsage?: number;\n behavior?: 'readonly' | null;\n}\n\n/**\n * Wrapper que gateia children por entitlement check.\n *\n * Quando `mode='readonly'` + check retorna `behavior='readonly'`:\n * - Children renderizam normalmente.\n * - Context injeta `{readonly: true}`.\n * - Componentes filhos chamam `useEntitlementContext()` pra desabilitar\n * escritas (botões, forms, inputs).\n *\n * Quando `mode='block'` + check retorna `allowed=false`:\n * - `fallback` é renderizado (ex: paywall, upsell modal).\n *\n * Decisão CEO §5 (overage): plataforma usa `readonly` sempre — produto SaaS\n * deve preferir `mode='readonly'` pra preservar engajamento (não hard-block).\n */\nexport function EntitlementGate({\n client,\n feature,\n productSlug,\n mode = 'block',\n fallback,\n loading,\n children,\n}: EntitlementGateProps): React.ReactElement {\n const [state, setState] = React.useState<{ status: 'loading' | 'ready'; result?: EntitlementCheckResult }>({\n status: 'loading',\n });\n\n // Resolve productSlug from prop or client.config\n const resolvedSlug =\n productSlug ?? (client.config as { productId?: string }).productId ?? '';\n\n React.useEffect(() => {\n let cancelled = false;\n const run = async () => {\n try {\n // Try usage.check first (returns full {allowed, reason, behavior, ...})\n // Falls back to entitlements.check (just boolean) if usage.check unavailable.\n let result: EntitlementCheckResult;\n const usage = (client as { usage?: { check: (resource: string, opts?: { productId?: string }) => Promise<EntitlementCheckResult> } }).usage;\n if (usage && typeof usage.check === 'function') {\n const res = await usage.check(feature, { productId: resolvedSlug });\n result = {\n allowed: res.allowed === true,\n reason: res.reason,\n limit: res.limit,\n currentUsage: res.currentUsage,\n behavior: res.behavior,\n };\n } else {\n const allowed = await client.entitlements.check(resolvedSlug, feature);\n result = { allowed };\n }\n if (!cancelled) setState({ status: 'ready', result });\n } catch (err) {\n // Fail open in dev/prod — caller pode decidir via onError pattern futuro.\n if (!cancelled) {\n setState({ status: 'ready', result: { allowed: false, reason: 'check_failed' } });\n }\n if (typeof console !== 'undefined') console.warn('[EntitlementGate] check failed', err);\n }\n };\n run();\n return () => {\n cancelled = true;\n };\n }, [client, feature, resolvedSlug]);\n\n if (state.status === 'loading') {\n return React.createElement(React.Fragment, null, loading ?? null);\n }\n\n const result = state.result;\n const allowed = result?.allowed === true;\n const isReadonly = !allowed && (mode === 'readonly' || result?.behavior === 'readonly');\n\n if (!allowed && mode === 'block' && !isReadonly) {\n return React.createElement(React.Fragment, null, fallback ?? null);\n }\n\n return React.createElement(\n EntitlementContext.Provider,\n {\n value: {\n readonly: isReadonly,\n reason: result?.reason,\n limit: result?.limit,\n currentUsage: result?.currentUsage,\n },\n },\n children,\n );\n}\n"]}
package/dist/support.cjs CHANGED
@@ -67,10 +67,14 @@ async function httpRequest(config, opts) {
67
67
  headers["content-type"] = "application/json";
68
68
  init.body = JSON.stringify(opts.body);
69
69
  }
70
+ init.signal = AbortSignal.timeout(3e4);
70
71
  let res;
71
72
  try {
72
73
  res = await config.fetch(url, init);
73
74
  } catch (err) {
75
+ if (err instanceof DOMException && err.name === "TimeoutError") {
76
+ throw new NeetruError("network_error", "Network error: timeout after 30s");
77
+ }
74
78
  const message = err instanceof Error ? err.message : "fetch failed";
75
79
  throw new NeetruError("network_error", `Network error: ${message}`);
76
80
  }