@grantex/sdk 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/LICENSE +185 -185
  2. package/NOTICE +16 -16
  3. package/README.md +974 -947
  4. package/dist/authorization-details.d.ts +86 -0
  5. package/dist/authorization-details.d.ts.map +1 -0
  6. package/dist/authorization-details.js +209 -0
  7. package/dist/authorization-details.js.map +1 -0
  8. package/dist/canonical.d.ts +47 -0
  9. package/dist/canonical.d.ts.map +1 -0
  10. package/dist/canonical.js +163 -0
  11. package/dist/canonical.js.map +1 -0
  12. package/dist/caps/index.d.ts +19 -0
  13. package/dist/caps/index.d.ts.map +1 -0
  14. package/dist/caps/index.js +19 -0
  15. package/dist/caps/index.js.map +1 -0
  16. package/dist/caps/limits.d.ts +40 -0
  17. package/dist/caps/limits.d.ts.map +1 -0
  18. package/dist/caps/limits.js +109 -0
  19. package/dist/caps/limits.js.map +1 -0
  20. package/dist/caps/memory.d.ts +15 -0
  21. package/dist/caps/memory.d.ts.map +1 -0
  22. package/dist/caps/memory.js +57 -0
  23. package/dist/caps/memory.js.map +1 -0
  24. package/dist/caps/meter.d.ts +107 -0
  25. package/dist/caps/meter.d.ts.map +1 -0
  26. package/dist/caps/meter.js +200 -0
  27. package/dist/caps/meter.js.map +1 -0
  28. package/dist/caps/postgres.d.ts +64 -0
  29. package/dist/caps/postgres.d.ts.map +1 -0
  30. package/dist/caps/postgres.js +221 -0
  31. package/dist/caps/postgres.js.map +1 -0
  32. package/dist/caps/redis.d.ts +45 -0
  33. package/dist/caps/redis.d.ts.map +1 -0
  34. package/dist/caps/redis.js +173 -0
  35. package/dist/caps/redis.js.map +1 -0
  36. package/dist/client.d.ts +12 -0
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +517 -57
  39. package/dist/client.js.map +1 -1
  40. package/dist/decisions/action.d.ts +51 -0
  41. package/dist/decisions/action.d.ts.map +1 -0
  42. package/dist/decisions/action.js +237 -0
  43. package/dist/decisions/action.js.map +1 -0
  44. package/dist/decisions/index.d.ts +11 -0
  45. package/dist/decisions/index.d.ts.map +1 -0
  46. package/dist/decisions/index.js +8 -0
  47. package/dist/decisions/index.js.map +1 -0
  48. package/dist/decisions/verify.d.ts +103 -0
  49. package/dist/decisions/verify.d.ts.map +1 -0
  50. package/dist/decisions/verify.js +277 -0
  51. package/dist/decisions/verify.js.map +1 -0
  52. package/dist/denials.d.ts +132 -0
  53. package/dist/denials.d.ts.map +1 -0
  54. package/dist/denials.js +129 -0
  55. package/dist/denials.js.map +1 -0
  56. package/dist/evidence/build.d.ts +63 -0
  57. package/dist/evidence/build.d.ts.map +1 -0
  58. package/dist/evidence/build.js +201 -0
  59. package/dist/evidence/build.js.map +1 -0
  60. package/dist/evidence/canonical.d.ts +3 -0
  61. package/dist/evidence/canonical.d.ts.map +1 -0
  62. package/dist/evidence/canonical.js +3 -0
  63. package/dist/evidence/canonical.js.map +1 -0
  64. package/dist/evidence/checks.d.ts +13 -0
  65. package/dist/evidence/checks.d.ts.map +1 -0
  66. package/dist/evidence/checks.js +406 -0
  67. package/dist/evidence/checks.js.map +1 -0
  68. package/dist/evidence/document.d.ts +8 -0
  69. package/dist/evidence/document.d.ts.map +1 -0
  70. package/dist/evidence/document.js +298 -0
  71. package/dist/evidence/document.js.map +1 -0
  72. package/dist/evidence/hashing.d.ts +37 -0
  73. package/dist/evidence/hashing.d.ts.map +1 -0
  74. package/dist/evidence/hashing.js +106 -0
  75. package/dist/evidence/hashing.js.map +1 -0
  76. package/dist/evidence/index.d.ts +24 -0
  77. package/dist/evidence/index.d.ts.map +1 -0
  78. package/dist/evidence/index.js +24 -0
  79. package/dist/evidence/index.js.map +1 -0
  80. package/dist/evidence/result.d.ts +88 -0
  81. package/dist/evidence/result.d.ts.map +1 -0
  82. package/dist/evidence/result.js +88 -0
  83. package/dist/evidence/result.js.map +1 -0
  84. package/dist/evidence/schema-1.0.d.ts +2 -0
  85. package/dist/evidence/schema-1.0.d.ts.map +1 -0
  86. package/dist/evidence/schema-1.0.js +1398 -0
  87. package/dist/evidence/schema-1.0.js.map +1 -0
  88. package/dist/evidence/schema.d.ts +13 -0
  89. package/dist/evidence/schema.d.ts.map +1 -0
  90. package/dist/evidence/schema.js +264 -0
  91. package/dist/evidence/schema.js.map +1 -0
  92. package/dist/evidence/signature.d.ts +23 -0
  93. package/dist/evidence/signature.d.ts.map +1 -0
  94. package/dist/evidence/signature.js +136 -0
  95. package/dist/evidence/signature.js.map +1 -0
  96. package/dist/evidence/trace.d.ts +23 -0
  97. package/dist/evidence/trace.d.ts.map +1 -0
  98. package/dist/evidence/trace.js +69 -0
  99. package/dist/evidence/trace.js.map +1 -0
  100. package/dist/evidence/verify.d.ts +29 -0
  101. package/dist/evidence/verify.d.ts.map +1 -0
  102. package/dist/evidence/verify.js +163 -0
  103. package/dist/evidence/verify.js.map +1 -0
  104. package/dist/http.d.ts +12 -1
  105. package/dist/http.d.ts.map +1 -1
  106. package/dist/http.js +11 -5
  107. package/dist/http.js.map +1 -1
  108. package/dist/index.d.ts +16 -3
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +16 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/manifest.d.ts +178 -9
  113. package/dist/manifest.d.ts.map +1 -1
  114. package/dist/manifest.js +379 -16
  115. package/dist/manifest.js.map +1 -1
  116. package/dist/purpose.d.ts +29 -0
  117. package/dist/purpose.d.ts.map +1 -0
  118. package/dist/purpose.js +60 -0
  119. package/dist/purpose.js.map +1 -0
  120. package/dist/resources/agents.js +3 -3
  121. package/dist/resources/agents.js.map +1 -1
  122. package/dist/resources/anomalies.d.ts +3 -1
  123. package/dist/resources/anomalies.d.ts.map +1 -1
  124. package/dist/resources/anomalies.js +7 -1
  125. package/dist/resources/anomalies.js.map +1 -1
  126. package/dist/resources/audit.js +1 -1
  127. package/dist/resources/audit.js.map +1 -1
  128. package/dist/resources/budgets.js +2 -2
  129. package/dist/resources/budgets.js.map +1 -1
  130. package/dist/resources/credentials.js +1 -1
  131. package/dist/resources/credentials.js.map +1 -1
  132. package/dist/resources/decisions.d.ts +69 -0
  133. package/dist/resources/decisions.d.ts.map +1 -0
  134. package/dist/resources/decisions.js +115 -0
  135. package/dist/resources/decisions.js.map +1 -0
  136. package/dist/resources/domains.js +2 -2
  137. package/dist/resources/domains.js.map +1 -1
  138. package/dist/resources/dpdp.js +4 -4
  139. package/dist/resources/dpdp.js.map +1 -1
  140. package/dist/resources/grants.js +2 -2
  141. package/dist/resources/grants.js.map +1 -1
  142. package/dist/resources/policies.js +3 -3
  143. package/dist/resources/policies.js.map +1 -1
  144. package/dist/resources/scim.js +5 -5
  145. package/dist/resources/scim.js.map +1 -1
  146. package/dist/resources/tokens.d.ts.map +1 -1
  147. package/dist/resources/tokens.js +5 -1
  148. package/dist/resources/tokens.js.map +1 -1
  149. package/dist/resources/vault.js +2 -2
  150. package/dist/resources/vault.js.map +1 -1
  151. package/dist/resources/webauthn.d.ts +6 -1
  152. package/dist/resources/webauthn.d.ts.map +1 -1
  153. package/dist/resources/webauthn.js +5 -1
  154. package/dist/resources/webauthn.js.map +1 -1
  155. package/dist/resources/webhooks.js +1 -1
  156. package/dist/resources/webhooks.js.map +1 -1
  157. package/dist/revocations/feed.d.ts +61 -0
  158. package/dist/revocations/feed.d.ts.map +1 -0
  159. package/dist/revocations/feed.js +282 -0
  160. package/dist/revocations/feed.js.map +1 -0
  161. package/dist/revocations/index.d.ts +20 -0
  162. package/dist/revocations/index.d.ts.map +1 -0
  163. package/dist/revocations/index.js +19 -0
  164. package/dist/revocations/index.js.map +1 -0
  165. package/dist/revocations/set.d.ts +59 -0
  166. package/dist/revocations/set.d.ts.map +1 -0
  167. package/dist/revocations/set.js +87 -0
  168. package/dist/revocations/set.js.map +1 -0
  169. package/dist/scopes.d.ts +26 -0
  170. package/dist/scopes.d.ts.map +1 -0
  171. package/dist/scopes.js +37 -0
  172. package/dist/scopes.js.map +1 -0
  173. package/dist/types.d.ts +121 -4
  174. package/dist/types.d.ts.map +1 -1
  175. package/dist/types.js +0 -1
  176. package/dist/types.js.map +1 -1
  177. package/dist/verify.d.ts +31 -2
  178. package/dist/verify.d.ts.map +1 -1
  179. package/dist/verify.js +236 -29
  180. package/dist/verify.js.map +1 -1
  181. package/package.json +92 -84
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Postgres caps backend: row locks serialize reservations per counter.
3
+ *
4
+ * A reservation runs in one READ COMMITTED transaction: it makes sure a row
5
+ * exists per counter in `grantex_cap_counters`, locks those rows in a fixed
6
+ * order (so concurrent reservations cannot deadlock), prunes and sums the
7
+ * counter's reservations in `grantex_cap_reservations`, and inserts the new
8
+ * reservation only if every limit holds. Time comes from the database
9
+ * (`clock_timestamp()`) unless a clock is injected for tests. Same tables and
10
+ * statements as the Python SDK's `PostgresCapsBackend`.
11
+ *
12
+ * Rows carry a SHA-256-derived hash of the tenant id, not the tenant id, in
13
+ * `tenant_id`: isolation comes from that hash being part of every key and
14
+ * query, and row-level security policies written against plain tenant ids do
15
+ * not match these rows.
16
+ *
17
+ * Expired rows are deleted lazily when their counter is next reserved. Run
18
+ * `prune()` periodically (for example hourly) to delete expired reservations,
19
+ * per-case reservations older than `caseTtlSeconds` if one is set, and
20
+ * counters with no reservations left.
21
+ *
22
+ * `pool` is a `pg` Pool, or anything with the same `connect()` / `query()` /
23
+ * `release()` shape. Create the tables with `CAPS_SCHEMA_SQL` through your own
24
+ * migration tool, or `ensureSchema()`.
25
+ */
26
+ import { CapExceededError, WINDOW_MS, tenantHash } from './meter.js';
27
+ export const CAPS_SCHEMA_SQL = `
28
+ CREATE TABLE IF NOT EXISTS grantex_cap_counters (
29
+ tenant_id TEXT NOT NULL,
30
+ counter_key TEXT NOT NULL,
31
+ PRIMARY KEY (tenant_id, counter_key)
32
+ );
33
+
34
+ CREATE TABLE IF NOT EXISTS grantex_cap_reservations (
35
+ tenant_id TEXT NOT NULL,
36
+ counter_key TEXT NOT NULL,
37
+ reservation_id TEXT NOT NULL,
38
+ units BIGINT NOT NULL CHECK (units >= 0),
39
+ reserved_at TIMESTAMPTZ NOT NULL,
40
+ expires_at TIMESTAMPTZ,
41
+ PRIMARY KEY (tenant_id, counter_key, reservation_id),
42
+ FOREIGN KEY (tenant_id, counter_key)
43
+ REFERENCES grantex_cap_counters (tenant_id, counter_key)
44
+ );
45
+
46
+ ALTER TABLE grantex_cap_reservations ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_grantex_cap_reservations_time
49
+ ON grantex_cap_reservations (tenant_id, counter_key, reserved_at);
50
+
51
+ CREATE INDEX IF NOT EXISTS idx_grantex_cap_reservations_expiry
52
+ ON grantex_cap_reservations (expires_at)
53
+ WHERE expires_at IS NOT NULL;
54
+ `;
55
+ const NOW_SQL = 'SELECT CASE WHEN $1::bigint IS NULL THEN clock_timestamp() ELSE to_timestamp($1::bigint / 1000.0) END AS now';
56
+ const LOCK_ATTEMPTS = 3;
57
+ function int(value) {
58
+ const n = typeof value === 'number' ? value : typeof value === 'string' || typeof value === 'bigint' ? Number(value) : Number.NaN;
59
+ if (!Number.isSafeInteger(n))
60
+ throw new Error('database returned an invalid usage');
61
+ return n;
62
+ }
63
+ export class PostgresCapsBackend {
64
+ #pool;
65
+ #caseTtlSeconds;
66
+ /**
67
+ * `caseTtlSeconds` optionally lets `prune()` delete per-case reservations
68
+ * that old; by default they are kept, because deleting them would reset the
69
+ * per-case cap.
70
+ */
71
+ constructor(pool, options = {}) {
72
+ const ttl = options.caseTtlSeconds;
73
+ if (ttl !== undefined && (!Number.isInteger(ttl) || ttl <= 0)) {
74
+ throw new Error('caseTtlSeconds must be a positive integer');
75
+ }
76
+ this.#pool = pool;
77
+ this.#caseTtlSeconds = ttl;
78
+ }
79
+ async ensureSchema() {
80
+ const client = await this.#pool.connect();
81
+ try {
82
+ for (const statement of CAPS_SCHEMA_SQL.split(';').map((s) => s.trim()).filter(Boolean)) {
83
+ await client.query(statement);
84
+ }
85
+ }
86
+ finally {
87
+ client.release();
88
+ }
89
+ }
90
+ static async #now(client, nowMs) {
91
+ const { rows } = await client.query(NOW_SQL, [nowMs ?? null]);
92
+ const row = rows[0];
93
+ if (row === undefined)
94
+ throw new Error('database returned no time');
95
+ return row['now'];
96
+ }
97
+ static async #used(client, tenant, key, windowMs, now, prune) {
98
+ let rows;
99
+ if (windowMs > 0) {
100
+ if (prune) {
101
+ await client.query('DELETE FROM grantex_cap_reservations WHERE tenant_id = $1 AND counter_key = $2 ' +
102
+ 'AND reserved_at <= $3::timestamptz - make_interval(secs => $4::double precision)', [tenant, key, now, windowMs / 1000]);
103
+ }
104
+ ({ rows } = await client.query('SELECT COALESCE(SUM(units), 0) AS used FROM grantex_cap_reservations ' +
105
+ 'WHERE tenant_id = $1 AND counter_key = $2 AND reserved_at > $3::timestamptz - make_interval(secs => $4::double precision)', [tenant, key, now, windowMs / 1000]));
106
+ }
107
+ else {
108
+ ({ rows } = await client.query('SELECT COALESCE(SUM(units), 0) AS used FROM grantex_cap_reservations WHERE tenant_id = $1 AND counter_key = $2', [tenant, key]));
109
+ }
110
+ const row = rows[0];
111
+ if (row === undefined)
112
+ throw new Error('database returned no usage');
113
+ return int(row['used']);
114
+ }
115
+ #expirySeconds(limit) {
116
+ const window = WINDOW_MS[limit.window];
117
+ if (window > 0)
118
+ return window / 1000;
119
+ return this.#caseTtlSeconds ?? null;
120
+ }
121
+ /** Run `work` in a READ COMMITTED transaction; a connection whose rollback fails is destroyed. */
122
+ async #transaction(work) {
123
+ const client = await this.#pool.connect();
124
+ let releaseError;
125
+ try {
126
+ await client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
127
+ try {
128
+ const result = await work(client);
129
+ await client.query('COMMIT');
130
+ return result;
131
+ }
132
+ catch (err) {
133
+ try {
134
+ await client.query('ROLLBACK');
135
+ }
136
+ catch (rollbackError) {
137
+ // The original error propagates; the connection is not reused.
138
+ releaseError = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError));
139
+ }
140
+ throw err;
141
+ }
142
+ }
143
+ finally {
144
+ client.release(releaseError);
145
+ }
146
+ }
147
+ async reserve(tenantId, reservationId, limits, nowMs) {
148
+ const tenant = await tenantHash(tenantId);
149
+ const ordered = [...limits].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
150
+ await this.#transaction(async (client) => {
151
+ for (const limit of ordered) {
152
+ let locked = false;
153
+ for (let attempt = 0; attempt < LOCK_ATTEMPTS && !locked; attempt += 1) {
154
+ await client.query('INSERT INTO grantex_cap_counters (tenant_id, counter_key) VALUES ($1, $2) ON CONFLICT DO NOTHING', [tenant, limit.key]);
155
+ const { rows } = await client.query('SELECT counter_key FROM grantex_cap_counters WHERE tenant_id = $1 AND counter_key = $2 FOR UPDATE', [tenant, limit.key]);
156
+ // No row: prune() removed an empty counter between the two statements.
157
+ locked = rows.length === 1;
158
+ }
159
+ if (!locked)
160
+ throw new Error('caps counter row could not be locked');
161
+ }
162
+ // Read the time after the locks are held, so a reservation that waited
163
+ // is judged against the moment it actually runs.
164
+ const now = await PostgresCapsBackend.#now(client, nowMs);
165
+ for (const limit of ordered) {
166
+ const used = await PostgresCapsBackend.#used(client, tenant, limit.key, WINDOW_MS[limit.window], now, true);
167
+ if (used + limit.units > limit.limit) {
168
+ throw new CapExceededError({
169
+ limit: limit.limit, window: limit.window, used, requested: limit.units, scope: limit.scope, kind: limit.kind,
170
+ });
171
+ }
172
+ }
173
+ for (const limit of ordered) {
174
+ await client.query('INSERT INTO grantex_cap_reservations (tenant_id, counter_key, reservation_id, units, reserved_at, expires_at) ' +
175
+ 'VALUES ($1, $2, $3, $4, $5::timestamptz, $5::timestamptz + make_interval(secs => $6::double precision))', [tenant, limit.key, reservationId, limit.units, now, this.#expirySeconds(limit)]);
176
+ }
177
+ });
178
+ }
179
+ async refund(tenantId, reservationId, limits) {
180
+ const tenant = await tenantHash(tenantId);
181
+ const client = await this.#pool.connect();
182
+ try {
183
+ for (const limit of limits) {
184
+ await client.query('DELETE FROM grantex_cap_reservations WHERE tenant_id = $1 AND counter_key = $2 AND reservation_id = $3', [tenant, limit.key, reservationId]);
185
+ }
186
+ }
187
+ finally {
188
+ client.release();
189
+ }
190
+ }
191
+ async usage(tenantId, limit, nowMs) {
192
+ const tenant = await tenantHash(tenantId);
193
+ const client = await this.#pool.connect();
194
+ try {
195
+ const now = await PostgresCapsBackend.#now(client, nowMs);
196
+ return await PostgresCapsBackend.#used(client, tenant, limit.key, WINDOW_MS[limit.window], now, false);
197
+ }
198
+ finally {
199
+ client.release();
200
+ }
201
+ }
202
+ /**
203
+ * Delete expired reservations and counters left empty; returns the numbers
204
+ * deleted. Counters being reserved are locked and skipped, and a counter that
205
+ * still has reservations cannot be deleted (the foreign key refuses it,
206
+ * failing this run rather than losing a reservation; run it again).
207
+ */
208
+ async prune(nowMs) {
209
+ return this.#transaction(async (client) => {
210
+ const now = await PostgresCapsBackend.#now(client, nowMs);
211
+ const expired = await client.query('DELETE FROM grantex_cap_reservations WHERE expires_at IS NOT NULL AND expires_at <= $1::timestamptz', [now]);
212
+ const empty = await client.query('DELETE FROM grantex_cap_counters WHERE (tenant_id, counter_key) IN (' +
213
+ ' SELECT c.tenant_id, c.counter_key FROM grantex_cap_counters c' +
214
+ ' WHERE NOT EXISTS (SELECT 1 FROM grantex_cap_reservations r' +
215
+ ' WHERE r.tenant_id = c.tenant_id AND r.counter_key = c.counter_key)' +
216
+ ' FOR UPDATE SKIP LOCKED)');
217
+ return { reservations: expired.rowCount ?? 0, counters: empty.rowCount ?? 0 };
218
+ });
219
+ }
220
+ }
221
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/caps/postgres.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Redis caps backend: one Lua script reserves every limit atomically.
3
+ *
4
+ * Keys are tenant-scoped and share a hash tag, `grantex:caps:{<tenant>}:...`,
5
+ * so one reservation touches a single Redis Cluster slot. Each counter is a
6
+ * sorted set of `<reservation id>:<units>` members scored by reservation time
7
+ * plus a running total; rolling windows drop members older than the window
8
+ * before checking. Time comes from the Redis server (`TIME`) unless a clock is
9
+ * injected for tests.
10
+ *
11
+ * Requires Redis 6.0 or later (`SET ... KEEPTTL`). Run it with
12
+ * `maxmemory-policy noeviction`: an evicted counter would forget reservations
13
+ * and let calls exceed a cap. Per-hour and per-day keys expire one window plus
14
+ * 60 seconds after the last reservation on the counter; per-case keys only with
15
+ * `caseTtlSeconds`.
16
+ *
17
+ * The scripts are byte-identical to the Python SDK's (`grantex/caps/_redis.py`);
18
+ * a test in each SDK checks this.
19
+ */
20
+ import { type CapsBackend, type ResolvedCapLimit } from './meter.js';
21
+ export declare const RESERVE_SCRIPT = "\nlocal now\nif ARGV[1] ~= '' then\n now = tonumber(ARGV[1])\nelse\n local t = redis.call('TIME')\n now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\nend\nlocal rid = ARGV[2]\nlocal n = tonumber(ARGV[3])\nlocal case_ttl = tonumber(ARGV[4])\nfor i = 1, n do\n local a = 4 + (i - 1) * 3\n local limit = tonumber(ARGV[a + 1])\n local window = tonumber(ARGV[a + 2])\n local units = tonumber(ARGV[a + 3])\n local zkey = KEYS[(i - 1) * 2 + 1]\n local skey = KEYS[(i - 1) * 2 + 2]\n local sum = tonumber(redis.call('GET', skey) or '0')\n if window > 0 then\n local expired = redis.call('ZRANGEBYSCORE', zkey, '-inf', now - window)\n if #expired > 0 then\n for _, member in ipairs(expired) do\n sum = sum - tonumber(string.match(member, ':(%d+)$'))\n end\n if sum < 0 then sum = 0 end\n redis.call('ZREMRANGEBYSCORE', zkey, '-inf', now - window)\n redis.call('SET', skey, sum, 'KEEPTTL')\n end\n end\n if sum + units > limit then\n return {0, i, sum}\n end\nend\nfor i = 1, n do\n local a = 4 + (i - 1) * 3\n local window = tonumber(ARGV[a + 2])\n local units = tonumber(ARGV[a + 3])\n local zkey = KEYS[(i - 1) * 2 + 1]\n local skey = KEYS[(i - 1) * 2 + 2]\n redis.call('ZADD', zkey, now, rid .. ':' .. units)\n redis.call('INCRBY', skey, units)\n local ttl = case_ttl\n if window > 0 then ttl = window + 60000 end\n if ttl > 0 then\n redis.call('PEXPIRE', zkey, ttl)\n redis.call('PEXPIRE', skey, ttl)\n end\nend\nreturn {1, 0, 0}\n";
22
+ export declare const REFUND_SCRIPT = "\nlocal rid = ARGV[1]\nlocal n = tonumber(ARGV[2])\nfor i = 1, n do\n local units = ARGV[2 + i]\n local zkey = KEYS[(i - 1) * 2 + 1]\n local skey = KEYS[(i - 1) * 2 + 2]\n if redis.call('ZREM', zkey, rid .. ':' .. units) == 1 then\n local left = redis.call('DECRBY', skey, tonumber(units))\n if left < 0 then redis.call('SET', skey, 0, 'KEEPTTL') end\n end\nend\nreturn 1\n";
23
+ export declare const USAGE_SCRIPT = "\nlocal now\nif ARGV[1] ~= '' then\n now = tonumber(ARGV[1])\nelse\n local t = redis.call('TIME')\n now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\nend\nlocal window = tonumber(ARGV[2])\nlocal sum = tonumber(redis.call('GET', KEYS[2]) or '0')\nif window > 0 then\n for _, member in ipairs(redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', now - window)) do\n sum = sum - tonumber(string.match(member, ':(%d+)$'))\n end\nend\nif sum < 0 then sum = 0 end\nreturn sum\n";
24
+ /** Runs a Lua script. Adapt your client, for example `ioredisRunner(new Redis(url))`. */
25
+ export interface RedisScriptRunner {
26
+ evalScript(script: string, keys: readonly string[], args: readonly (string | number)[]): Promise<unknown>;
27
+ }
28
+ /** Adapter for an ioredis client (`client.eval(script, numKeys, ...keysAndArgs)`). */
29
+ export declare function ioredisRunner(client: {
30
+ eval(script: string, numKeys: number, ...keysAndArgs: (string | number)[]): Promise<unknown>;
31
+ }): RedisScriptRunner;
32
+ export declare class RedisCapsBackend implements CapsBackend {
33
+ #private;
34
+ /**
35
+ * `caseTtlSeconds` optionally expires per-case counters; by default they never
36
+ * expire, because an expired per-case counter would reset the cap.
37
+ */
38
+ constructor(redis: RedisScriptRunner, options?: {
39
+ caseTtlSeconds?: number;
40
+ });
41
+ reserve(tenantId: string, reservationId: string, limits: readonly ResolvedCapLimit[], nowMs: number | undefined): Promise<void>;
42
+ refund(tenantId: string, reservationId: string, limits: readonly ResolvedCapLimit[]): Promise<void>;
43
+ usage(tenantId: string, limit: ResolvedCapLimit, nowMs: number | undefined): Promise<number>;
44
+ }
45
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/caps/redis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAA2C,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9G,eAAO,MAAM,cAAc,i/CAkD1B,CAAC;AAEF,eAAO,MAAM,aAAa,qYAazB,CAAC;AAEF,eAAO,MAAM,YAAY,2eAiBxB,CAAC;AAGF,yFAAyF;AACzF,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3G;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9F,GAAG,iBAAiB,CAIpB;AAaD,qBAAa,gBAAiB,YAAW,WAAW;;IAIlD;;;OAGG;IACH,YAAY,KAAK,EAAE,iBAAiB,EAAE,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO,EAO9E;IAEK,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBpI;IAEK,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxG;IAEK,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAMjG;CACF"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Redis caps backend: one Lua script reserves every limit atomically.
3
+ *
4
+ * Keys are tenant-scoped and share a hash tag, `grantex:caps:{<tenant>}:...`,
5
+ * so one reservation touches a single Redis Cluster slot. Each counter is a
6
+ * sorted set of `<reservation id>:<units>` members scored by reservation time
7
+ * plus a running total; rolling windows drop members older than the window
8
+ * before checking. Time comes from the Redis server (`TIME`) unless a clock is
9
+ * injected for tests.
10
+ *
11
+ * Requires Redis 6.0 or later (`SET ... KEEPTTL`). Run it with
12
+ * `maxmemory-policy noeviction`: an evicted counter would forget reservations
13
+ * and let calls exceed a cap. Per-hour and per-day keys expire one window plus
14
+ * 60 seconds after the last reservation on the counter; per-case keys only with
15
+ * `caseTtlSeconds`.
16
+ *
17
+ * The scripts are byte-identical to the Python SDK's (`grantex/caps/_redis.py`);
18
+ * a test in each SDK checks this.
19
+ */
20
+ import { CapExceededError, WINDOW_MS, tenantHash } from './meter.js';
21
+ // grantex caps redis script v1 begin
22
+ export const RESERVE_SCRIPT = `
23
+ local now
24
+ if ARGV[1] ~= '' then
25
+ now = tonumber(ARGV[1])
26
+ else
27
+ local t = redis.call('TIME')
28
+ now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)
29
+ end
30
+ local rid = ARGV[2]
31
+ local n = tonumber(ARGV[3])
32
+ local case_ttl = tonumber(ARGV[4])
33
+ for i = 1, n do
34
+ local a = 4 + (i - 1) * 3
35
+ local limit = tonumber(ARGV[a + 1])
36
+ local window = tonumber(ARGV[a + 2])
37
+ local units = tonumber(ARGV[a + 3])
38
+ local zkey = KEYS[(i - 1) * 2 + 1]
39
+ local skey = KEYS[(i - 1) * 2 + 2]
40
+ local sum = tonumber(redis.call('GET', skey) or '0')
41
+ if window > 0 then
42
+ local expired = redis.call('ZRANGEBYSCORE', zkey, '-inf', now - window)
43
+ if #expired > 0 then
44
+ for _, member in ipairs(expired) do
45
+ sum = sum - tonumber(string.match(member, ':(%d+)$'))
46
+ end
47
+ if sum < 0 then sum = 0 end
48
+ redis.call('ZREMRANGEBYSCORE', zkey, '-inf', now - window)
49
+ redis.call('SET', skey, sum, 'KEEPTTL')
50
+ end
51
+ end
52
+ if sum + units > limit then
53
+ return {0, i, sum}
54
+ end
55
+ end
56
+ for i = 1, n do
57
+ local a = 4 + (i - 1) * 3
58
+ local window = tonumber(ARGV[a + 2])
59
+ local units = tonumber(ARGV[a + 3])
60
+ local zkey = KEYS[(i - 1) * 2 + 1]
61
+ local skey = KEYS[(i - 1) * 2 + 2]
62
+ redis.call('ZADD', zkey, now, rid .. ':' .. units)
63
+ redis.call('INCRBY', skey, units)
64
+ local ttl = case_ttl
65
+ if window > 0 then ttl = window + 60000 end
66
+ if ttl > 0 then
67
+ redis.call('PEXPIRE', zkey, ttl)
68
+ redis.call('PEXPIRE', skey, ttl)
69
+ end
70
+ end
71
+ return {1, 0, 0}
72
+ `;
73
+ export const REFUND_SCRIPT = `
74
+ local rid = ARGV[1]
75
+ local n = tonumber(ARGV[2])
76
+ for i = 1, n do
77
+ local units = ARGV[2 + i]
78
+ local zkey = KEYS[(i - 1) * 2 + 1]
79
+ local skey = KEYS[(i - 1) * 2 + 2]
80
+ if redis.call('ZREM', zkey, rid .. ':' .. units) == 1 then
81
+ local left = redis.call('DECRBY', skey, tonumber(units))
82
+ if left < 0 then redis.call('SET', skey, 0, 'KEEPTTL') end
83
+ end
84
+ end
85
+ return 1
86
+ `;
87
+ export const USAGE_SCRIPT = `
88
+ local now
89
+ if ARGV[1] ~= '' then
90
+ now = tonumber(ARGV[1])
91
+ else
92
+ local t = redis.call('TIME')
93
+ now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)
94
+ end
95
+ local window = tonumber(ARGV[2])
96
+ local sum = tonumber(redis.call('GET', KEYS[2]) or '0')
97
+ if window > 0 then
98
+ for _, member in ipairs(redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', now - window)) do
99
+ sum = sum - tonumber(string.match(member, ':(%d+)$'))
100
+ end
101
+ end
102
+ if sum < 0 then sum = 0 end
103
+ return sum
104
+ `;
105
+ /** Adapter for an ioredis client (`client.eval(script, numKeys, ...keysAndArgs)`). */
106
+ export function ioredisRunner(client) {
107
+ return {
108
+ evalScript: (script, keys, args) => client.eval(script, keys.length, ...keys, ...args),
109
+ };
110
+ }
111
+ async function keysFor(tenantId, limit) {
112
+ const base = `grantex:caps:{${await tenantHash(tenantId)}}:${limit.key}`;
113
+ return [`${base}:z`, `${base}:s`];
114
+ }
115
+ function toInt(value) {
116
+ const n = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : Number.NaN;
117
+ if (!Number.isInteger(n))
118
+ throw new Error('unexpected caps script result');
119
+ return n;
120
+ }
121
+ export class RedisCapsBackend {
122
+ #redis;
123
+ #caseTtlMs;
124
+ /**
125
+ * `caseTtlSeconds` optionally expires per-case counters; by default they never
126
+ * expire, because an expired per-case counter would reset the cap.
127
+ */
128
+ constructor(redis, options = {}) {
129
+ const ttl = options.caseTtlSeconds;
130
+ if (ttl !== undefined && (!Number.isInteger(ttl) || ttl <= 0)) {
131
+ throw new Error('caseTtlSeconds must be a positive integer');
132
+ }
133
+ this.#redis = redis;
134
+ this.#caseTtlMs = ttl === undefined ? 0 : ttl * 1000;
135
+ }
136
+ async reserve(tenantId, reservationId, limits, nowMs) {
137
+ const keys = [];
138
+ const args = [nowMs === undefined ? '' : String(nowMs), reservationId, limits.length, this.#caseTtlMs];
139
+ for (const limit of limits) {
140
+ keys.push(...(await keysFor(tenantId, limit)));
141
+ args.push(limit.limit, WINDOW_MS[limit.window], limit.units);
142
+ }
143
+ const result = await this.#redis.evalScript(RESERVE_SCRIPT, keys, args);
144
+ if (!Array.isArray(result) || result.length !== 3)
145
+ throw new Error('unexpected caps script result');
146
+ const [ok, index, used] = result.map(toInt);
147
+ if (ok === 1)
148
+ return;
149
+ const limit = limits[index - 1];
150
+ if (limit === undefined)
151
+ throw new Error('unexpected caps script result');
152
+ throw new CapExceededError({
153
+ limit: limit.limit, window: limit.window, used, requested: limit.units, scope: limit.scope, kind: limit.kind,
154
+ });
155
+ }
156
+ async refund(tenantId, reservationId, limits) {
157
+ const keys = [];
158
+ const args = [reservationId, limits.length];
159
+ for (const limit of limits) {
160
+ keys.push(...(await keysFor(tenantId, limit)));
161
+ args.push(limit.units);
162
+ }
163
+ await this.#redis.evalScript(REFUND_SCRIPT, keys, args);
164
+ }
165
+ async usage(tenantId, limit, nowMs) {
166
+ const result = await this.#redis.evalScript(USAGE_SCRIPT, await keysFor(tenantId, limit), [
167
+ nowMs === undefined ? '' : String(nowMs),
168
+ WINDOW_MS[limit.window],
169
+ ]);
170
+ return toInt(result);
171
+ }
172
+ }
173
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/caps/redis.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -21,6 +21,8 @@ import { PassportsClient } from './resources/passports.js';
21
21
  import { DpdpClient } from './resources/dpdp.js';
