@clocknext/sdk 0.3.0 → 0.4.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/README.md +12 -9
- package/dist/index.cjs +71 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +171 -34
- package/dist/index.d.ts +171 -34
- package/dist/index.js +71 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,12 +90,16 @@ const cnk = new ClockNext({ apiKey, mode: "sync" });
|
|
|
90
90
|
- **Retries:** transient failures (network, timeout, `408/409/429/5xx`) are
|
|
91
91
|
retried with exponential backoff + equal jitter, honoring `Retry-After`.
|
|
92
92
|
Deterministic `4xx` (`400/401/404/422`) are never retried.
|
|
93
|
-
- **Replay safety:** reads (`GET`)
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
Other non-idempotent writes — e.g. `customers.create`, `plans.create`,
|
|
93
|
+
- **Replay safety:** reads (`GET`) and `DELETE` are retried automatically. Metering
|
|
94
|
+
signals (`signals.*`) opt into retries too — made safe by the idempotency key
|
|
95
|
+
below. Other non-idempotent writes — e.g. `customers.create`, `plans.create`,
|
|
97
96
|
`purchases.create` — are *not* auto-retried, so a transient blip can never
|
|
98
97
|
create a duplicate.
|
|
98
|
+
- **Idempotency:** every metering signal carries an idempotency key — auto-
|
|
99
|
+
generated per signal, or pass your own via `idempotencyKey` on the signal —
|
|
100
|
+
reused across all retries, so a retried send is **deduplicated by the server**
|
|
101
|
+
instead of double-counted. Pass a stable key (e.g. your own event id) to also
|
|
102
|
+
dedup across process restarts or at-least-once redelivery.
|
|
99
103
|
- **Flushing:** call `await cnk.flush()` before a serverless return, and
|
|
100
104
|
`await cnk.close()` on graceful shutdown to drain the buffer.
|
|
101
105
|
- **Durability:** the async buffer is **in-memory**. A signal already accepted
|
|
@@ -140,9 +144,8 @@ await cnk.customers.removeMember(c.id, members[0].id);
|
|
|
140
144
|
await cnk.customers.wallet(c.id, { limit: 50 }); // { balanceUsd, transactions }
|
|
141
145
|
await cnk.customers.addWalletEntry(c.id, { type: "CREDIT", amount: 25 }); // top up (DEBIT to spend)
|
|
142
146
|
|
|
143
|
-
// billing config
|
|
144
|
-
await cnk.customers.
|
|
145
|
-
await cnk.customers.setCreditTypes(c.id, ["cr_1", "cr_2"]);
|
|
147
|
+
// billing config — currency is set through update()
|
|
148
|
+
await cnk.customers.update(c.id, { currencyCode: "EUR" });
|
|
146
149
|
|
|
147
150
|
// grant / claw back balances (delta is signed)
|
|
148
151
|
await cnk.customers.adjustCredit(c.id, { creditId: "cr_1", delta: 100 });
|
|
@@ -200,7 +203,7 @@ await cnk.outcomes.create({
|
|
|
200
203
|
});
|
|
201
204
|
|
|
202
205
|
// unit type — a quantity-priced meter (FLAT / SLAB / VOLUME)
|
|
203
|
-
await cnk.units.create({ name: "Seats", pricingType: "FLAT", flatPrice: 5 });
|
|
206
|
+
await cnk.units.create({ name: "Seats", agentKey: "seats", pricingType: "FLAT", flatPrice: 5 });
|
|
204
207
|
```
|
|
205
208
|
|
|
206
209
|
### Unit metering
|
|
@@ -208,7 +211,7 @@ await cnk.units.create({ name: "Seats", pricingType: "FLAT", flatPrice: 5 });
|
|
|
208
211
|
Record a unit event (no tokens) and read a customer's unit balances:
|
|
209
212
|
|
|
210
213
|
```ts
|
|
211
|
-
await cnk.signals.unit({ customerId: c.id,
|
|
214
|
+
await cnk.signals.unit({ customerId: c.id, agentKey: "seats" });
|
|
212
215
|
await cnk.signals.unitUsage({ customerId: c.id });
|
|
213
216
|
```
|
|
214
217
|
|
package/dist/index.cjs
CHANGED
|
@@ -229,7 +229,7 @@ function sleep(ms) {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
// src/http.ts
|
|
232
|
-
var SDK_VERSION = "0.
|
|
232
|
+
var SDK_VERSION = "0.4.0" ;
|
|
233
233
|
var Transport = class {
|
|
234
234
|
constructor(cfg) {
|
|
235
235
|
this.cfg = cfg;
|
|
@@ -526,22 +526,9 @@ var Customers = class {
|
|
|
526
526
|
body: input
|
|
527
527
|
});
|
|
528
528
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
method: "PUT",
|
|
533
|
-
path: `/api/v1/customers/${encodeURIComponent(id)}/currency`,
|
|
534
|
-
body: { currencyCode }
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
/** Set which credit types the customer is billed against. `PUT …/:id/credits`. */
|
|
538
|
-
async setCreditTypes(id, creditIds) {
|
|
539
|
-
await this.transport.request({
|
|
540
|
-
method: "PUT",
|
|
541
|
-
path: `/api/v1/customers/${encodeURIComponent(id)}/credits`,
|
|
542
|
-
body: { creditIds }
|
|
543
|
-
});
|
|
544
|
-
}
|
|
529
|
+
// Note: a customer's currency is set through `update(id, { currencyCode })`
|
|
530
|
+
// (PATCH …/:id) — there is no separate currency endpoint. Credit-type
|
|
531
|
+
// assignment has no public API.
|
|
545
532
|
// --- Balance adjustments -------------------------------------------------
|
|
546
533
|
/** Grant or claw back a credit balance. `POST …/:id/credits/adjust`. */
|
|
547
534
|
async adjustCredit(id, input) {
|
|
@@ -830,6 +817,17 @@ var Purchases = class {
|
|
|
830
817
|
}
|
|
831
818
|
};
|
|
832
819
|
|
|
820
|
+
// src/idempotency.ts
|
|
821
|
+
function newKey() {
|
|
822
|
+
const c = globalThis.crypto;
|
|
823
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
824
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
|
|
825
|
+
const r = Math.random() * 16 | 0;
|
|
826
|
+
const v = ch === "x" ? r : r & 3 | 8;
|
|
827
|
+
return v.toString(16);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
833
831
|
// src/resources/signals.ts
|
|
834
832
|
function signalToWire(signal) {
|
|
835
833
|
const body = {
|
|
@@ -838,17 +836,15 @@ function signalToWire(signal) {
|
|
|
838
836
|
model: signal.model
|
|
839
837
|
};
|
|
840
838
|
if (signal.member) body.member = signal.member;
|
|
841
|
-
if (signal.status) body.status = signal.status;
|
|
842
839
|
if (signal.custom) body.custom = signal.custom;
|
|
843
840
|
const t = signal.tokens;
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (t.cache != null) body.cacheTokens = t.cache;
|
|
848
|
-
}
|
|
841
|
+
body.inputTokens = t.input;
|
|
842
|
+
body.outputTokens = t.output;
|
|
843
|
+
if (t.cache != null) body.cacheTokens = t.cache;
|
|
849
844
|
if (signal.type === "credit" || signal.type === "outcome") {
|
|
850
845
|
body.agentKey = signal.agentKey;
|
|
851
846
|
}
|
|
847
|
+
if (signal.idempotencyKey) body.idempotencyKey = signal.idempotencyKey;
|
|
852
848
|
return body;
|
|
853
849
|
}
|
|
854
850
|
var Signals = class {
|
|
@@ -862,6 +858,7 @@ var Signals = class {
|
|
|
862
858
|
cfg;
|
|
863
859
|
async track(signal, opts = {}) {
|
|
864
860
|
const body = signalToWire(signal);
|
|
861
|
+
if (body.idempotencyKey == null) body.idempotencyKey = newKey();
|
|
865
862
|
const sendNow = opts.wait || this.cfg.mode === "sync";
|
|
866
863
|
if (!sendNow) {
|
|
867
864
|
this.flusher.enqueue({ body, signal });
|
|
@@ -879,6 +876,23 @@ var Signals = class {
|
|
|
879
876
|
usageLog: res.usageLog ?? null
|
|
880
877
|
};
|
|
881
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
|
|
881
|
+
* the projected usage log so an integration can preflight before sending real
|
|
882
|
+
* traffic — no inbox row, wallet / outcome writes, or webhooks fire.
|
|
883
|
+
*/
|
|
884
|
+
async verify(signal) {
|
|
885
|
+
const body = signalToWire(signal);
|
|
886
|
+
body.dryRun = true;
|
|
887
|
+
const res = await this.transport.request({
|
|
888
|
+
method: "POST",
|
|
889
|
+
path: "/api/v1/usage",
|
|
890
|
+
body,
|
|
891
|
+
// A dry run writes nothing, so it is always safe to retry.
|
|
892
|
+
retry: true
|
|
893
|
+
});
|
|
894
|
+
return res.usageLog ?? null;
|
|
895
|
+
}
|
|
882
896
|
/** Meter a named credit. */
|
|
883
897
|
credit(input, opts) {
|
|
884
898
|
return this.track({ ...input, type: "credit" }, opts);
|
|
@@ -901,9 +915,9 @@ var Signals = class {
|
|
|
901
915
|
return { customer: res.customer, totals: res.totals, logs: res.logs };
|
|
902
916
|
}
|
|
903
917
|
/**
|
|
904
|
-
* Record one unit-metered event (a fixed-price meter with no tokens),
|
|
905
|
-
* to a catalog unit by
|
|
906
|
-
* `POST /api/v1/units`.
|
|
918
|
+
* Record one unit-metered event (a fixed-price meter with no tokens),
|
|
919
|
+
* addressed to a catalog unit by its `agentKey`. Sent immediately — unit
|
|
920
|
+
* events are not buffered. `POST /api/v1/units`.
|
|
907
921
|
*/
|
|
908
922
|
async unit(input) {
|
|
909
923
|
return this.transport.request({
|
|
@@ -911,7 +925,7 @@ var Signals = class {
|
|
|
911
925
|
path: "/api/v1/units",
|
|
912
926
|
body: {
|
|
913
927
|
customerId: input.customerId,
|
|
914
|
-
|
|
928
|
+
agentKey: input.agentKey,
|
|
915
929
|
...input.member ? { member: input.member } : {}
|
|
916
930
|
},
|
|
917
931
|
// Best-effort metering opts into retries on transient failures.
|
|
@@ -1019,6 +1033,33 @@ var Webhooks = class {
|
|
|
1019
1033
|
}
|
|
1020
1034
|
};
|
|
1021
1035
|
|
|
1036
|
+
// src/resources/workspace.ts
|
|
1037
|
+
var Workspace = class {
|
|
1038
|
+
constructor(transport) {
|
|
1039
|
+
this.transport = transport;
|
|
1040
|
+
}
|
|
1041
|
+
transport;
|
|
1042
|
+
/** Identify the org + key behind the request, incl. `sandbox` | `live`. */
|
|
1043
|
+
me() {
|
|
1044
|
+
return this.transport.request({
|
|
1045
|
+
method: "GET",
|
|
1046
|
+
path: "/api/v1/me"
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/** The org's enabled models with live prices. `{ active: true }` returns only
|
|
1050
|
+
* the models that can be metered right now. */
|
|
1051
|
+
async models(params = {}) {
|
|
1052
|
+
const res = await this.transport.request({
|
|
1053
|
+
method: "GET",
|
|
1054
|
+
path: "/api/v1/models",
|
|
1055
|
+
query: {
|
|
1056
|
+
active: params.active === void 0 ? void 0 : String(params.active)
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
return res.models;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1022
1063
|
// src/client.ts
|
|
1023
1064
|
var ClockNext = class {
|
|
1024
1065
|
/** Record + read usage signals (credit / wallet / outcome / unit). */
|
|
@@ -1043,6 +1084,8 @@ var ClockNext = class {
|
|
|
1043
1084
|
webhooks;
|
|
1044
1085
|
/** Mint customer-portal embed tokens. */
|
|
1045
1086
|
portal;
|
|
1087
|
+
/** Identify the workspace (sandbox/live) + list its models. `/me`, `/models`. */
|
|
1088
|
+
workspace;
|
|
1046
1089
|
cfg;
|
|
1047
1090
|
flusher;
|
|
1048
1091
|
constructor(config) {
|
|
@@ -1065,6 +1108,7 @@ var ClockNext = class {
|
|
|
1065
1108
|
this.units = new Units(transport);
|
|
1066
1109
|
this.webhooks = new Webhooks(transport);
|
|
1067
1110
|
this.portal = new Portal(transport);
|
|
1111
|
+
this.workspace = new Workspace(transport);
|
|
1068
1112
|
}
|
|
1069
1113
|
/** Number of signals currently buffered (async mode). */
|
|
1070
1114
|
get pending() {
|