@clocknext/sdk 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +549 -264
- package/dist/index.cjs +257 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -43
- package/dist/index.d.ts +228 -43
- package/dist/index.js +257 -117
- package/dist/index.js.map +1 -1
- package/examples/node-basic.ts +93 -0
- package/examples/serverless-flush.ts +47 -0
- package/package.json +3 -1
- package/src/client.ts +107 -0
- package/src/config.ts +116 -0
- package/src/errors.ts +215 -0
- package/src/filters.ts +33 -0
- package/src/flusher.ts +144 -0
- package/src/http.ts +178 -0
- package/src/idempotency.ts +33 -0
- package/src/index.ts +165 -0
- package/src/queue.ts +48 -0
- package/src/resources/composites.ts +47 -0
- package/src/resources/credits.ts +76 -0
- package/src/resources/customers.ts +298 -0
- package/src/resources/invoices.ts +51 -0
- package/src/resources/outcomes.ts +82 -0
- package/src/resources/payments.ts +30 -0
- package/src/resources/plans.ts +76 -0
- package/src/resources/portal.ts +31 -0
- package/src/resources/purchases.ts +75 -0
- package/src/resources/signals.ts +393 -0
- package/src/resources/units.ts +76 -0
- package/src/resources/webhooks.ts +37 -0
- package/src/resources/workspace.ts +33 -0
- package/src/retry.ts +56 -0
- package/src/types.ts +1601 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,143 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ClockNextError = class extends Error {
|
|
3
|
+
/** HTTP status, when the failure came from a server response. */
|
|
4
|
+
status;
|
|
5
|
+
/** Whether the SDK considers this failure worth retrying. */
|
|
6
|
+
retryable;
|
|
7
|
+
/**
|
|
8
|
+
* The server's machine-readable code, when it sent one. Branch on this in
|
|
9
|
+
* preference to `.message`, which is human copy and may be reworded.
|
|
10
|
+
*/
|
|
11
|
+
code;
|
|
12
|
+
/**
|
|
13
|
+
* The idempotency key of the request that failed, for metering calls.
|
|
14
|
+
*
|
|
15
|
+
* This is what makes a dead-letter retry safe: re-send the failed signal with
|
|
16
|
+
* this SAME key and the server deduplicates it instead of billing twice. A
|
|
17
|
+
* fresh key would double-bill, so never generate one when replaying.
|
|
18
|
+
*/
|
|
19
|
+
idempotencyKey;
|
|
20
|
+
constructor(message, opts = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = new.target.name;
|
|
23
|
+
this.status = opts.status;
|
|
24
|
+
this.retryable = opts.retryable ?? false;
|
|
25
|
+
this.code = opts.code;
|
|
26
|
+
this.idempotencyKey = opts.idempotencyKey;
|
|
27
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var AuthError = class extends ClockNextError {
|
|
31
|
+
};
|
|
32
|
+
var ForbiddenError = class extends ClockNextError {
|
|
33
|
+
};
|
|
34
|
+
var ValidationError = class extends ClockNextError {
|
|
35
|
+
};
|
|
36
|
+
var NotFoundError = class extends ClockNextError {
|
|
37
|
+
};
|
|
38
|
+
var PlanError = class extends ClockNextError {
|
|
39
|
+
};
|
|
40
|
+
var AllowanceError = class extends ClockNextError {
|
|
41
|
+
};
|
|
42
|
+
var ConflictError = class extends ClockNextError {
|
|
43
|
+
};
|
|
44
|
+
var RateLimitError = class extends ClockNextError {
|
|
45
|
+
retryAfterMs;
|
|
46
|
+
constructor(message, opts = {}) {
|
|
47
|
+
super(message, {
|
|
48
|
+
status: 429,
|
|
49
|
+
retryable: true,
|
|
50
|
+
idempotencyKey: opts.idempotencyKey
|
|
51
|
+
});
|
|
52
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var ServerError = class extends ClockNextError {
|
|
56
|
+
};
|
|
57
|
+
var NetworkError = class extends ClockNextError {
|
|
58
|
+
constructor(message, opts = {}) {
|
|
59
|
+
super(message, { retryable: true, idempotencyKey: opts.idempotencyKey });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var FIELD_PATHS = [
|
|
63
|
+
[/\busage\.inputTokens\b/g, "tokens.input"],
|
|
64
|
+
[/\busage\.outputTokens\b/g, "tokens.output"],
|
|
65
|
+
[/\busage\.cacheTokens\b/g, "tokens.cache"],
|
|
66
|
+
[/\busage\.model\b/g, "model"],
|
|
67
|
+
// A missing/invalid `usage` block as a whole: the caller's equivalent is
|
|
68
|
+
// `tokens` (plus `model`, which the SDK hoists to the top level).
|
|
69
|
+
[/^usage:/, "tokens:"]
|
|
70
|
+
];
|
|
71
|
+
function translateFieldPaths(message) {
|
|
72
|
+
let out = message;
|
|
73
|
+
for (const [pattern, replacement] of FIELD_PATHS) {
|
|
74
|
+
out = out.replace(pattern, replacement);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
var ALLOWANCE_MESSAGE = /\binsufficient\b|\bno allowance left\b|\bno slots left\b|\boutcomes remaining\b|\btop up the (?:allowance|balance)\b/i;
|
|
79
|
+
function isAllowanceFailure(message, code) {
|
|
80
|
+
if (code) return code === "ALLOWANCE_EXHAUSTED";
|
|
81
|
+
return ALLOWANCE_MESSAGE.test(message);
|
|
82
|
+
}
|
|
83
|
+
var TRANSIENT_CONFLICT = /could not resolve the idempotent request/i;
|
|
84
|
+
function errorFromResponse(status, message, retryAfterMs, meta = {}) {
|
|
85
|
+
const raw = message || `Request failed with status ${status}.`;
|
|
86
|
+
const { code, idempotencyKey } = meta;
|
|
87
|
+
const opts = { status, code, idempotencyKey };
|
|
88
|
+
if (status === 400) {
|
|
89
|
+
return new ValidationError(translateFieldPaths(raw), opts);
|
|
90
|
+
}
|
|
91
|
+
if (status === 401) return new AuthError(raw, opts);
|
|
92
|
+
if (status === 403) return new ForbiddenError(raw, opts);
|
|
93
|
+
if (status === 404) return new NotFoundError(raw, opts);
|
|
94
|
+
if (status === 408) return new ClockNextError(raw, { ...opts, retryable: true });
|
|
95
|
+
if (status === 409) {
|
|
96
|
+
return new ConflictError(raw, {
|
|
97
|
+
...opts,
|
|
98
|
+
retryable: code === "IDEMPOTENCY_UNRESOLVED" || TRANSIENT_CONFLICT.test(raw)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (status === 422) {
|
|
102
|
+
return isAllowanceFailure(raw, code) ? new AllowanceError(raw, opts) : new PlanError(raw, opts);
|
|
103
|
+
}
|
|
104
|
+
if (status === 429) return new RateLimitError(raw, { retryAfterMs, idempotencyKey });
|
|
105
|
+
if (status >= 500) return new ServerError(raw, { ...opts, retryable: true });
|
|
106
|
+
return new ClockNextError(raw, opts);
|
|
107
|
+
}
|
|
108
|
+
|
|
1
109
|
// src/config.ts
|
|
2
110
|
var DEFAULT_BASE_URL = "https://payments.clocknext.com";
|
|
111
|
+
function defaultOnError(error, signal) {
|
|
112
|
+
const what = signal ? `${signal.type} signal` : "request";
|
|
113
|
+
const who = signal ? ` customer=${signal.customerId}` : "";
|
|
114
|
+
const key = signal?.idempotencyKey != null ? ` idempotencyKey=${signal.idempotencyKey}` : "";
|
|
115
|
+
console.error(
|
|
116
|
+
`[clocknext] ${what} permanently failed and was NOT recorded.${who}${key}`,
|
|
117
|
+
error
|
|
118
|
+
);
|
|
119
|
+
console.error(
|
|
120
|
+
"[clocknext] Pass `onError` to handle this yourself, or `onError: () => {}` to silence it."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
function defaultOnDrop(signal, reason) {
|
|
124
|
+
if (reason === "send_failed") return;
|
|
125
|
+
const why = reason === "queue_full" ? "the local queue is full \u2014 raise `batch.maxQueueSize` or flush more often" : "the client is closed \u2014 this signal was tracked after `close()`";
|
|
126
|
+
console.error(
|
|
127
|
+
`[clocknext] dropped a ${signal.type} signal without sending it: ${why}.`,
|
|
128
|
+
`customer=${signal.customerId}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
3
131
|
function num(value, fallback, min) {
|
|
4
132
|
return typeof value === "number" && Number.isFinite(value) && value >= min ? value : fallback;
|
|
5
133
|
}
|
|
6
134
|
function resolveConfig(config) {
|
|
7
135
|
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
8
|
-
throw new
|
|
136
|
+
throw new ValidationError("ClockNext: `apiKey` is required.");
|
|
9
137
|
}
|
|
10
138
|
const fetchImpl = config.fetch ?? (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0);
|
|
11
139
|
if (!fetchImpl) {
|
|
12
|
-
throw new
|
|
140
|
+
throw new ValidationError(
|
|
13
141
|
"ClockNext: no global `fetch` found. Use Node 18+, or pass a `fetch` implementation."
|
|
14
142
|
);
|
|
15
143
|
}
|
|
@@ -31,10 +159,11 @@ function resolveConfig(config) {
|
|
|
31
159
|
},
|
|
32
160
|
fetch: fetchImpl,
|
|
33
161
|
logger: config.logger ?? {},
|
|
34
|
-
|
|
162
|
+
// Both default to loud. See the note above `defaultOnError`.
|
|
163
|
+
onError: config.onError ?? defaultOnError,
|
|
35
164
|
onFlush: config.onFlush,
|
|
36
165
|
onRetry: config.onRetry,
|
|
37
|
-
onDrop: config.onDrop
|
|
166
|
+
onDrop: config.onDrop ?? defaultOnDrop
|
|
38
167
|
};
|
|
39
168
|
}
|
|
40
169
|
|
|
@@ -64,20 +193,25 @@ var Flusher = class {
|
|
|
64
193
|
timer = null;
|
|
65
194
|
draining = null;
|
|
66
195
|
closed = false;
|
|
67
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Buffer one signal. Drops (with onDrop) when closed or the queue is full.
|
|
198
|
+
* Returns whether it was actually buffered, so `track()` can report a drop
|
|
199
|
+
* honestly instead of answering "buffered" for a signal it threw away.
|
|
200
|
+
*/
|
|
68
201
|
enqueue(item) {
|
|
69
202
|
if (this.closed) {
|
|
70
|
-
this.cfg.onDrop?.(item.signal, "
|
|
71
|
-
return;
|
|
203
|
+
this.cfg.onDrop?.(item.signal, "closed");
|
|
204
|
+
return false;
|
|
72
205
|
}
|
|
73
206
|
if (this.queue.size() >= this.cfg.batch.maxQueueSize) {
|
|
74
207
|
this.cfg.logger.warn?.("[clocknext] queue full \u2014 dropping signal");
|
|
75
208
|
this.cfg.onDrop?.(item.signal, "queue_full");
|
|
76
|
-
return;
|
|
209
|
+
return false;
|
|
77
210
|
}
|
|
78
211
|
this.queue.push(item);
|
|
79
212
|
if (this.queue.size() >= this.cfg.batch.maxSize) void this.flush();
|
|
80
213
|
else this.arm();
|
|
214
|
+
return true;
|
|
81
215
|
}
|
|
82
216
|
/** Arm the interval timer if it isn't already running. */
|
|
83
217
|
arm() {
|
|
@@ -88,21 +222,37 @@ var Flusher = class {
|
|
|
88
222
|
}, this.cfg.batch.maxIntervalMs);
|
|
89
223
|
this.timer.unref?.();
|
|
90
224
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
* in-progress drain, so total in-flight sends never exceed `maxConcurrency`.
|
|
94
|
-
*/
|
|
95
|
-
flush() {
|
|
225
|
+
/** Cancel the interval timer if one is armed. */
|
|
226
|
+
clearTimer() {
|
|
96
227
|
if (this.timer) {
|
|
97
228
|
clearTimeout(this.timer);
|
|
98
229
|
this.timer = null;
|
|
99
230
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Send everything buffered at the moment this resolves.
|
|
234
|
+
*
|
|
235
|
+
* Overlapping calls CHAIN onto the in-flight drain rather than sharing it.
|
|
236
|
+
* Sharing looks equivalent and is not: a drain that has already passed its
|
|
237
|
+
* final `size() > 0` check cannot see a signal enqueued a moment later, so
|
|
238
|
+
* handing that same promise back would resolve `flush()` — and therefore
|
|
239
|
+
* `close()` — with the buffer non-empty. Since `close()` has already set
|
|
240
|
+
* `closed`, `arm()` will not re-arm, and the timer it did arm is `unref`'d,
|
|
241
|
+
* so the process is free to exit and that signal is simply lost. Chaining
|
|
242
|
+
* guarantees at least one full drain begins after this call.
|
|
243
|
+
*
|
|
244
|
+
* Concurrency is unaffected: drains run one after another, and each one
|
|
245
|
+
* sends at most `maxConcurrency` signals at a time.
|
|
246
|
+
*/
|
|
247
|
+
flush() {
|
|
248
|
+
this.clearTimer();
|
|
249
|
+
const prior = this.draining ?? Promise.resolve();
|
|
250
|
+
const tracked = prior.catch(() => {
|
|
251
|
+
}).then(() => this.drain()).finally(() => {
|
|
252
|
+
if (this.draining === tracked) this.draining = null;
|
|
253
|
+
});
|
|
254
|
+
this.draining = tracked;
|
|
255
|
+
return tracked;
|
|
106
256
|
}
|
|
107
257
|
/** Drain the buffer in concurrency-bounded waves until it's empty. */
|
|
108
258
|
async drain() {
|
|
@@ -123,7 +273,10 @@ var Flusher = class {
|
|
|
123
273
|
path: item.path,
|
|
124
274
|
body: item.body,
|
|
125
275
|
// Best-effort metering opts into retries on transient failures.
|
|
126
|
-
retry: true
|
|
276
|
+
retry: true,
|
|
277
|
+
// Rides onto any thrown error so `onError` can replay this exact
|
|
278
|
+
// signal without minting a new key (which would bill it twice).
|
|
279
|
+
idempotencyKey: typeof item.body.idempotencyKey === "string" ? item.body.idempotencyKey : void 0
|
|
127
280
|
});
|
|
128
281
|
return true;
|
|
129
282
|
} catch (error) {
|
|
@@ -139,65 +292,10 @@ var Flusher = class {
|
|
|
139
292
|
async close() {
|
|
140
293
|
this.closed = true;
|
|
141
294
|
await this.flush();
|
|
295
|
+
this.clearTimer();
|
|
142
296
|
}
|
|
143
297
|
};
|
|
144
298
|
|
|
145
|
-
// src/errors.ts
|
|
146
|
-
var ClockNextError = class extends Error {
|
|
147
|
-
/** HTTP status, when the failure came from a server response. */
|
|
148
|
-
status;
|
|
149
|
-
/** Whether the SDK considers this failure worth retrying. */
|
|
150
|
-
retryable;
|
|
151
|
-
constructor(message, opts = {}) {
|
|
152
|
-
super(message);
|
|
153
|
-
this.name = new.target.name;
|
|
154
|
-
this.status = opts.status;
|
|
155
|
-
this.retryable = opts.retryable ?? false;
|
|
156
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
var AuthError = class extends ClockNextError {
|
|
160
|
-
};
|
|
161
|
-
var ValidationError = class extends ClockNextError {
|
|
162
|
-
};
|
|
163
|
-
var NotFoundError = class extends ClockNextError {
|
|
164
|
-
};
|
|
165
|
-
var PlanError = class extends ClockNextError {
|
|
166
|
-
};
|
|
167
|
-
var AllowanceError = class extends ClockNextError {
|
|
168
|
-
};
|
|
169
|
-
var ConflictError = class extends ClockNextError {
|
|
170
|
-
};
|
|
171
|
-
var RateLimitError = class extends ClockNextError {
|
|
172
|
-
retryAfterMs;
|
|
173
|
-
constructor(message, opts = {}) {
|
|
174
|
-
super(message, { status: 429, retryable: true });
|
|
175
|
-
this.retryAfterMs = opts.retryAfterMs;
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
var ServerError = class extends ClockNextError {
|
|
179
|
-
};
|
|
180
|
-
var NetworkError = class extends ClockNextError {
|
|
181
|
-
constructor(message) {
|
|
182
|
-
super(message, { retryable: true });
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
function errorFromResponse(status, message, retryAfterMs) {
|
|
186
|
-
const msg = message || `Request failed with status ${status}.`;
|
|
187
|
-
const opts = { status };
|
|
188
|
-
if (status === 400) return new ValidationError(msg, opts);
|
|
189
|
-
if (status === 401) return new AuthError(msg, opts);
|
|
190
|
-
if (status === 404) return new NotFoundError(msg, opts);
|
|
191
|
-
if (status === 408) return new ClockNextError(msg, { ...opts, retryable: true });
|
|
192
|
-
if (status === 409) return new ConflictError(msg, { ...opts, retryable: true });
|
|
193
|
-
if (status === 422) {
|
|
194
|
-
return /insufficient/i.test(msg) ? new AllowanceError(msg, opts) : new PlanError(msg, opts);
|
|
195
|
-
}
|
|
196
|
-
if (status === 429) return new RateLimitError(msg, { retryAfterMs });
|
|
197
|
-
if (status >= 500) return new ServerError(msg, { ...opts, retryable: true });
|
|
198
|
-
return new ClockNextError(msg, opts);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
299
|
// src/retry.ts
|
|
202
300
|
function isRetryable(error) {
|
|
203
301
|
return error instanceof ClockNextError && error.retryable;
|
|
@@ -228,7 +326,7 @@ function sleep(ms) {
|
|
|
228
326
|
}
|
|
229
327
|
|
|
230
328
|
// src/http.ts
|
|
231
|
-
var SDK_VERSION = "0.
|
|
329
|
+
var SDK_VERSION = "0.12.1" ;
|
|
232
330
|
var Transport = class {
|
|
233
331
|
constructor(cfg) {
|
|
234
332
|
this.cfg = cfg;
|
|
@@ -272,7 +370,7 @@ var Transport = class {
|
|
|
272
370
|
});
|
|
273
371
|
} catch (err) {
|
|
274
372
|
const message2 = controller.signal.aborted ? `Request to ${opts.path} timed out after ${this.cfg.timeoutMs}ms.` : `Network error calling ${opts.path}: ${err instanceof Error ? err.message : String(err)}`;
|
|
275
|
-
throw new NetworkError(message2);
|
|
373
|
+
throw new NetworkError(message2, { idempotencyKey: opts.idempotencyKey });
|
|
276
374
|
} finally {
|
|
277
375
|
clearTimeout(timer);
|
|
278
376
|
}
|
|
@@ -285,21 +383,30 @@ var Transport = class {
|
|
|
285
383
|
}
|
|
286
384
|
}
|
|
287
385
|
const obj = json ?? {};
|
|
288
|
-
const
|
|
289
|
-
|
|
386
|
+
const meta = { idempotencyKey: opts.idempotencyKey };
|
|
387
|
+
const isEnveloped = typeof obj === "object" && obj !== null && "statusDetail" in obj;
|
|
388
|
+
if (isEnveloped) {
|
|
290
389
|
const detail = obj.statusDetail;
|
|
291
390
|
if (res.ok && detail?.status === "SUCCESS") {
|
|
292
391
|
return obj.result ?? {};
|
|
293
392
|
}
|
|
294
393
|
const message2 = detail?.message ?? `Request failed with status ${res.status}.`;
|
|
295
|
-
throw errorFromResponse(res.status, message2, parseRetryAfter(res.headers)
|
|
394
|
+
throw errorFromResponse(res.status, message2, parseRetryAfter(res.headers), {
|
|
395
|
+
...meta,
|
|
396
|
+
code: detail?.code
|
|
397
|
+
});
|
|
296
398
|
}
|
|
297
399
|
const envelope = obj;
|
|
298
400
|
if (res.ok && envelope.ok !== false) {
|
|
299
401
|
return envelope;
|
|
300
402
|
}
|
|
301
403
|
const message = envelope.error ?? text ?? `Request failed with status ${res.status}.`;
|
|
302
|
-
throw errorFromResponse(
|
|
404
|
+
throw errorFromResponse(
|
|
405
|
+
res.status,
|
|
406
|
+
message,
|
|
407
|
+
parseRetryAfter(res.headers),
|
|
408
|
+
meta
|
|
409
|
+
);
|
|
303
410
|
}
|
|
304
411
|
headers(opts) {
|
|
305
412
|
const h = {
|
|
@@ -323,6 +430,14 @@ var Transport = class {
|
|
|
323
430
|
}
|
|
324
431
|
};
|
|
325
432
|
|
|
433
|
+
// src/filters.ts
|
|
434
|
+
function activeQuery(active) {
|
|
435
|
+
return active === true ? "true" : void 0;
|
|
436
|
+
}
|
|
437
|
+
function filterByActive(rows, active) {
|
|
438
|
+
return active === false ? rows.filter((row) => !row.isActive) : rows;
|
|
439
|
+
}
|
|
440
|
+
|
|
326
441
|
// src/resources/composites.ts
|
|
327
442
|
var Composites = class {
|
|
328
443
|
constructor(transport) {
|
|
@@ -331,16 +446,16 @@ var Composites = class {
|
|
|
331
446
|
transport;
|
|
332
447
|
/** List composites. Pass `{ active: true }` for only the ones a signal can
|
|
333
448
|
* still be tagged with — the default list INCLUDES archived ones, which
|
|
334
|
-
* keep every row they already own.
|
|
449
|
+
* keep every row they already own.
|
|
450
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
451
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
335
452
|
async list(params = {}) {
|
|
336
453
|
const res = await this.transport.request({
|
|
337
454
|
method: "GET",
|
|
338
455
|
path: "/api/v1/composites",
|
|
339
|
-
query: {
|
|
340
|
-
active: params.active === void 0 ? void 0 : String(params.active)
|
|
341
|
-
}
|
|
456
|
+
query: { active: activeQuery(params.active) }
|
|
342
457
|
});
|
|
343
|
-
return res.composites;
|
|
458
|
+
return filterByActive(res.composites, params.active);
|
|
344
459
|
}
|
|
345
460
|
/** Create a composite. `POST /api/v1/composites`. */
|
|
346
461
|
async create(input) {
|
|
@@ -359,14 +474,16 @@ var Credits = class {
|
|
|
359
474
|
this.transport = transport;
|
|
360
475
|
}
|
|
361
476
|
transport;
|
|
362
|
-
/** List credit types. Pass `{ active: true }` to only return active ones.
|
|
477
|
+
/** List credit types. Pass `{ active: true }` to only return active ones.
|
|
478
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
479
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
363
480
|
async list(params = {}) {
|
|
364
481
|
const res = await this.transport.request({
|
|
365
482
|
method: "GET",
|
|
366
483
|
path: "/api/v1/credits",
|
|
367
|
-
query: { active:
|
|
484
|
+
query: { active: activeQuery(params.active) }
|
|
368
485
|
});
|
|
369
|
-
return res.credits;
|
|
486
|
+
return filterByActive(res.credits, params.active);
|
|
370
487
|
}
|
|
371
488
|
/** Create a credit type; returns the created credit. `POST /api/v1/credits`. */
|
|
372
489
|
async create(input) {
|
|
@@ -657,14 +774,16 @@ var Outcomes = class {
|
|
|
657
774
|
this.transport = transport;
|
|
658
775
|
}
|
|
659
776
|
transport;
|
|
660
|
-
/** List outcome types. Pass `{ active: true }` to only return active ones.
|
|
777
|
+
/** List outcome types. Pass `{ active: true }` to only return active ones.
|
|
778
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
779
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
661
780
|
async list(params = {}) {
|
|
662
781
|
const res = await this.transport.request({
|
|
663
782
|
method: "GET",
|
|
664
783
|
path: "/api/v1/outcomes",
|
|
665
|
-
query: { active:
|
|
784
|
+
query: { active: activeQuery(params.active) }
|
|
666
785
|
});
|
|
667
|
-
return res.outcomes;
|
|
786
|
+
return filterByActive(res.outcomes, params.active);
|
|
668
787
|
}
|
|
669
788
|
/** Create an outcome type with its steps. `POST /api/v1/outcomes`. */
|
|
670
789
|
async create(input) {
|
|
@@ -740,14 +859,16 @@ var Plans = class {
|
|
|
740
859
|
this.transport = transport;
|
|
741
860
|
}
|
|
742
861
|
transport;
|
|
743
|
-
/** List plans, oldest-first. Pass `{ active: true }` to only return active plans.
|
|
862
|
+
/** List plans, oldest-first. Pass `{ active: true }` to only return active plans.
|
|
863
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
864
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
744
865
|
async list(params = {}) {
|
|
745
866
|
const res = await this.transport.request({
|
|
746
867
|
method: "GET",
|
|
747
868
|
path: "/api/v1/plans",
|
|
748
|
-
query: { active:
|
|
869
|
+
query: { active: activeQuery(params.active) }
|
|
749
870
|
});
|
|
750
|
-
return res.plans;
|
|
871
|
+
return filterByActive(res.plans, params.active);
|
|
751
872
|
}
|
|
752
873
|
/** Create a plan. `POST /api/v1/plans`. */
|
|
753
874
|
async create(input) {
|
|
@@ -945,22 +1066,27 @@ var Signals = class {
|
|
|
945
1066
|
*/
|
|
946
1067
|
async track(signal, opts = {}) {
|
|
947
1068
|
const body = signalToWire(signal);
|
|
948
|
-
|
|
1069
|
+
const idempotencyKey = typeof body.idempotencyKey === "string" ? body.idempotencyKey : newKey();
|
|
1070
|
+
body.idempotencyKey = idempotencyKey;
|
|
1071
|
+
const tracked = { ...signal, idempotencyKey };
|
|
949
1072
|
const path = SIGNAL_PATHS[signal.type];
|
|
950
1073
|
const sendNow = opts.wait || this.cfg.mode === "sync";
|
|
951
1074
|
if (!sendNow) {
|
|
952
|
-
this.flusher.enqueue({ path, body, signal });
|
|
953
|
-
return { queued: true };
|
|
1075
|
+
const buffered = this.flusher.enqueue({ path, body, signal: tracked });
|
|
1076
|
+
return buffered ? { status: "buffered", queued: true } : { status: "dropped", queued: false };
|
|
954
1077
|
}
|
|
955
1078
|
const res = await this.transport.request({
|
|
956
1079
|
method: "POST",
|
|
957
1080
|
path,
|
|
958
1081
|
body,
|
|
959
1082
|
// Best-effort metering opts into retries on transient failures.
|
|
960
|
-
retry: true
|
|
1083
|
+
retry: true,
|
|
1084
|
+
idempotencyKey
|
|
961
1085
|
});
|
|
1086
|
+
const queued = res.queued === true;
|
|
962
1087
|
return {
|
|
963
|
-
|
|
1088
|
+
status: queued ? "accepted" : "recorded",
|
|
1089
|
+
queued,
|
|
964
1090
|
usageLog: res.usageLog ?? null,
|
|
965
1091
|
...handles(res)
|
|
966
1092
|
};
|
|
@@ -1007,9 +1133,11 @@ var Signals = class {
|
|
|
1007
1133
|
* Branch on `queued`.
|
|
1008
1134
|
*/
|
|
1009
1135
|
async unit(input) {
|
|
1136
|
+
const idempotencyKey = input.idempotencyKey ?? newKey();
|
|
1010
1137
|
const res = await this.transport.request({
|
|
1011
1138
|
method: "POST",
|
|
1012
1139
|
path: UNIT_PATH,
|
|
1140
|
+
idempotencyKey,
|
|
1013
1141
|
body: {
|
|
1014
1142
|
customerId: input.customerId,
|
|
1015
1143
|
agentKey: input.agentKey,
|
|
@@ -1023,13 +1151,15 @@ var Signals = class {
|
|
|
1023
1151
|
// times. The route gained idempotency precisely to stop that; not
|
|
1024
1152
|
// using it by default would leave the double-charge reachable from the
|
|
1025
1153
|
// client instead of the server.
|
|
1026
|
-
idempotencyKey
|
|
1154
|
+
idempotencyKey
|
|
1027
1155
|
},
|
|
1028
1156
|
// Best-effort metering opts into retries on transient failures.
|
|
1029
1157
|
retry: true
|
|
1030
1158
|
});
|
|
1159
|
+
const queued = res.queued === true;
|
|
1031
1160
|
return {
|
|
1032
|
-
|
|
1161
|
+
status: queued ? "accepted" : "recorded",
|
|
1162
|
+
queued,
|
|
1033
1163
|
unitUsage: res.unitUsage ?? null,
|
|
1034
1164
|
...handles(res)
|
|
1035
1165
|
};
|
|
@@ -1045,9 +1175,13 @@ var Signals = class {
|
|
|
1045
1175
|
* arrive in any order and the occurrence stays open and unbilled until you
|
|
1046
1176
|
* say otherwise. Put it on the line before your own `return`.
|
|
1047
1177
|
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1178
|
+
* EVERY composite needs this, outcome-backed ones included. An outcome's own
|
|
1179
|
+
* `complete: true` closes its RUN and nothing else — it does not close the
|
|
1180
|
+
* composite occurrence the run was tagged into, whatever mix of credits,
|
|
1181
|
+
* outcomes and units that composite wraps. (Earlier versions of this SDK
|
|
1182
|
+
* documented the opposite. Believing it means the occurrence stays ONGOING
|
|
1183
|
+
* forever and is never billed, so if you tag outcome signals with a
|
|
1184
|
+
* `composite`, you must still call this.)
|
|
1051
1185
|
*
|
|
1052
1186
|
* Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
|
|
1053
1187
|
* cannot bill again. Calling it EARLY is also safe — before every entitlement
|
|
@@ -1056,9 +1190,11 @@ var Signals = class {
|
|
|
1056
1190
|
* rather than failing it.
|
|
1057
1191
|
*/
|
|
1058
1192
|
async completeComposite(input) {
|
|
1193
|
+
const idempotencyKey = input.idempotencyKey ?? newKey();
|
|
1059
1194
|
const res = await this.transport.request({
|
|
1060
1195
|
method: "POST",
|
|
1061
1196
|
path: COMPOSITE_COMPLETE_PATH,
|
|
1197
|
+
idempotencyKey,
|
|
1062
1198
|
body: {
|
|
1063
1199
|
customerId: input.customerId,
|
|
1064
1200
|
composite: { [input.composite.ref]: input.composite.value },
|
|
@@ -1067,12 +1203,14 @@ var Signals = class {
|
|
|
1067
1203
|
// no-op — but that safety comes from occurrence-level dedup, not from
|
|
1068
1204
|
// idempotency, and an un-keyed replay still writes a redundant inbox
|
|
1069
1205
|
// row. Keying it collapses the retry into the one attempt it is.
|
|
1070
|
-
idempotencyKey
|
|
1206
|
+
idempotencyKey
|
|
1071
1207
|
},
|
|
1072
1208
|
retry: true
|
|
1073
1209
|
});
|
|
1210
|
+
const queued = res.queued === true;
|
|
1074
1211
|
return {
|
|
1075
|
-
|
|
1212
|
+
delivery: queued ? "accepted" : "recorded",
|
|
1213
|
+
queued,
|
|
1076
1214
|
...res.status != null ? { status: res.status } : {},
|
|
1077
1215
|
...res.composite !== void 0 ? { composite: res.composite } : {},
|
|
1078
1216
|
...res.value !== void 0 ? { value: res.value } : {},
|
|
@@ -1100,14 +1238,16 @@ var Units = class {
|
|
|
1100
1238
|
this.transport = transport;
|
|
1101
1239
|
}
|
|
1102
1240
|
transport;
|
|
1103
|
-
/** List unit types. Pass `{ active: true }` to only return active ones.
|
|
1241
|
+
/** List unit types. Pass `{ active: true }` to only return active ones.
|
|
1242
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
1243
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
1104
1244
|
async list(params = {}) {
|
|
1105
1245
|
const res = await this.transport.request({
|
|
1106
1246
|
method: "GET",
|
|
1107
1247
|
path: "/api/v1/units-catalog",
|
|
1108
|
-
query: { active:
|
|
1248
|
+
query: { active: activeQuery(params.active) }
|
|
1109
1249
|
});
|
|
1110
|
-
return res.units;
|
|
1250
|
+
return filterByActive(res.units, params.active);
|
|
1111
1251
|
}
|
|
1112
1252
|
/** Create a unit type. `POST /api/v1/units-catalog`. */
|
|
1113
1253
|
async create(input) {
|
|
@@ -1199,16 +1339,16 @@ var Workspace = class {
|
|
|
1199
1339
|
});
|
|
1200
1340
|
}
|
|
1201
1341
|
/** The org's enabled models with live prices. `{ active: true }` returns only
|
|
1202
|
-
* the models that can be metered right now.
|
|
1342
|
+
* the models that can be metered right now.
|
|
1343
|
+
* `{ active: false }` returns only archived ones; omitting it returns
|
|
1344
|
+
* both. See `activeQuery` — the API itself only supports `true`. */
|
|
1203
1345
|
async models(params = {}) {
|
|
1204
1346
|
const res = await this.transport.request({
|
|
1205
1347
|
method: "GET",
|
|
1206
1348
|
path: "/api/v1/models",
|
|
1207
|
-
query: {
|
|
1208
|
-
active: params.active === void 0 ? void 0 : String(params.active)
|
|
1209
|
-
}
|
|
1349
|
+
query: { active: activeQuery(params.active) }
|
|
1210
1350
|
});
|
|
1211
|
-
return res.models;
|
|
1351
|
+
return filterByActive(res.models, params.active);
|
|
1212
1352
|
}
|
|
1213
1353
|
};
|
|
1214
1354
|
|
|
@@ -1279,6 +1419,6 @@ var ClockNext = class {
|
|
|
1279
1419
|
}
|
|
1280
1420
|
};
|
|
1281
1421
|
|
|
1282
|
-
export { AllowanceError, AuthError, ClockNext, ClockNextError, ConflictError, NetworkError, NotFoundError, PlanError, RateLimitError, ServerError, ValidationError, signalToWire };
|
|
1422
|
+
export { AllowanceError, AuthError, ClockNext, ClockNextError, ConflictError, ForbiddenError, NetworkError, NotFoundError, PlanError, RateLimitError, ServerError, ValidationError, signalToWire };
|
|
1283
1423
|
//# sourceMappingURL=index.js.map
|
|
1284
1424
|
//# sourceMappingURL=index.js.map
|