22
22
  import { CommerceClient } from './resources/commerce.js';
23
23
  import { ToolManifest, type EnforceOptions, type EnforceResult, type WrapToolOptions, type EnforceMiddlewareOptions } from './manifest.js';
24
+ import { RevocationFeed, type RevocationFeedState } from './revocations/index.js';
25
+ import { DecisionsClient } from './resources/decisions.js';
24
26
  import type { AuthorizationRequest, AuthorizeParams, GrantexClientOptions, RateLimit, RotateKeyResponse, SignupParams, SignupResponse, UpdateDeveloperSettingsParams, UpdateDeveloperSettingsResponse } from './types.js';
25
27
  export declare class Grantex {
26
28
  #private;
@@ -46,6 +48,7 @@ export declare class Grantex {
46
48
  readonly passports: PassportsClient;
47
49
  readonly dpdp: DpdpClient;
48
50
  readonly commerce: CommerceClient;
51
+ readonly decisions: DecisionsClient;
49
52
  get lastRateLimit(): RateLimit | undefined;
50
53
  constructor(options?: GrantexClientOptions);
51
54
  /**
@@ -101,6 +104,15 @@ export declare class Grantex {
101
104
  * if (!result.allowed) throw new Error(result.reason);
102
105
  * ```
103
106
  */
107
+ /**
108
+ * The revocation feed this client uses for `revocationCheck: 'feed'`,
109
+ * started on first use. Call `stopRevocationFeed()` to close it.
110
+ */
111
+ revocationFeed(): RevocationFeed;
112
+ /** What the revocation feed knows, and whether it is fresh enough to be trusted. */
113
+ revocationFeedState(): RevocationFeedState | undefined;
114
+ /** Stop following the revocation feed and release its connection. */
115
+ stopRevocationFeed(): Promise<void>;
104
116
  enforce(options: EnforceOptions): Promise<EnforceResult>;
105
117
  /**
106
118
  * Wrap a LangChain StructuredTool with automatic Grantex scope enforcement.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}