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