@neetru/sdk 2.3.5 → 3.0.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/CHANGELOG.md +184 -0
- package/README.md +59 -22
- package/dist/auth.cjs +368 -142
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +368 -142
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +21 -2
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs +21 -2
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +21 -2
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +21 -2
- package/dist/checkout.mjs.map +1 -1
- package/dist/db.cjs +36 -5
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +36 -5
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +21 -2
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs +21 -2
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +3 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +432 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -19
- package/dist/index.d.ts +31 -19
- package/dist/index.mjs +432 -152
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +49 -37
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +23 -19
- package/dist/mocks.d.ts +23 -19
- package/dist/mocks.mjs +49 -37
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +80 -14
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs +80 -14
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.cjs +72 -12
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +72 -12
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +23 -4
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +23 -4
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-sGGN1vkg.d.cts → types-DLOvkeAP.d.cts} +110 -49
- package/dist/{types-CSC-RIdS.d.ts → types-dw19bdID.d.ts} +110 -49
- package/dist/usage.cjs +57 -63
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +57 -63
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +110 -33
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +110 -33
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,23 @@ var init_errors = __esm({
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
// src/idempotency.ts
|
|
35
|
+
function generateIdempotencyKey() {
|
|
36
|
+
const cryptoObj = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto : void 0;
|
|
37
|
+
if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
|
|
38
|
+
return cryptoObj.randomUUID();
|
|
39
|
+
}
|
|
40
|
+
throw new NeetruError(
|
|
41
|
+
"runtime_unsupported",
|
|
42
|
+
"crypto.randomUUID not available in this runtime. SDK 3.0 requires Node \u226520 or modern browser/Edge."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
var init_idempotency = __esm({
|
|
46
|
+
"src/idempotency.ts"() {
|
|
47
|
+
init_errors();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
34
51
|
// src/http.ts
|
|
35
52
|
var http_exports = {};
|
|
36
53
|
__export(http_exports, {
|
|
@@ -88,7 +105,8 @@ async function safeJson(res) {
|
|
|
88
105
|
async function httpRequest(config, opts) {
|
|
89
106
|
const method = opts.method ?? "GET";
|
|
90
107
|
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
91
|
-
const
|
|
108
|
+
const defaultRetries = READ_METHODS.has(method) ? DEFAULT_RETRIES_READ : DEFAULT_RETRIES_WRITE;
|
|
109
|
+
const maxRetries = opts.retries ?? defaultRetries;
|
|
92
110
|
const headers = {
|
|
93
111
|
accept: "application/json",
|
|
94
112
|
...opts.headers
|
|
@@ -102,6 +120,10 @@ async function httpRequest(config, opts) {
|
|
|
102
120
|
}
|
|
103
121
|
headers.authorization = `Bearer ${config.apiKey}`;
|
|
104
122
|
}
|
|
123
|
+
if (opts.idempotencyKey) {
|
|
124
|
+
const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : generateIdempotencyKey();
|
|
125
|
+
headers["idempotency-key"] = key;
|
|
126
|
+
}
|
|
105
127
|
const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
|
|
106
128
|
if (bodyString !== void 0) {
|
|
107
129
|
headers["content-type"] = "application/json";
|
|
@@ -153,11 +175,14 @@ async function httpRequest(config, opts) {
|
|
|
153
175
|
}
|
|
154
176
|
throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
|
|
155
177
|
}
|
|
156
|
-
var
|
|
178
|
+
var DEFAULT_RETRIES_READ, DEFAULT_RETRIES_WRITE, READ_METHODS, RETRYABLE_CODES;
|
|
157
179
|
var init_http = __esm({
|
|
158
180
|
"src/http.ts"() {
|
|
159
181
|
init_errors();
|
|
160
|
-
|
|
182
|
+
init_idempotency();
|
|
183
|
+
DEFAULT_RETRIES_READ = 2;
|
|
184
|
+
DEFAULT_RETRIES_WRITE = 0;
|
|
185
|
+
READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
161
186
|
RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
162
187
|
"rate_limited",
|
|
163
188
|
"server_error",
|
|
@@ -448,7 +473,10 @@ function parseLease(raw) {
|
|
|
448
473
|
};
|
|
449
474
|
}
|
|
450
475
|
async function createPgPool(lease) {
|
|
451
|
-
const { Pool } = await import(
|
|
476
|
+
const { Pool } = await import(
|
|
477
|
+
/* webpackIgnore: true */
|
|
478
|
+
'pg'
|
|
479
|
+
);
|
|
452
480
|
const ssl = lease.sslca ? {
|
|
453
481
|
ca: lease.sslca,
|
|
454
482
|
cert: lease.clientCert ?? void 0,
|
|
@@ -468,7 +496,10 @@ async function createPgPool(lease) {
|
|
|
468
496
|
});
|
|
469
497
|
}
|
|
470
498
|
async function createDrizzleHandle(pool, schema) {
|
|
471
|
-
const { drizzle } = await import(
|
|
499
|
+
const { drizzle } = await import(
|
|
500
|
+
/* webpackIgnore: true */
|
|
501
|
+
'drizzle-orm/node-postgres'
|
|
502
|
+
);
|
|
472
503
|
return drizzle(pool, { schema });
|
|
473
504
|
}
|
|
474
505
|
async function createSqlClientWithDeps(deps, options) {
|
|
@@ -568,6 +599,109 @@ var init_sql_client = __esm({
|
|
|
568
599
|
// src/auth.ts
|
|
569
600
|
init_errors();
|
|
570
601
|
|
|
602
|
+
// src/api-key.ts
|
|
603
|
+
init_errors();
|
|
604
|
+
var API_KEY_RE = /^nrt_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/;
|
|
605
|
+
function parseApiKey(apiKey) {
|
|
606
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
607
|
+
throw new NeetruError(
|
|
608
|
+
"invalid_config",
|
|
609
|
+
"apiKey is required (formato nrt_<keyId>_<secret>)"
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const match = API_KEY_RE.exec(apiKey);
|
|
613
|
+
if (!match) {
|
|
614
|
+
throw new NeetruError(
|
|
615
|
+
"invalid_config",
|
|
616
|
+
"apiKey malformado - esperado formato nrt_<keyId>_<secret>"
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return { keyId: match[1], secret: match[2] };
|
|
620
|
+
}
|
|
621
|
+
function tryParseApiKey(apiKey) {
|
|
622
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) return null;
|
|
623
|
+
const match = API_KEY_RE.exec(apiKey);
|
|
624
|
+
if (!match) return null;
|
|
625
|
+
return { keyId: match[1], secret: match[2] };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/base-url-allowlist.ts
|
|
629
|
+
init_errors();
|
|
630
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
631
|
+
var NEETRU_SUFFIX = ".neetru.com";
|
|
632
|
+
var NEETRU_APEX = "neetru.com";
|
|
633
|
+
var SAFE_DEV_HOST_RE = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\.(test|localhost)$/;
|
|
634
|
+
function isSafeDevHost(hostname) {
|
|
635
|
+
return SAFE_DEV_HOST_RE.test(hostname);
|
|
636
|
+
}
|
|
637
|
+
function normalizeHostname(hostname) {
|
|
638
|
+
return hostname.toLowerCase().replace(/\.+$/, "");
|
|
639
|
+
}
|
|
640
|
+
function validateBaseUrl(baseUrl, allowedHosts = []) {
|
|
641
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) {
|
|
642
|
+
throw new NeetruError("invalid_config", "baseUrl is required");
|
|
643
|
+
}
|
|
644
|
+
let parsed;
|
|
645
|
+
try {
|
|
646
|
+
parsed = new URL(baseUrl);
|
|
647
|
+
} catch {
|
|
648
|
+
throw new NeetruError("invalid_config", `baseUrl is not a valid URL: ${baseUrl}`);
|
|
649
|
+
}
|
|
650
|
+
const protocol = parsed.protocol;
|
|
651
|
+
const hostname = normalizeHostname(parsed.hostname);
|
|
652
|
+
if (isInsecureBypassActive()) {
|
|
653
|
+
if (typeof console !== "undefined") {
|
|
654
|
+
console.warn(
|
|
655
|
+
`[neetru-sdk] NEETRU_ALLOW_INSECURE_BASEURL=1 ativo. baseUrl=${baseUrl} aceito sem valida\xE7\xE3o.`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return baseUrl.replace(/\/+$/, "");
|
|
659
|
+
}
|
|
660
|
+
if (protocol !== "https:" && protocol !== "http:") {
|
|
661
|
+
throw new NeetruError(
|
|
662
|
+
"invalid_config",
|
|
663
|
+
`baseUrl protocol must be http: or https:, got ${protocol}`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (protocol === "http:") {
|
|
667
|
+
const isLoopback = LOOPBACK_HOSTS.has(hostname) || isSafeDevHost(hostname);
|
|
668
|
+
if (!isLoopback) {
|
|
669
|
+
throw new NeetruError(
|
|
670
|
+
"invalid_config",
|
|
671
|
+
`baseUrl with http: only allowed for localhost/127.0.0.1/*.test/*.localhost. Got: ${hostname}. Use https: or set NEETRU_ALLOW_INSECURE_BASEURL=1 to bypass.`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
return baseUrl.replace(/\/+$/, "");
|
|
675
|
+
}
|
|
676
|
+
const isNeetruDomain = hostname === NEETRU_APEX || hostname.endsWith(NEETRU_SUFFIX);
|
|
677
|
+
const isAllowedHost = allowedHosts.some((allowed) => {
|
|
678
|
+
const norm = allowed.toLowerCase();
|
|
679
|
+
return hostname === norm || hostname.endsWith(`.${norm}`);
|
|
680
|
+
});
|
|
681
|
+
const isLoopbackHttps = LOOPBACK_HOSTS.has(hostname) || isSafeDevHost(hostname);
|
|
682
|
+
if (!isNeetruDomain && !isAllowedHost && !isLoopbackHttps) {
|
|
683
|
+
throw new NeetruError(
|
|
684
|
+
"invalid_config",
|
|
685
|
+
`baseUrl ${hostname} is not in the allowlist. Allowed: *.neetru.com, localhost, or configure { allowedHosts: ['...'] }. Bypass: NEETRU_ALLOW_INSECURE_BASEURL=1.`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return baseUrl.replace(/\/+$/, "");
|
|
689
|
+
}
|
|
690
|
+
function isInsecureBypassActive() {
|
|
691
|
+
try {
|
|
692
|
+
const proc = globalThis.process;
|
|
693
|
+
const envVal = proc?.env?.NEETRU_ALLOW_INSECURE_BASEURL;
|
|
694
|
+
if (envVal === "1" || envVal === "true") return true;
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const g = globalThis.NEETRU_ALLOW_INSECURE_BASEURL;
|
|
699
|
+
if (g === "1" || g === "true" || g === true) return true;
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
|
|
571
705
|
// src/types.ts
|
|
572
706
|
var DEFAULT_BASE_URL = "https://api.neetru.com";
|
|
573
707
|
|
|
@@ -769,7 +903,7 @@ function createTelemetryNamespace(config) {
|
|
|
769
903
|
if (ev.timestamp) body.timestamp = ev.timestamp;
|
|
770
904
|
await httpRequest(config, {
|
|
771
905
|
method: "POST",
|
|
772
|
-
path: "/api/v1/
|
|
906
|
+
path: "/api/sdk/v1/telemetry/event",
|
|
773
907
|
body,
|
|
774
908
|
requireAuth: true,
|
|
775
909
|
retries: 1
|
|
@@ -818,7 +952,7 @@ function createTelemetryNamespace(config) {
|
|
|
818
952
|
if (input.timestamp) body.timestamp = input.timestamp;
|
|
819
953
|
const raw = await httpRequest(config, {
|
|
820
954
|
method: "POST",
|
|
821
|
-
path: "/api/v1/
|
|
955
|
+
path: "/api/sdk/v1/telemetry/event",
|
|
822
956
|
body,
|
|
823
957
|
requireAuth: true
|
|
824
958
|
});
|
|
@@ -930,65 +1064,33 @@ function createTelemetryNamespace(config) {
|
|
|
930
1064
|
// src/usage.ts
|
|
931
1065
|
init_errors();
|
|
932
1066
|
init_http();
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (typeof
|
|
939
|
-
throw new NeetruError(
|
|
1067
|
+
|
|
1068
|
+
// src/validators.ts
|
|
1069
|
+
init_errors();
|
|
1070
|
+
var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
1071
|
+
function assertValidSdkId(field, value) {
|
|
1072
|
+
if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
|
|
1073
|
+
throw new NeetruError(
|
|
1074
|
+
"validation_failed",
|
|
1075
|
+
`${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`
|
|
1076
|
+
);
|
|
940
1077
|
}
|
|
941
|
-
return {
|
|
942
|
-
metric: typeof r.metric === "string" ? r.metric : metric,
|
|
943
|
-
used: r.used,
|
|
944
|
-
limit: r.limit,
|
|
945
|
-
resetsAt: typeof r.resetsAt === "string" ? r.resetsAt : void 0,
|
|
946
|
-
plan: typeof r.plan === "string" ? r.plan : void 0
|
|
947
|
-
};
|
|
948
1078
|
}
|
|
1079
|
+
|
|
1080
|
+
// src/usage.ts
|
|
949
1081
|
function createUsageNamespace(config) {
|
|
950
1082
|
return {
|
|
951
1083
|
/**
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
const body = { event };
|
|
963
|
-
if (properties && typeof properties === "object") body.properties = properties;
|
|
964
|
-
const raw = await httpRequest(config, {
|
|
965
|
-
method: "POST",
|
|
966
|
-
path: "/sdk/v1/usage/record",
|
|
967
|
-
body,
|
|
968
|
-
requireAuth: true
|
|
969
|
-
});
|
|
970
|
-
if (!raw || raw.ok !== true) {
|
|
971
|
-
throw new NeetruError("invalid_response", "Usage record response missing ok");
|
|
972
|
-
}
|
|
973
|
-
return { ok: true };
|
|
974
|
-
},
|
|
975
|
-
/**
|
|
976
|
-
* Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.
|
|
977
|
-
*/
|
|
978
|
-
async getQuota(metric) {
|
|
979
|
-
if (!metric || typeof metric !== "string") {
|
|
980
|
-
throw new NeetruError("validation_failed", "metric is required");
|
|
981
|
-
}
|
|
982
|
-
const raw = await httpRequest(config, {
|
|
983
|
-
method: "GET",
|
|
984
|
-
path: "/sdk/v1/usage/quota",
|
|
985
|
-
query: { metric },
|
|
986
|
-
requireAuth: true
|
|
987
|
-
});
|
|
988
|
-
return toQuota(metric, raw);
|
|
989
|
-
},
|
|
990
|
-
/**
|
|
991
|
-
* v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.
|
|
1084
|
+
* Reporta consumo metered de um resource. Hit no endpoint canônico
|
|
1085
|
+
* `POST /api/sdk/v1/usage/record`. Idempotency-Key gerado por chamada;
|
|
1086
|
+
* Core dedup serverside TTL 24h.
|
|
1087
|
+
*
|
|
1088
|
+
* @example
|
|
1089
|
+
* ```ts
|
|
1090
|
+
* await client.usage.report('api_call', 1);
|
|
1091
|
+
* // ou explicit:
|
|
1092
|
+
* await client.usage.report('api_call', 5, { productId: 'gestovendas', tenantId: 't-acme' });
|
|
1093
|
+
* ```
|
|
992
1094
|
*/
|
|
993
1095
|
async report(resource, qty = 1, options) {
|
|
994
1096
|
if (!resource || typeof resource !== "string") {
|
|
@@ -1011,11 +1113,14 @@ function createUsageNamespace(config) {
|
|
|
1011
1113
|
"tenantId required (pass to options or set on createNeetruClient)"
|
|
1012
1114
|
);
|
|
1013
1115
|
}
|
|
1116
|
+
assertValidSdkId("productId", productId);
|
|
1117
|
+
assertValidSdkId("tenantId", tenantId);
|
|
1014
1118
|
const raw = await httpRequest(config, {
|
|
1015
1119
|
method: "POST",
|
|
1016
|
-
path: "/sdk/v1/usage/record",
|
|
1120
|
+
path: "/api/sdk/v1/usage/record",
|
|
1017
1121
|
body: { productId, tenantId, resource, qty: Math.floor(qty) },
|
|
1018
|
-
requireAuth: true
|
|
1122
|
+
requireAuth: true,
|
|
1123
|
+
idempotencyKey: true
|
|
1019
1124
|
});
|
|
1020
1125
|
if (!raw || raw.ok !== true) {
|
|
1021
1126
|
throw new NeetruError("invalid_response", "usage.report response missing ok");
|
|
@@ -1030,7 +1135,10 @@ function createUsageNamespace(config) {
|
|
|
1030
1135
|
};
|
|
1031
1136
|
},
|
|
1032
1137
|
/**
|
|
1033
|
-
*
|
|
1138
|
+
* Verifica entitlement de um resource via `GET /api/sdk/v1/entitlements`.
|
|
1139
|
+
* Resposta inclui `allowed`, `reason` ('granted' | 'not_subscribed' |
|
|
1140
|
+
* 'subscription_inactive' | 'feature_not_in_plan' | 'limit_exceeded'),
|
|
1141
|
+
* `remaining`, `limit`, `planId`, `planFeatures`.
|
|
1034
1142
|
*/
|
|
1035
1143
|
async check(resource, options) {
|
|
1036
1144
|
if (!resource || typeof resource !== "string") {
|
|
@@ -1041,25 +1149,29 @@ function createUsageNamespace(config) {
|
|
|
1041
1149
|
if (!productId || !tenantId) {
|
|
1042
1150
|
throw new NeetruError(
|
|
1043
1151
|
"validation_failed",
|
|
1044
|
-
"productId and tenantId required"
|
|
1152
|
+
"productId and tenantId required (pass to options or set on createNeetruClient)"
|
|
1045
1153
|
);
|
|
1046
1154
|
}
|
|
1155
|
+
assertValidSdkId("productId", productId);
|
|
1156
|
+
assertValidSdkId("tenantId", tenantId);
|
|
1047
1157
|
const raw = await httpRequest(config, {
|
|
1048
1158
|
method: "GET",
|
|
1049
|
-
path: "/sdk/v1/entitlements",
|
|
1159
|
+
path: "/api/sdk/v1/entitlements",
|
|
1050
1160
|
query: { productId, tenantId, feature: resource },
|
|
1051
1161
|
requireAuth: true
|
|
1052
1162
|
});
|
|
1053
1163
|
if (!raw || typeof raw.allowed !== "boolean") {
|
|
1054
1164
|
throw new NeetruError("invalid_response", "usage.check response missing allowed");
|
|
1055
1165
|
}
|
|
1166
|
+
const behavior = raw.behavior ?? (raw.reason === "limit_exceeded" ? "readonly" : null);
|
|
1056
1167
|
return {
|
|
1057
1168
|
allowed: raw.allowed,
|
|
1058
1169
|
reason: raw.reason,
|
|
1059
1170
|
remaining: raw.remaining,
|
|
1060
1171
|
limit: raw.limit,
|
|
1061
1172
|
planId: raw.planId,
|
|
1062
|
-
planFeatures: raw.planFeatures
|
|
1173
|
+
planFeatures: raw.planFeatures,
|
|
1174
|
+
behavior
|
|
1063
1175
|
};
|
|
1064
1176
|
}
|
|
1065
1177
|
};
|
|
@@ -1086,14 +1198,32 @@ function toTicket(raw) {
|
|
|
1086
1198
|
status: VALID_STATUSES.includes(r.status) ? r.status : "open",
|
|
1087
1199
|
createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1088
1200
|
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : void 0,
|
|
1089
|
-
productSlug: typeof r.productSlug === "string" ? r.productSlug : void 0
|
|
1201
|
+
productSlug: typeof r.productId === "string" ? r.productId : typeof r.productSlug === "string" ? r.productSlug : void 0
|
|
1090
1202
|
};
|
|
1091
1203
|
}
|
|
1204
|
+
function resolveProductId(config, ...candidates) {
|
|
1205
|
+
for (const c of candidates) {
|
|
1206
|
+
if (typeof c === "string" && c.length > 0) {
|
|
1207
|
+
assertValidSdkId("productSlug", c);
|
|
1208
|
+
return c;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (config.productId) {
|
|
1212
|
+
assertValidSdkId("productId", config.productId);
|
|
1213
|
+
return config.productId;
|
|
1214
|
+
}
|
|
1215
|
+
throw new NeetruError(
|
|
1216
|
+
"validation_failed",
|
|
1217
|
+
"productId required (pass to input.productSlug or set on createNeetruClient)"
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1092
1220
|
function createSupportNamespace(config) {
|
|
1093
1221
|
return {
|
|
1094
1222
|
/**
|
|
1095
|
-
* Cria um ticket de suporte. Requer Bearer auth.
|
|
1096
|
-
*
|
|
1223
|
+
* Cria um ticket de suporte. Requer Bearer auth. `productSlug` no input
|
|
1224
|
+
* (ou `config.productId` default) vai como `productId` no body do Core.
|
|
1225
|
+
*
|
|
1226
|
+
* Endpoint canonical: `POST /api/sdk/v1/support/tickets`.
|
|
1097
1227
|
*/
|
|
1098
1228
|
async createTicket(input) {
|
|
1099
1229
|
if (!input || typeof input !== "object") {
|
|
@@ -1112,30 +1242,42 @@ function createSupportNamespace(config) {
|
|
|
1112
1242
|
throw new NeetruError("validation_failed", "message max 10000 chars");
|
|
1113
1243
|
}
|
|
1114
1244
|
if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {
|
|
1115
|
-
throw new NeetruError(
|
|
1245
|
+
throw new NeetruError(
|
|
1246
|
+
"validation_failed",
|
|
1247
|
+
`severity must be one of ${VALID_SEVERITIES.join(", ")}`
|
|
1248
|
+
);
|
|
1116
1249
|
}
|
|
1117
|
-
const
|
|
1250
|
+
const productId = resolveProductId(config, input.productSlug);
|
|
1118
1251
|
const body = {
|
|
1252
|
+
productId,
|
|
1119
1253
|
subject: input.subject,
|
|
1120
1254
|
message: input.message,
|
|
1121
1255
|
severity: input.severity ?? "normal"
|
|
1122
1256
|
};
|
|
1257
|
+
if (input.metadata && typeof input.metadata === "object") {
|
|
1258
|
+
body.metadata = input.metadata;
|
|
1259
|
+
}
|
|
1123
1260
|
const raw = await httpRequest(config, {
|
|
1124
1261
|
method: "POST",
|
|
1125
|
-
path:
|
|
1262
|
+
path: "/api/sdk/v1/support/tickets",
|
|
1126
1263
|
body,
|
|
1127
|
-
requireAuth: true
|
|
1264
|
+
requireAuth: true,
|
|
1265
|
+
idempotencyKey: true
|
|
1128
1266
|
});
|
|
1129
1267
|
const candidate = raw && typeof raw === "object" && "ticket" in raw ? raw.ticket : raw;
|
|
1130
1268
|
return toTicket(candidate);
|
|
1131
1269
|
},
|
|
1132
1270
|
/**
|
|
1133
|
-
* Lista tickets do customer no produto atual
|
|
1271
|
+
* Lista tickets do customer no produto atual. `productSlug` no contexto.
|
|
1272
|
+
*
|
|
1273
|
+
* Endpoint canonical: `GET /api/sdk/v1/support/tickets?productId=...`.
|
|
1134
1274
|
*/
|
|
1135
|
-
async listMyTickets() {
|
|
1275
|
+
async listMyTickets(options) {
|
|
1276
|
+
const productId = resolveProductId(config, options?.productSlug);
|
|
1136
1277
|
const raw = await httpRequest(config, {
|
|
1137
1278
|
method: "GET",
|
|
1138
|
-
path: "/api/v1/
|
|
1279
|
+
path: "/api/sdk/v1/support/tickets",
|
|
1280
|
+
query: { productId },
|
|
1139
1281
|
requireAuth: true
|
|
1140
1282
|
});
|
|
1141
1283
|
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "tickets" in raw ? raw.tickets ?? [] : [];
|
|
@@ -4713,6 +4855,52 @@ function createCheckoutNamespace(config) {
|
|
|
4713
4855
|
// src/webhooks.ts
|
|
4714
4856
|
init_errors();
|
|
4715
4857
|
init_http();
|
|
4858
|
+
async function verifyWebhookSignature(payload, signature, secret) {
|
|
4859
|
+
if (!signature || !secret) return false;
|
|
4860
|
+
const sigHex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
4861
|
+
if (!/^[0-9a-f]+$/i.test(sigHex)) return false;
|
|
4862
|
+
const subtle = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto?.subtle : void 0;
|
|
4863
|
+
if (!subtle) {
|
|
4864
|
+
throw new NeetruError(
|
|
4865
|
+
"runtime_unsupported",
|
|
4866
|
+
"verifyWebhookSignature requires globalThis.crypto.subtle (Node \u226520, modern browser, or Edge runtime)."
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4869
|
+
const encoder = new TextEncoder();
|
|
4870
|
+
const keyBytes = encoder.encode(secret);
|
|
4871
|
+
const payloadBytes = typeof payload === "string" ? encoder.encode(payload) : payload;
|
|
4872
|
+
const key = await subtle.importKey(
|
|
4873
|
+
"raw",
|
|
4874
|
+
keyBytes,
|
|
4875
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
4876
|
+
false,
|
|
4877
|
+
["sign"]
|
|
4878
|
+
);
|
|
4879
|
+
const macBuffer = await subtle.sign(
|
|
4880
|
+
"HMAC",
|
|
4881
|
+
key,
|
|
4882
|
+
payloadBytes
|
|
4883
|
+
);
|
|
4884
|
+
const macBytes = new Uint8Array(macBuffer);
|
|
4885
|
+
const sigBytes = hexToBytes(sigHex.toLowerCase());
|
|
4886
|
+
if (!sigBytes || sigBytes.length !== macBytes.length) return false;
|
|
4887
|
+
let diff = 0;
|
|
4888
|
+
for (let i = 0; i < macBytes.length; i++) {
|
|
4889
|
+
diff |= macBytes[i] ^ sigBytes[i];
|
|
4890
|
+
}
|
|
4891
|
+
return diff === 0;
|
|
4892
|
+
}
|
|
4893
|
+
function hexToBytes(hex) {
|
|
4894
|
+
if (hex.length % 2 !== 0) return null;
|
|
4895
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
4896
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4897
|
+
const high = parseInt(hex[i * 2], 16);
|
|
4898
|
+
const low = parseInt(hex[i * 2 + 1], 16);
|
|
4899
|
+
if (Number.isNaN(high) || Number.isNaN(low)) return null;
|
|
4900
|
+
bytes[i] = high << 4 | low;
|
|
4901
|
+
}
|
|
4902
|
+
return bytes;
|
|
4903
|
+
}
|
|
4716
4904
|
var VALID_EVENTS = [
|
|
4717
4905
|
"subscription.activated",
|
|
4718
4906
|
"subscription.cancelled",
|
|
@@ -4768,42 +4956,71 @@ function validateInput(input) {
|
|
|
4768
4956
|
throw new NeetruError("validation_failed", "secret deve ter \u226516 chars (recomendado 32+)");
|
|
4769
4957
|
}
|
|
4770
4958
|
}
|
|
4959
|
+
function resolveProductId2(config, ...candidates) {
|
|
4960
|
+
for (const c of candidates) {
|
|
4961
|
+
if (typeof c === "string" && c.length > 0) {
|
|
4962
|
+
assertValidSdkId("productId", c);
|
|
4963
|
+
return c;
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
if (config.productId) {
|
|
4967
|
+
assertValidSdkId("productId", config.productId);
|
|
4968
|
+
return config.productId;
|
|
4969
|
+
}
|
|
4970
|
+
throw new NeetruError(
|
|
4971
|
+
"validation_failed",
|
|
4972
|
+
"productId required (pass to options/input or set on createNeetruClient)"
|
|
4973
|
+
);
|
|
4974
|
+
}
|
|
4771
4975
|
function createWebhooksNamespace(config) {
|
|
4772
4976
|
return {
|
|
4773
4977
|
async register(input) {
|
|
4774
4978
|
validateInput(input);
|
|
4979
|
+
const productId = resolveProductId2(config, input.productId);
|
|
4775
4980
|
const raw = await httpRequest(config, {
|
|
4776
4981
|
method: "POST",
|
|
4777
4982
|
path: "/api/sdk/v1/webhooks",
|
|
4778
|
-
body:
|
|
4983
|
+
body: {
|
|
4984
|
+
productId,
|
|
4985
|
+
url: input.url,
|
|
4986
|
+
events: input.events,
|
|
4987
|
+
...input.secret ? { secret: input.secret } : {}
|
|
4988
|
+
},
|
|
4779
4989
|
requireAuth: true
|
|
4780
4990
|
});
|
|
4781
4991
|
return toEndpoint(raw);
|
|
4782
4992
|
},
|
|
4783
|
-
async list() {
|
|
4993
|
+
async list(options) {
|
|
4994
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4784
4995
|
const raw = await httpRequest(config, {
|
|
4785
4996
|
method: "GET",
|
|
4786
4997
|
path: "/api/sdk/v1/webhooks",
|
|
4998
|
+
query: { productId },
|
|
4787
4999
|
requireAuth: true
|
|
4788
5000
|
});
|
|
4789
5001
|
const list = Array.isArray(raw?.endpoints) ? raw.endpoints : [];
|
|
4790
5002
|
return list.map(toEndpoint);
|
|
4791
5003
|
},
|
|
4792
|
-
async unregister(id) {
|
|
5004
|
+
async unregister(id, options) {
|
|
4793
5005
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5006
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4794
5007
|
await httpRequest(config, {
|
|
4795
5008
|
method: "DELETE",
|
|
4796
5009
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}`,
|
|
5010
|
+
query: { productId },
|
|
4797
5011
|
requireAuth: true
|
|
4798
5012
|
});
|
|
4799
5013
|
return { ok: true };
|
|
4800
5014
|
},
|
|
4801
|
-
async test(id) {
|
|
5015
|
+
async test(id, options) {
|
|
4802
5016
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5017
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4803
5018
|
const raw = await httpRequest(config, {
|
|
4804
5019
|
method: "POST",
|
|
4805
5020
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}/test`,
|
|
4806
|
-
|
|
5021
|
+
query: { productId },
|
|
5022
|
+
requireAuth: true,
|
|
5023
|
+
idempotencyKey: true
|
|
4807
5024
|
});
|
|
4808
5025
|
if (!raw || typeof raw !== "object") {
|
|
4809
5026
|
return { ok: false, error: "invalid response" };
|
|
@@ -4835,17 +5052,17 @@ var MockWebhooks = class {
|
|
|
4835
5052
|
this.endpoints.set(id, endpoint);
|
|
4836
5053
|
return endpoint;
|
|
4837
5054
|
}
|
|
4838
|
-
async list() {
|
|
5055
|
+
async list(_options) {
|
|
4839
5056
|
return [...this.endpoints.values()];
|
|
4840
5057
|
}
|
|
4841
|
-
async unregister(id) {
|
|
5058
|
+
async unregister(id, _options) {
|
|
4842
5059
|
if (!this.endpoints.has(id)) {
|
|
4843
5060
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4844
5061
|
}
|
|
4845
5062
|
this.endpoints.delete(id);
|
|
4846
5063
|
return { ok: true };
|
|
4847
5064
|
}
|
|
4848
|
-
async test(id) {
|
|
5065
|
+
async test(id, _options) {
|
|
4849
5066
|
if (!this.endpoints.has(id)) {
|
|
4850
5067
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4851
5068
|
}
|
|
@@ -4902,49 +5119,85 @@ function validateInput2(input) {
|
|
|
4902
5119
|
throw new NeetruError("validation_failed", "body m\xE1x 2000 chars");
|
|
4903
5120
|
}
|
|
4904
5121
|
}
|
|
5122
|
+
function resolveProductId3(config, ...candidates) {
|
|
5123
|
+
for (const c of candidates) {
|
|
5124
|
+
if (typeof c === "string" && c.length > 0) {
|
|
5125
|
+
assertValidSdkId("productId", c);
|
|
5126
|
+
return c;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
if (config.productId) {
|
|
5130
|
+
assertValidSdkId("productId", config.productId);
|
|
5131
|
+
return config.productId;
|
|
5132
|
+
}
|
|
5133
|
+
throw new NeetruError(
|
|
5134
|
+
"validation_failed",
|
|
5135
|
+
"productId required (pass to options/input or set on createNeetruClient)"
|
|
5136
|
+
);
|
|
5137
|
+
}
|
|
4905
5138
|
function createNotificationsNamespace(config) {
|
|
4906
5139
|
return {
|
|
4907
5140
|
async send(input) {
|
|
4908
5141
|
validateInput2(input);
|
|
5142
|
+
const productId = resolveProductId3(config, input.productId);
|
|
5143
|
+
const body = {
|
|
5144
|
+
productId,
|
|
5145
|
+
userId: input.userId,
|
|
5146
|
+
kind: input.kind,
|
|
5147
|
+
title: input.title,
|
|
5148
|
+
severity: input.severity ?? "info"
|
|
5149
|
+
};
|
|
5150
|
+
if (input.body !== void 0) body.body = input.body;
|
|
5151
|
+
if (input.link !== void 0) body.link = input.link;
|
|
5152
|
+
if (input.metadata !== void 0) body.metadata = input.metadata;
|
|
5153
|
+
if (input.fingerprint !== void 0) body.fingerprint = input.fingerprint;
|
|
4909
5154
|
const raw = await httpRequest(config, {
|
|
4910
5155
|
method: "POST",
|
|
4911
5156
|
path: "/api/sdk/v1/notifications",
|
|
4912
|
-
body
|
|
4913
|
-
requireAuth: true
|
|
5157
|
+
body,
|
|
5158
|
+
requireAuth: true,
|
|
5159
|
+
idempotencyKey: true
|
|
4914
5160
|
});
|
|
4915
5161
|
return toNotification(raw);
|
|
4916
5162
|
},
|
|
4917
5163
|
async list(userId, options) {
|
|
4918
5164
|
if (!userId) throw new NeetruError("validation_failed", "userId obrigat\xF3rio");
|
|
4919
|
-
const
|
|
4920
|
-
|
|
4921
|
-
|
|
5165
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
5166
|
+
const query = {
|
|
5167
|
+
productId
|
|
5168
|
+
};
|
|
5169
|
+
if (options?.includeDismissed) query.includeDismissed = true;
|
|
5170
|
+
if (options?.onlyUnread) query.onlyUnread = true;
|
|
4922
5171
|
if (options?.limit) {
|
|
4923
|
-
|
|
5172
|
+
query.limit = Math.min(Math.max(1, options.limit), 200);
|
|
4924
5173
|
}
|
|
4925
|
-
const qs = params.toString();
|
|
4926
5174
|
const raw = await httpRequest(config, {
|
|
4927
5175
|
method: "GET",
|
|
4928
|
-
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}
|
|
5176
|
+
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}`,
|
|
5177
|
+
query,
|
|
4929
5178
|
requireAuth: true
|
|
4930
5179
|
});
|
|
4931
5180
|
const list = Array.isArray(raw?.notifications) ? raw.notifications : [];
|
|
4932
5181
|
return list.map(toNotification);
|
|
4933
5182
|
},
|
|
4934
|
-
async markRead(id) {
|
|
5183
|
+
async markRead(id, options) {
|
|
4935
5184
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5185
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4936
5186
|
await httpRequest(config, {
|
|
4937
5187
|
method: "POST",
|
|
4938
5188
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/read`,
|
|
5189
|
+
query: { productId },
|
|
4939
5190
|
requireAuth: true
|
|
4940
5191
|
});
|
|
4941
5192
|
return { ok: true };
|
|
4942
5193
|
},
|
|
4943
|
-
async dismiss(id) {
|
|
5194
|
+
async dismiss(id, options) {
|
|
4944
5195
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5196
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4945
5197
|
await httpRequest(config, {
|
|
4946
5198
|
method: "POST",
|
|
4947
5199
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/dismiss`,
|
|
5200
|
+
query: { productId },
|
|
4948
5201
|
requireAuth: true
|
|
4949
5202
|
});
|
|
4950
5203
|
return { ok: true };
|
|
@@ -4985,7 +5238,7 @@ var MockNotifications = class {
|
|
|
4985
5238
|
const limit = Math.min(Math.max(1, options?.limit ?? 50), 200);
|
|
4986
5239
|
return [...this.notifications.values()].filter((n) => n.userId === userId).filter((n) => options?.includeDismissed || !n.dismissedAt).filter((n) => !options?.onlyUnread || !n.readAt).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, limit);
|
|
4987
5240
|
}
|
|
4988
|
-
async markRead(id) {
|
|
5241
|
+
async markRead(id, _options) {
|
|
4989
5242
|
const n = this.notifications.get(id);
|
|
4990
5243
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
4991
5244
|
if (!n.readAt) {
|
|
@@ -4994,7 +5247,7 @@ var MockNotifications = class {
|
|
|
4994
5247
|
}
|
|
4995
5248
|
return { ok: true };
|
|
4996
5249
|
}
|
|
4997
|
-
async dismiss(id) {
|
|
5250
|
+
async dismiss(id, _options) {
|
|
4998
5251
|
const n = this.notifications.get(id);
|
|
4999
5252
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
5000
5253
|
if (!n.dismissedAt) {
|
|
@@ -5007,6 +5260,7 @@ var MockNotifications = class {
|
|
|
5007
5260
|
|
|
5008
5261
|
// src/mocks.ts
|
|
5009
5262
|
init_db_errors();
|
|
5263
|
+
init_errors();
|
|
5010
5264
|
var DEV_FIXTURE_USER = Object.freeze({
|
|
5011
5265
|
uid: "dev-fixture-uid-0001",
|
|
5012
5266
|
email: "dev@neetru.local",
|
|
@@ -5114,39 +5368,25 @@ var MockAuth = class {
|
|
|
5114
5368
|
}
|
|
5115
5369
|
};
|
|
5116
5370
|
var MockUsage = class {
|
|
5117
|
-
|
|
5371
|
+
_reports = [];
|
|
5118
5372
|
_quotas;
|
|
5119
|
-
/** v0.3 — counters in-memory pra `report()` / `check()`. */
|
|
5120
5373
|
_counters = /* @__PURE__ */ new Map();
|
|
5121
5374
|
constructor(initialQuotas = {}) {
|
|
5122
5375
|
this._quotas = new Map(Object.entries(initialQuotas));
|
|
5123
5376
|
}
|
|
5124
|
-
async
|
|
5125
|
-
this._records.push({
|
|
5126
|
-
event,
|
|
5127
|
-
properties,
|
|
5128
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5129
|
-
});
|
|
5130
|
-
return { ok: true };
|
|
5131
|
-
}
|
|
5132
|
-
async getQuota(metric) {
|
|
5133
|
-
const existing = this._quotas.get(metric);
|
|
5134
|
-
if (existing) return existing;
|
|
5135
|
-
return {
|
|
5136
|
-
metric,
|
|
5137
|
-
used: this._records.filter((r) => r.event === metric).length,
|
|
5138
|
-
limit: -1,
|
|
5139
|
-
// unlimited em mock por default
|
|
5140
|
-
plan: "mock"
|
|
5141
|
-
};
|
|
5142
|
-
}
|
|
5143
|
-
/** v0.3 — Mock report incrementa o counter local. */
|
|
5144
|
-
async report(resource, qty = 1, _options) {
|
|
5377
|
+
async report(resource, qty = 1, options) {
|
|
5145
5378
|
if (!resource) throw new Error("resource required");
|
|
5146
5379
|
const safeQty = Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1;
|
|
5147
5380
|
const existing = this._counters.get(resource) ?? 0;
|
|
5148
5381
|
const next = existing + safeQty;
|
|
5149
5382
|
this._counters.set(resource, next);
|
|
5383
|
+
this._reports.push({
|
|
5384
|
+
resource,
|
|
5385
|
+
qty: safeQty,
|
|
5386
|
+
productId: options?.productId,
|
|
5387
|
+
tenantId: options?.tenantId,
|
|
5388
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5389
|
+
});
|
|
5150
5390
|
const limitFixture = this._quotas.get(resource);
|
|
5151
5391
|
const limit = limitFixture?.limit ?? -1;
|
|
5152
5392
|
return {
|
|
@@ -5158,12 +5398,11 @@ var MockUsage = class {
|
|
|
5158
5398
|
status: limit > 0 && next >= limit ? "read_only_overlimit" : "ok"
|
|
5159
5399
|
};
|
|
5160
5400
|
}
|
|
5161
|
-
/** v0.3 — Mock check usa quotas + counters in-memory. */
|
|
5162
5401
|
async check(resource, _options) {
|
|
5163
5402
|
const quota = this._quotas.get(resource);
|
|
5164
5403
|
const used = this._counters.get(resource) ?? 0;
|
|
5165
5404
|
if (!quota) {
|
|
5166
|
-
return { allowed: true, reason: "granted", limit: -1, remaining: -1 };
|
|
5405
|
+
return { allowed: true, reason: "granted", limit: -1, remaining: -1, behavior: null };
|
|
5167
5406
|
}
|
|
5168
5407
|
const remaining = quota.limit < 0 ? -1 : Math.max(0, quota.limit - used);
|
|
5169
5408
|
if (remaining === 0) {
|
|
@@ -5171,19 +5410,25 @@ var MockUsage = class {
|
|
|
5171
5410
|
allowed: false,
|
|
5172
5411
|
reason: "limit_exceeded",
|
|
5173
5412
|
limit: quota.limit,
|
|
5174
|
-
remaining: 0
|
|
5413
|
+
remaining: 0,
|
|
5414
|
+
behavior: "readonly"
|
|
5175
5415
|
};
|
|
5176
5416
|
}
|
|
5177
5417
|
return {
|
|
5178
5418
|
allowed: true,
|
|
5179
5419
|
reason: "granted",
|
|
5180
5420
|
limit: quota.limit,
|
|
5181
|
-
remaining
|
|
5421
|
+
remaining,
|
|
5422
|
+
behavior: null
|
|
5182
5423
|
};
|
|
5183
5424
|
}
|
|
5184
|
-
/** Test helper. */
|
|
5425
|
+
/** Test helper — retorna histórico de `report()` calls. */
|
|
5426
|
+
__getReports() {
|
|
5427
|
+
return [...this._reports];
|
|
5428
|
+
}
|
|
5429
|
+
/** @deprecated Removido em 3.0 (usage.track foi removido). Use `__getReports`. */
|
|
5185
5430
|
__getRecords() {
|
|
5186
|
-
return
|
|
5431
|
+
return this.__getReports();
|
|
5187
5432
|
}
|
|
5188
5433
|
/** Test helper — substitui quota fixture. */
|
|
5189
5434
|
__setQuota(metric, quota) {
|
|
@@ -5191,7 +5436,7 @@ var MockUsage = class {
|
|
|
5191
5436
|
}
|
|
5192
5437
|
/** Test helper — limpa estado. */
|
|
5193
5438
|
__reset() {
|
|
5194
|
-
this.
|
|
5439
|
+
this._reports = [];
|
|
5195
5440
|
this._quotas.clear();
|
|
5196
5441
|
this._counters.clear();
|
|
5197
5442
|
}
|
|
@@ -5200,8 +5445,14 @@ var MockSupport = class {
|
|
|
5200
5445
|
_tickets = [];
|
|
5201
5446
|
_ticketSeq = 0;
|
|
5202
5447
|
async createTicket(input) {
|
|
5203
|
-
if (!input?.subject) throw new
|
|
5204
|
-
if (!input?.message) throw new
|
|
5448
|
+
if (!input?.subject) throw new NeetruError("validation_failed", "subject required");
|
|
5449
|
+
if (!input?.message) throw new NeetruError("validation_failed", "message required");
|
|
5450
|
+
if (!input?.productSlug) {
|
|
5451
|
+
throw new NeetruError(
|
|
5452
|
+
"validation_failed",
|
|
5453
|
+
"productSlug required (3.0 breaking \u2014 pass to input or set on createNeetruClient)"
|
|
5454
|
+
);
|
|
5455
|
+
}
|
|
5205
5456
|
const ticket = {
|
|
5206
5457
|
id: `mock-ticket-${++this._ticketSeq}`,
|
|
5207
5458
|
subject: input.subject,
|
|
@@ -5214,7 +5465,10 @@ var MockSupport = class {
|
|
|
5214
5465
|
this._tickets.push(ticket);
|
|
5215
5466
|
return ticket;
|
|
5216
5467
|
}
|
|
5217
|
-
async listMyTickets() {
|
|
5468
|
+
async listMyTickets(options) {
|
|
5469
|
+
if (options?.productSlug) {
|
|
5470
|
+
return this._tickets.filter((t) => t.productSlug === options.productSlug);
|
|
5471
|
+
}
|
|
5218
5472
|
return [...this._tickets];
|
|
5219
5473
|
}
|
|
5220
5474
|
/** Test helper. */
|
|
@@ -5457,8 +5711,14 @@ var MockDb = class {
|
|
|
5457
5711
|
async sql(schema, options) {
|
|
5458
5712
|
const ddls = options?.initSql ? Array.isArray(options.initSql) ? options.initSql : [options.initSql] : [];
|
|
5459
5713
|
try {
|
|
5460
|
-
const pglitePkg = await import(
|
|
5461
|
-
|
|
5714
|
+
const pglitePkg = await import(
|
|
5715
|
+
/* webpackIgnore: true */
|
|
5716
|
+
'@electric-sql/pglite'
|
|
5717
|
+
);
|
|
5718
|
+
const drizzlePglitePkg = await import(
|
|
5719
|
+
/* webpackIgnore: true */
|
|
5720
|
+
'drizzle-orm/pglite'
|
|
5721
|
+
);
|
|
5462
5722
|
const pglite = await pglitePkg.PGlite.create();
|
|
5463
5723
|
for (const ddl of ddls) {
|
|
5464
5724
|
await pglite.query(ddl);
|
|
@@ -5479,8 +5739,14 @@ var MockDb = class {
|
|
|
5479
5739
|
let pgMemMod;
|
|
5480
5740
|
let drizzlePg;
|
|
5481
5741
|
try {
|
|
5482
|
-
pgMemMod = await import(
|
|
5483
|
-
|
|
5742
|
+
pgMemMod = await import(
|
|
5743
|
+
/* webpackIgnore: true */
|
|
5744
|
+
'pg-mem'
|
|
5745
|
+
);
|
|
5746
|
+
drizzlePg = await import(
|
|
5747
|
+
/* webpackIgnore: true */
|
|
5748
|
+
'drizzle-orm/node-postgres'
|
|
5749
|
+
);
|
|
5484
5750
|
} catch {
|
|
5485
5751
|
throw new NeetruDbError(
|
|
5486
5752
|
"db_unavailable",
|
|
@@ -5671,8 +5937,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5671
5937
|
url.searchParams.set("scope", scope);
|
|
5672
5938
|
url.searchParams.set("state", state);
|
|
5673
5939
|
if (config.apiKey) {
|
|
5674
|
-
const
|
|
5675
|
-
url.searchParams.set("client_id",
|
|
5940
|
+
const { keyId } = parseApiKey(config.apiKey);
|
|
5941
|
+
url.searchParams.set("client_id", keyId);
|
|
5676
5942
|
}
|
|
5677
5943
|
globalThis.location.assign(url.toString());
|
|
5678
5944
|
return;
|
|
@@ -5686,14 +5952,30 @@ function createOidcAuthNamespace(config) {
|
|
|
5686
5952
|
const storage = getStorage();
|
|
5687
5953
|
if (storage) storage.removeItem(STORAGE_KEY);
|
|
5688
5954
|
cachedUser = null;
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5955
|
+
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
5956
|
+
const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
5957
|
+
const revokeUrl = `${idpOrigin}/api/v1/oauth/revoke`;
|
|
5958
|
+
let revokeError = null;
|
|
5959
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5960
|
+
try {
|
|
5961
|
+
await config.fetch(revokeUrl, {
|
|
5962
|
+
method: "POST",
|
|
5963
|
+
headers: { "content-type": "application/json" }
|
|
5964
|
+
});
|
|
5965
|
+
revokeError = null;
|
|
5966
|
+
break;
|
|
5967
|
+
} catch (err) {
|
|
5968
|
+
revokeError = err;
|
|
5969
|
+
if (attempt === 0) {
|
|
5970
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
if (revokeError && typeof console !== "undefined") {
|
|
5975
|
+
console.warn(
|
|
5976
|
+
"[neetru-sdk] signOut: server-side revoke falhou (2 tentativas). Refresh token pode estar stale.",
|
|
5977
|
+
revokeError
|
|
5978
|
+
);
|
|
5697
5979
|
}
|
|
5698
5980
|
notify();
|
|
5699
5981
|
},
|
|
@@ -5719,7 +6001,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5719
6001
|
const issuer = idpOrigin;
|
|
5720
6002
|
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
5721
6003
|
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
5722
|
-
const
|
|
6004
|
+
const parsedKey = tryParseApiKey(config.apiKey);
|
|
6005
|
+
const audience = parsedKey?.keyId;
|
|
5723
6006
|
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
5724
6007
|
try {
|
|
5725
6008
|
const { payload } = await jwtVerify(token, jwks, {
|
|
@@ -5759,7 +6042,8 @@ function createNeetruClient(config = {}) {
|
|
|
5759
6042
|
"fetch is not available in this runtime. Pass `fetch` explicitly to createNeetruClient."
|
|
5760
6043
|
);
|
|
5761
6044
|
}
|
|
5762
|
-
const
|
|
6045
|
+
const rawBaseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
6046
|
+
const baseUrl = validateBaseUrl(rawBaseUrl, config.allowedHosts ?? []);
|
|
5763
6047
|
const apiKey = config.apiKey ?? readEnvApiKey();
|
|
5764
6048
|
const env = resolveEnv(config.env);
|
|
5765
6049
|
const resolved = Object.freeze({
|
|
@@ -5797,12 +6081,8 @@ function createNeetruClient(config = {}) {
|
|
|
5797
6081
|
// src/index.ts
|
|
5798
6082
|
init_errors();
|
|
5799
6083
|
init_db_errors();
|
|
5800
|
-
var VERSION = "
|
|
5801
|
-
function initNeetru(config) {
|
|
5802
|
-
const { apiUrl: _apiUrl, baseUrl, ...rest } = config;
|
|
5803
|
-
return createNeetruClient({ ...rest, baseUrl });
|
|
5804
|
-
}
|
|
6084
|
+
var VERSION = "3.0.0";
|
|
5805
6085
|
|
|
5806
|
-
export { DEFAULT_BASE_URL, DEV_FIXTURE_USER, MockAuth, MockCheckout, MockDb, MockEntitlements, MockNotifications, MockSupport, MockUsage, MockWebhooks, NeetruDbError, NeetruError, VERSION, createCheckoutNamespace, createNeetruClient, createNeetruDb, createNotificationsNamespace, createWebhooksNamespace,
|
|
6086
|
+
export { DEFAULT_BASE_URL, DEV_FIXTURE_USER, MockAuth, MockCheckout, MockDb, MockEntitlements, MockNotifications, MockSupport, MockUsage, MockWebhooks, NeetruDbError, NeetruError, VERSION, createCheckoutNamespace, createNeetruClient, createNeetruDb, createNotificationsNamespace, createWebhooksNamespace, verifyWebhookSignature };
|
|
5807
6087
|
//# sourceMappingURL=index.mjs.map
|
|
5808
6088
|
//# sourceMappingURL=index.mjs.map
|