@shiprev/server-sdk-node 0.0.0-stage → 0.1.0-beta.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 (43) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +253 -2
  3. package/dist/auth/token-verifier.d.ts +42 -0
  4. package/dist/auth/token-verifier.js +230 -0
  5. package/dist/client/connection-supervisor.d.ts +117 -0
  6. package/dist/client/connection-supervisor.js +547 -0
  7. package/dist/client/shiprev-client.d.ts +106 -0
  8. package/dist/client/shiprev-client.js +267 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.js +47 -0
  11. package/dist/metering/backlog/host-adapter-backlog.d.ts +10 -0
  12. package/dist/metering/backlog/host-adapter-backlog.js +27 -0
  13. package/dist/metering/backlog/journal-backlog.d.ts +14 -0
  14. package/dist/metering/backlog/journal-backlog.js +135 -0
  15. package/dist/metering/backlog/memory-backlog.d.ts +9 -0
  16. package/dist/metering/backlog/memory-backlog.js +34 -0
  17. package/dist/metering/usage-buffer.d.ts +59 -0
  18. package/dist/metering/usage-buffer.js +175 -0
  19. package/dist/mirror/entitlement-mirror.d.ts +47 -0
  20. package/dist/mirror/entitlement-mirror.js +109 -0
  21. package/dist/nest/decorators.d.ts +15 -0
  22. package/dist/nest/decorators.js +21 -0
  23. package/dist/nest/index.d.ts +4 -0
  24. package/dist/nest/index.js +21 -0
  25. package/dist/nest/shiprev-auth.guard.d.ts +7 -0
  26. package/dist/nest/shiprev-auth.guard.js +52 -0
  27. package/dist/nest/shiprev-capability.guard.d.ts +9 -0
  28. package/dist/nest/shiprev-capability.guard.js +65 -0
  29. package/dist/nest/shiprev.module.d.ts +17 -0
  30. package/dist/nest/shiprev.module.js +57 -0
  31. package/dist/ruling/adjudicator.d.ts +15 -0
  32. package/dist/ruling/adjudicator.js +131 -0
  33. package/dist/transport/event-stream.d.ts +27 -0
  34. package/dist/transport/event-stream.js +92 -0
  35. package/dist/transport/http.transport.d.ts +64 -0
  36. package/dist/transport/http.transport.js +218 -0
  37. package/dist/transport/wire.d.ts +79 -0
  38. package/dist/transport/wire.js +8 -0
  39. package/dist/types.d.ts +191 -0
  40. package/dist/types.js +25 -0
  41. package/dist/util/decimal.d.ts +14 -0
  42. package/dist/util/decimal.js +57 -0
  43. package/package.json +98 -4
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Adjudicator = void 0;
4
+ const decimal_1 = require("../util/decimal");
5
+ class Adjudicator {
6
+ mirror;
7
+ presets;
8
+ constructor(mirror, presets) {
9
+ this.mirror = mirror;
10
+ this.presets = presets;
11
+ }
12
+ /**
13
+ * Synthesizes a four-tier ruling based on local mirror and connection state.
14
+ *
15
+ * @param options Adjudication parameters including explicit operation ('read' | 'write')
16
+ * @param isOnline Whether the connection to the control plane is currently healthy
17
+ * @param authoritativeLookup Optional callback to synchronously fetch from remote if missing in mirror
18
+ */
19
+ adjudicate(options, isOnline, authoritativeLookup) {
20
+ const now = Date.now();
21
+ // 1. Mandatory operation guard
22
+ if (options.operation !== 'read' && options.operation !== 'write') {
23
+ return {
24
+ allowed: false,
25
+ source: 'client_default',
26
+ reason: 'invalid_request',
27
+ adjudicated_at: now,
28
+ };
29
+ }
30
+ const { organization_id, capability_key, quantity = 1, operation } = options;
31
+ if (typeof quantity !== 'number' || !Number.isFinite(quantity) || quantity < 0) {
32
+ return {
33
+ allowed: false,
34
+ source: 'client_default',
35
+ reason: 'invalid_request',
36
+ adjudicated_at: now,
37
+ };
38
+ }
39
+ // 2. Cold start offline (mirror is empty and control plane is offline)
40
+ if (this.mirror.isEmpty() && !isOnline) {
41
+ if (operation === 'write') {
42
+ return {
43
+ allowed: false,
44
+ source: 'client_default',
45
+ reason: 'disconnected_write',
46
+ adjudicated_at: now,
47
+ };
48
+ }
49
+ // Read operation: check customer preset
50
+ const allowColdRead = this.presets?.cold_start === 'allow';
51
+ return {
52
+ allowed: allowColdRead,
53
+ source: 'client_default',
54
+ reason: allowColdRead ? undefined : 'cold_start_denied',
55
+ adjudicated_at: now,
56
+ };
57
+ }
58
+ // 3. Disconnected mode with existing mirror
59
+ if (!isOnline) {
60
+ if (operation === 'write') {
61
+ return {
62
+ allowed: false,
63
+ source: 'degraded',
64
+ reason: 'disconnected_write',
65
+ adjudicated_at: now,
66
+ };
67
+ }
68
+ // Read fail-open under disconnected state
69
+ return {
70
+ allowed: true,
71
+ source: 'degraded',
72
+ adjudicated_at: now,
73
+ };
74
+ }
75
+ // 4. Online evaluation: check mirror first
76
+ const item = this.mirror.getItem(organization_id, capability_key);
77
+ if (item) {
78
+ // Tier 1: realtime, compared exactly on the snapshot's decimal strings
79
+ const limit = (0, decimal_1.decimalToNumber)(item.allowance);
80
+ if ((0, decimal_1.compareDecimal)((0, decimal_1.toDecimalString)(quantity), item.remaining) <= 0) {
81
+ return {
82
+ allowed: true,
83
+ remaining: (0, decimal_1.decimalToNumber)(item.remaining),
84
+ limit,
85
+ source: 'realtime',
86
+ adjudicated_at: now,
87
+ };
88
+ }
89
+ // Beyond the remaining allowance only ALLOW_AND_BILL lets the call through locally; any other
90
+ // policy depends on prices and wallet state the mirror does not hold, so it is refused.
91
+ if (item.overage_policy === 'ALLOW_AND_BILL') {
92
+ return {
93
+ allowed: true,
94
+ remaining: 0,
95
+ limit,
96
+ source: 'realtime',
97
+ adjudicated_at: now,
98
+ };
99
+ }
100
+ return {
101
+ allowed: false,
102
+ remaining: (0, decimal_1.decimalToNumber)(item.remaining),
103
+ limit,
104
+ source: 'realtime',
105
+ reason: 'quota_exhausted',
106
+ adjudicated_at: now,
107
+ };
108
+ }
109
+ // 5. Tier 2: authoritative fallback if missing in mirror but online
110
+ if (authoritativeLookup) {
111
+ const authoritativeResult = authoritativeLookup();
112
+ if (authoritativeResult) {
113
+ return {
114
+ ...authoritativeResult,
115
+ source: 'authoritative',
116
+ adjudicated_at: now,
117
+ };
118
+ }
119
+ }
120
+ // Online but the mirror does not know the organization or capability: a client-side refusal,
121
+ // not a service ruling. adjudicateAsync() asks the service on this reason.
122
+ return {
123
+ allowed: false,
124
+ source: 'client_default',
125
+ reason: 'capability_not_granted',
126
+ adjudicated_at: now,
127
+ };
128
+ }
129
+ }
130
+ exports.Adjudicator = Adjudicator;
131
+ //# sourceMappingURL=adjudicator.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Server-Sent Events reader for `GET /sdk/v1/events`, on top of a `fetch` response body. No
3
+ * dependency: the frames are parsed here.
4
+ *
5
+ * Accepts what the format allows: frames split across reads or several per read, LF, CRLF or CR
6
+ * line endings, comment lines (starting with `:`), and `data` spread over several lines (joined
7
+ * with a newline). Fields other than `event` and `data` are ignored.
8
+ */
9
+ export interface EventStreamFrame {
10
+ /** Event type; `message` when the frame names none. */
11
+ event: string;
12
+ /** The `data` lines joined with `\n`. */
13
+ data: string;
14
+ }
15
+ /** Incremental parser: feed it text, it returns the frames completed so far. */
16
+ export declare class EventStreamParser {
17
+ private pending;
18
+ private eventType;
19
+ private dataLines;
20
+ push(chunk: string): EventStreamFrame[];
21
+ private line;
22
+ }
23
+ /**
24
+ * Reads the body of an event stream response and hands every frame to `onFrame`. Resolves when the
25
+ * body ends, rejects on a read error (a reset connection, an abort).
26
+ */
27
+ export declare function readEventStream(body: ReadableStream<Uint8Array>, onFrame: (frame: EventStreamFrame) => void): Promise<void>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /**
3
+ * Server-Sent Events reader for `GET /sdk/v1/events`, on top of a `fetch` response body. No
4
+ * dependency: the frames are parsed here.
5
+ *
6
+ * Accepts what the format allows: frames split across reads or several per read, LF, CRLF or CR
7
+ * line endings, comment lines (starting with `:`), and `data` spread over several lines (joined
8
+ * with a newline). Fields other than `event` and `data` are ignored.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.EventStreamParser = void 0;
12
+ exports.readEventStream = readEventStream;
13
+ /** Incremental parser: feed it text, it returns the frames completed so far. */
14
+ class EventStreamParser {
15
+ pending = '';
16
+ eventType = '';
17
+ dataLines = [];
18
+ push(chunk) {
19
+ this.pending += chunk;
20
+ const frames = [];
21
+ let start = 0;
22
+ for (let i = 0; i < this.pending.length; i++) {
23
+ const ch = this.pending[i];
24
+ if (ch !== '\n' && ch !== '\r')
25
+ continue;
26
+ // A CR at the very end may be the first half of a CRLF split across reads: wait for more.
27
+ if (ch === '\r' && i === this.pending.length - 1)
28
+ break;
29
+ const line = this.pending.slice(start, i);
30
+ if (ch === '\r' && this.pending[i + 1] === '\n')
31
+ i++;
32
+ start = i + 1;
33
+ const frame = this.line(line);
34
+ if (frame)
35
+ frames.push(frame);
36
+ }
37
+ this.pending = this.pending.slice(start);
38
+ return frames;
39
+ }
40
+ line(line) {
41
+ if (line === '') {
42
+ if (this.dataLines.length === 0) {
43
+ this.eventType = '';
44
+ return undefined;
45
+ }
46
+ const frame = { event: this.eventType || 'message', data: this.dataLines.join('\n') };
47
+ this.eventType = '';
48
+ this.dataLines = [];
49
+ return frame;
50
+ }
51
+ if (line.startsWith(':'))
52
+ return undefined;
53
+ const colon = line.indexOf(':');
54
+ const field = colon === -1 ? line : line.slice(0, colon);
55
+ let value = colon === -1 ? '' : line.slice(colon + 1);
56
+ if (value.startsWith(' '))
57
+ value = value.slice(1);
58
+ if (field === 'event') {
59
+ this.eventType = value;
60
+ }
61
+ else if (field === 'data') {
62
+ this.dataLines.push(value);
63
+ }
64
+ return undefined;
65
+ }
66
+ }
67
+ exports.EventStreamParser = EventStreamParser;
68
+ /**
69
+ * Reads the body of an event stream response and hands every frame to `onFrame`. Resolves when the
70
+ * body ends, rejects on a read error (a reset connection, an abort).
71
+ */
72
+ async function readEventStream(body, onFrame) {
73
+ const reader = body.getReader();
74
+ const decoder = new TextDecoder('utf-8');
75
+ const parser = new EventStreamParser();
76
+ try {
77
+ for (;;) {
78
+ const { done, value } = await reader.read();
79
+ if (done) {
80
+ for (const frame of parser.push(decoder.decode() + '\n'))
81
+ onFrame(frame);
82
+ return;
83
+ }
84
+ for (const frame of parser.push(decoder.decode(value, { stream: true })))
85
+ onFrame(frame);
86
+ }
87
+ }
88
+ finally {
89
+ reader.releaseLock();
90
+ }
91
+ }
92
+ //# sourceMappingURL=event-stream.js.map
@@ -0,0 +1,64 @@
1
+ import { AdjudicationLookupRequest, AdjudicationLookupResponse, ConfirmReservationResult, JwksResponse, ReleaseReservationResult, ReservationResult, ReserveOptions, Snapshot, UsageEventItem, UsageEventsResponse } from '../types';
2
+ /** A non-success HTTP answer from the instance; `status` lets callers tell a refused token from an outage. */
3
+ export declare class HttpError extends Error {
4
+ readonly status: number;
5
+ constructor(message: string, status: number);
6
+ }
7
+ /** True when `error` is an HTTP answer with one of the given statuses. */
8
+ export declare function isHttpStatus(error: unknown, ...statuses: number[]): boolean;
9
+ export interface TransportOptions {
10
+ baseUrl: string;
11
+ connectTimeoutMs?: number;
12
+ clientVersion?: string;
13
+ }
14
+ export declare class HttpTransport {
15
+ private readonly baseUrl;
16
+ private readonly timeoutMs;
17
+ private readonly userAgent;
18
+ constructor(options: TransportOptions);
19
+ /**
20
+ * Fetches public JWKS keys from the unauthenticated runtime endpoint GET /sdk/v1/jwks
21
+ */
22
+ fetchJwks(): Promise<JwksResponse>;
23
+ /**
24
+ * Exchanges an SDK-scoped API key for an RS256 short-lived M2M JWT token
25
+ */
26
+ exchangeM2MToken(apiKey: string): Promise<{
27
+ access_token: string;
28
+ expires_in: number;
29
+ }>;
30
+ /**
31
+ * Fetches the application-level entitlement snapshot: GET /sdk/v1/entitlements/snapshot.
32
+ * With `organizationId` the envelope carries that organization only (used to refill a mirror miss).
33
+ */
34
+ fetchSnapshot(token: string, organizationId?: string): Promise<Snapshot>;
35
+ /**
36
+ * Submits a batch of usage events: POST /sdk/v1/usage-events.
37
+ * Quantities are sent as decimal strings; `properties` travel as the event's `dimensions`.
38
+ */
39
+ reportUsageEvents(events: UsageEventItem[], token: string): Promise<UsageEventsResponse>;
40
+ /**
41
+ * Authoritative ruling when the mirror misses an organization or capability: POST /sdk/v1/adjudicate.
42
+ */
43
+ lookupAdjudication(request: AdjudicationLookupRequest, token: string): Promise<AdjudicationLookupResponse>;
44
+ /**
45
+ * Requests a two-phase reservation: POST /sdk/v1/reservations/reserve.
46
+ */
47
+ reserve(options: ReserveOptions, token: string): Promise<ReservationResult>;
48
+ /**
49
+ * Confirms a pending reservation with the quantity actually used: POST /sdk/v1/reservations/:id/confirm.
50
+ */
51
+ confirmReservation(reservationId: string, actualAmount: number, token: string): Promise<ConfirmReservationResult>;
52
+ /**
53
+ * Releases a pending reservation: POST /sdk/v1/reservations/:id/release.
54
+ */
55
+ releaseReservation(reservationId: string, token: string): Promise<ReleaseReservationResult>;
56
+ /**
57
+ * Opens the push stream `GET /sdk/v1/events`. Resolves with the response once the headers arrive;
58
+ * the body stays open until the instance ends it or `signal` aborts it, so no request timeout
59
+ * applies here (the caller bounds the wait for `ready`).
60
+ */
61
+ openEventStream(token: string, signal: AbortSignal): Promise<Response>;
62
+ private postJson;
63
+ private request;
64
+ }
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpTransport = exports.HttpError = void 0;
4
+ exports.isHttpStatus = isHttpStatus;
5
+ const decimal_1 = require("../util/decimal");
6
+ /** A non-success HTTP answer from the instance; `status` lets callers tell a refused token from an outage. */
7
+ class HttpError extends Error {
8
+ status;
9
+ constructor(message, status) {
10
+ super(message);
11
+ this.status = status;
12
+ this.name = 'HttpError';
13
+ }
14
+ }
15
+ exports.HttpError = HttpError;
16
+ /** True when `error` is an HTTP answer with one of the given statuses. */
17
+ function isHttpStatus(error, ...statuses) {
18
+ return error instanceof HttpError && statuses.includes(error.status);
19
+ }
20
+ class HttpTransport {
21
+ baseUrl;
22
+ timeoutMs;
23
+ userAgent;
24
+ constructor(options) {
25
+ // Strip trailing slashes
26
+ this.baseUrl = options.baseUrl.replace(/\/+$/, '');
27
+ this.timeoutMs = options.connectTimeoutMs ?? 5000;
28
+ this.userAgent = `@shiprev/server-sdk-node/${options.clientVersion ?? '0.0.0'}`;
29
+ }
30
+ /**
31
+ * Fetches public JWKS keys from the unauthenticated runtime endpoint GET /sdk/v1/jwks
32
+ */
33
+ async fetchJwks() {
34
+ const url = `${this.baseUrl}/sdk/v1/jwks`;
35
+ const response = await this.request(url, {
36
+ method: 'GET',
37
+ });
38
+ if (!response.ok) {
39
+ throw new HttpError(`Failed to fetch JWKS from ${url}: HTTP ${response.status}`, response.status);
40
+ }
41
+ return (await response.json());
42
+ }
43
+ /**
44
+ * Exchanges an SDK-scoped API key for an RS256 short-lived M2M JWT token
45
+ */
46
+ async exchangeM2MToken(apiKey) {
47
+ const url = `${this.baseUrl}/sdk/v1/m2m/token`;
48
+ const response = await this.request(url, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify({ api_key: apiKey }),
54
+ });
55
+ if (!response.ok) {
56
+ const errorText = await response.text();
57
+ throw new HttpError(`Failed to exchange M2M token: HTTP ${response.status} - ${errorText}`, response.status);
58
+ }
59
+ return (await response.json());
60
+ }
61
+ /**
62
+ * Fetches the application-level entitlement snapshot: GET /sdk/v1/entitlements/snapshot.
63
+ * With `organizationId` the envelope carries that organization only (used to refill a mirror miss).
64
+ */
65
+ async fetchSnapshot(token, organizationId) {
66
+ const url = organizationId
67
+ ? `${this.baseUrl}/sdk/v1/entitlements/snapshot?organization_id=${encodeURIComponent(organizationId)}`
68
+ : `${this.baseUrl}/sdk/v1/entitlements/snapshot`;
69
+ const response = await this.request(url, {
70
+ method: 'GET',
71
+ headers: {
72
+ Authorization: `Bearer ${token}`,
73
+ },
74
+ });
75
+ if (!response.ok) {
76
+ throw new HttpError(`Failed to fetch snapshot: HTTP ${response.status}`, response.status);
77
+ }
78
+ return (await response.json());
79
+ }
80
+ /**
81
+ * Submits a batch of usage events: POST /sdk/v1/usage-events.
82
+ * Quantities are sent as decimal strings; `properties` travel as the event's `dimensions`.
83
+ */
84
+ async reportUsageEvents(events, token) {
85
+ const wireEvents = events.map((event) => ({
86
+ usage_event_id: event.usage_event_id,
87
+ organization_id: event.organization_id,
88
+ capability_key: event.capability_key,
89
+ quantity: (0, decimal_1.toDecimalString)(event.quantity),
90
+ occurred_at: event.occurred_at,
91
+ ...(event.properties !== undefined ? { dimensions: event.properties } : {}),
92
+ }));
93
+ const body = await this.postJson('/sdk/v1/usage-events', { events: wireEvents }, token, 'Failed to report usage events');
94
+ return {
95
+ accepted: body.accepted_count,
96
+ duplicates_dropped: body.duplicates_dropped,
97
+ rejected: body.rejected ?? [],
98
+ };
99
+ }
100
+ /**
101
+ * Authoritative ruling when the mirror misses an organization or capability: POST /sdk/v1/adjudicate.
102
+ */
103
+ async lookupAdjudication(request, token) {
104
+ const wireRequest = {
105
+ organization_id: request.organization_id,
106
+ capability_key: request.capability_key,
107
+ operation: request.operation,
108
+ ...(request.quantity !== undefined ? { quantity: (0, decimal_1.toDecimalString)(request.quantity) } : {}),
109
+ };
110
+ const body = await this.postJson('/sdk/v1/adjudicate', wireRequest, token, 'Authoritative lookup failed');
111
+ return {
112
+ allowed: body.allowed,
113
+ remaining: (0, decimal_1.decimalToNumber)(body.remaining),
114
+ limit: (0, decimal_1.decimalToNumber)(body.limit),
115
+ source: 'authoritative',
116
+ reason: body.reason ?? undefined,
117
+ };
118
+ }
119
+ /**
120
+ * Requests a two-phase reservation: POST /sdk/v1/reservations/reserve.
121
+ */
122
+ async reserve(options, token) {
123
+ const wireRequest = {
124
+ organization_id: options.organization_id,
125
+ capability_key: options.capability_key,
126
+ quantity: (0, decimal_1.toDecimalString)(options.amount),
127
+ ...(options.ttl_seconds !== undefined ? { ttl_seconds: options.ttl_seconds } : {}),
128
+ };
129
+ const body = await this.postJson('/sdk/v1/reservations/reserve', wireRequest, token, 'Reservation failed');
130
+ return {
131
+ reservation_id: body.reservation_id,
132
+ status: body.status === 'RESERVED' ? 'PENDING' : body.status,
133
+ expires_at: body.expires_at,
134
+ };
135
+ }
136
+ /**
137
+ * Confirms a pending reservation with the quantity actually used: POST /sdk/v1/reservations/:id/confirm.
138
+ */
139
+ async confirmReservation(reservationId, actualAmount, token) {
140
+ const wireRequest = { actual_quantity: (0, decimal_1.toDecimalString)(actualAmount) };
141
+ const body = await this.postJson(`/sdk/v1/reservations/${encodeURIComponent(reservationId)}/confirm`, wireRequest, token, 'Reservation confirm failed');
142
+ return {
143
+ reservation_id: body.reservation_id,
144
+ status: 'CONFIRMED',
145
+ confirmed_amount: (0, decimal_1.decimalToNumber)(body.actual_quantity) ?? actualAmount,
146
+ };
147
+ }
148
+ /**
149
+ * Releases a pending reservation: POST /sdk/v1/reservations/:id/release.
150
+ */
151
+ async releaseReservation(reservationId, token) {
152
+ const body = await this.postJson(`/sdk/v1/reservations/${encodeURIComponent(reservationId)}/release`, {}, token, 'Reservation release failed');
153
+ return { reservation_id: body.reservation_id, status: 'RELEASED' };
154
+ }
155
+ /**
156
+ * Opens the push stream `GET /sdk/v1/events`. Resolves with the response once the headers arrive;
157
+ * the body stays open until the instance ends it or `signal` aborts it, so no request timeout
158
+ * applies here (the caller bounds the wait for `ready`).
159
+ */
160
+ async openEventStream(token, signal) {
161
+ const url = `${this.baseUrl}/sdk/v1/events`;
162
+ const response = await fetch(url, {
163
+ method: 'GET',
164
+ headers: {
165
+ Authorization: `Bearer ${token}`,
166
+ Accept: 'text/event-stream',
167
+ 'User-Agent': this.userAgent,
168
+ },
169
+ signal,
170
+ });
171
+ if (!response.ok || !response.body) {
172
+ await response.body?.cancel().catch(() => undefined);
173
+ throw new HttpError(`Failed to open the event stream: HTTP ${response.status}`, response.status);
174
+ }
175
+ return response;
176
+ }
177
+ async postJson(path, payload, token, failure) {
178
+ const response = await this.request(`${this.baseUrl}${path}`, {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ Authorization: `Bearer ${token}`,
183
+ },
184
+ body: JSON.stringify(payload),
185
+ });
186
+ if (!response.ok) {
187
+ const errorText = await response.text();
188
+ throw new HttpError(`${failure}: HTTP ${response.status} - ${errorText}`, response.status);
189
+ }
190
+ return (await response.json());
191
+ }
192
+ async request(url, init) {
193
+ const controller = new AbortController();
194
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
195
+ try {
196
+ const headers = new Headers(init.headers);
197
+ headers.set('User-Agent', this.userAgent);
198
+ headers.set('Accept', 'application/json');
199
+ const response = await fetch(url, {
200
+ ...init,
201
+ headers,
202
+ signal: controller.signal,
203
+ });
204
+ return response;
205
+ }
206
+ catch (err) {
207
+ if (err instanceof Error && err.name === 'AbortError') {
208
+ throw new Error(`Request to ${url} timed out after ${this.timeoutMs}ms`);
209
+ }
210
+ throw err;
211
+ }
212
+ finally {
213
+ clearTimeout(timeoutId);
214
+ }
215
+ }
216
+ }
217
+ exports.HttpTransport = HttpTransport;
218
+ //# sourceMappingURL=http.transport.js.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Request and response bodies of the service's `sdk` partition, exactly as they travel on the wire.
3
+ * The service owns these shapes; the transport maps them to and from the public SDK types, so
4
+ * nothing outside `transport/` depends on them. Quantities are fixed-point decimal strings.
5
+ */
6
+ /** `POST /sdk/v1/adjudicate` request. */
7
+ export interface WireAdjudicateRequest {
8
+ organization_id: string;
9
+ capability_key: string;
10
+ quantity?: string;
11
+ operation: 'read' | 'write';
12
+ }
13
+ /** `POST /sdk/v1/adjudicate` response. */
14
+ export interface WireAdjudicateResponse {
15
+ allowed: boolean;
16
+ remaining: string;
17
+ limit?: string;
18
+ source: 'AUTHORITATIVE';
19
+ reason?: string | null;
20
+ adjudicated_at: number;
21
+ }
22
+ /** One event of `POST /sdk/v1/usage-events`. */
23
+ export interface WireUsageEvent {
24
+ usage_event_id: string;
25
+ organization_id: string;
26
+ capability_key: string;
27
+ quantity: string;
28
+ occurred_at: string;
29
+ dimensions?: Record<string, unknown>;
30
+ }
31
+ /** `POST /sdk/v1/usage-events` response. */
32
+ export interface WireUsageEventsResponse {
33
+ accepted_count: number;
34
+ duplicates_dropped: number;
35
+ rejected_count: number;
36
+ rejected: Array<{
37
+ usage_event_id: string;
38
+ reason: string;
39
+ }>;
40
+ }
41
+ /** `POST /sdk/v1/reservations/reserve` request. */
42
+ export interface WireReserveRequest {
43
+ organization_id: string;
44
+ capability_key: string;
45
+ quantity: string;
46
+ ttl_seconds?: number;
47
+ }
48
+ /** `POST /sdk/v1/reservations/reserve` response. */
49
+ export interface WireReservationResponse {
50
+ reservation_id: string;
51
+ project_id: string;
52
+ organization_id: string;
53
+ capability_key: string;
54
+ quantity: string;
55
+ status: 'RESERVED' | 'CONFIRMED' | 'RELEASED';
56
+ expires_at: string;
57
+ created_at: string;
58
+ }
59
+ /** `POST /sdk/v1/reservations/:id/confirm` request. */
60
+ export interface WireConfirmReservationRequest {
61
+ actual_quantity: string;
62
+ }
63
+ /** `POST /sdk/v1/reservations/:id/confirm` response. */
64
+ export interface WireConfirmReservationResponse {
65
+ reservation_id: string;
66
+ status: 'CONFIRMED';
67
+ reserved_quantity: string;
68
+ actual_quantity: string;
69
+ difference_refunded?: string;
70
+ difference_debited?: string;
71
+ settled_at: string;
72
+ }
73
+ /** `POST /sdk/v1/reservations/:id/release` response. */
74
+ export interface WireReleaseReservationResponse {
75
+ reservation_id: string;
76
+ status: 'RELEASED';
77
+ released_quantity: string;
78
+ released_at: string;
79
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Request and response bodies of the service's `sdk` partition, exactly as they travel on the wire.
4
+ * The service owns these shapes; the transport maps them to and from the public SDK types, so
5
+ * nothing outside `transport/` depends on them. Quantities are fixed-point decimal strings.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ //# sourceMappingURL=wire.js.map