@clocknext/sdk 0.3.0 → 0.5.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/dist/index.js CHANGED
@@ -227,7 +227,7 @@ function sleep(ms) {
227
227
  }
228
228
 
229
229
  // src/http.ts
230
- var SDK_VERSION = "0.3.0" ;
230
+ var SDK_VERSION = "0.5.0" ;
231
231
  var Transport = class {
232
232
  constructor(cfg) {
233
233
  this.cfg = cfg;
@@ -337,13 +337,14 @@ var Credits = class {
337
337
  });
338
338
  return res.credits;
339
339
  }
340
- /** Create a credit type. `POST /api/v1/credits`. */
340
+ /** Create a credit type; returns the created credit. `POST /api/v1/credits`. */
341
341
  async create(input) {
342
- await this.transport.request({
342
+ const res = await this.transport.request({
343
343
  method: "POST",
344
344
  path: "/api/v1/credits",
345
345
  body: input
346
346
  });
347
+ return res.credit;
347
348
  }
348
349
  /** Fetch one credit type with usage stats. `GET /api/v1/credits/:id`. */
349
350
  async get(id) {
@@ -353,13 +354,14 @@ var Credits = class {
353
354
  });
354
355
  return res.credit;
355
356
  }
356
- /** Update a credit type's definition. `PATCH /api/v1/credits/:id`. */
357
+ /** Update a credit type's definition; returns the updated credit. `PATCH /api/v1/credits/:id`. */
357
358
  async update(id, input) {
358
- await this.transport.request({
359
+ const res = await this.transport.request({
359
360
  method: "PATCH",
360
361
  path: `/api/v1/credits/${encodeURIComponent(id)}`,
361
362
  body: input
362
363
  });
364
+ return res.credit;
363
365
  }
364
366
  /** Activate or deactivate a credit type. */
365
367
  async setActive(id, isActive) {
@@ -439,12 +441,12 @@ var Customers = class {
439
441
  path: `/api/v1/customers/${encodeURIComponent(id)}`
440
442
  });
441
443
  }
442
- /** A customer's usage history. `GET /api/v1/customers/:id/usage`. */
444
+ /** A customer's usage history. `GET /api/v1/usage?customerId=`. */
443
445
  async usage(id, params = {}) {
444
446
  const res = await this.transport.request({
445
447
  method: "GET",
446
- path: `/api/v1/customers/${encodeURIComponent(id)}/usage`,
447
- query: { limit: params.limit }
448
+ path: `/api/v1/usage`,
449
+ query: { customerId: id, limit: params.limit }
448
450
  });
449
451
  return { customer: res.customer, totals: res.totals, logs: res.logs };
450
452
  }
@@ -455,6 +457,37 @@ var Customers = class {
455
457
  path: `/api/v1/customers/${encodeURIComponent(id)}/balances`
456
458
  });
457
459
  }
460
+ /**
461
+ * One credit balance for a customer, matched by `creditId` or `creditName`;
462
+ * `null` when the customer has no plan or no matching credit. Convenience
463
+ * over `balances()` + `Array.find` — it fetches the FULL balances under the
464
+ * hood (there is no server-side scoped read), so prefer `balances()` when you
465
+ * need several rows from the same customer.
466
+ */
467
+ async creditBalance(customerId, match) {
468
+ const { credits } = await this.balances(customerId);
469
+ return credits.find(
470
+ (c) => match.creditId != null && c.creditId === match.creditId || match.creditName != null && c.creditName === match.creditName
471
+ ) ?? null;
472
+ }
473
+ /** One outcome balance for a customer, matched by `outcomeId` or
474
+ * `outcomeName`; `null` when absent. See {@link creditBalance} for the
475
+ * fetch-then-filter caveat. */
476
+ async outcomeBalance(customerId, match) {
477
+ const { outcomes } = await this.balances(customerId);
478
+ return outcomes.find(
479
+ (o) => match.outcomeId != null && o.outcomeId === match.outcomeId || match.outcomeName != null && o.outcomeName === match.outcomeName
480
+ ) ?? null;
481
+ }
482
+ /** One unit balance for a customer, matched by `unitId` or `unitName`;
483
+ * `null` when absent. See {@link creditBalance} for the fetch-then-filter
484
+ * caveat. */
485
+ async unitBalance(customerId, match) {
486
+ const { units } = await this.balances(customerId);
487
+ return units.find(
488
+ (u) => match.unitId != null && u.unitId === match.unitId || match.unitName != null && u.unitName === match.unitName
489
+ ) ?? null;
490
+ }
458
491
  /** The customer's current plan. `GET …/:id/plan`. */
459
492
  plan(id) {
460
493
  return this.transport.request({
@@ -462,14 +495,6 @@ var Customers = class {
462
495
  path: `/api/v1/customers/${encodeURIComponent(id)}/plan`
463
496
  });
464
497
  }
465
- /** Revenue / cost / profit / margin over a window. `GET …/:id/revenue`. */
466
- revenue(id, params = {}) {
467
- return this.transport.request({
468
- method: "GET",
469
- path: `/api/v1/customers/${encodeURIComponent(id)}/revenue`,
470
- query: { range: params.range }
471
- });
472
- }
473
498
  // --- Members -------------------------------------------------------------
474
499
  /** List a customer's members. `GET …/:id/members`. */
475
500
  async listMembers(id) {
@@ -524,22 +549,9 @@ var Customers = class {
524
549
  body: input
525
550
  });
526
551
  }
527
- /** Set the customer's currency. `PUT …/:id/currency`. */
528
- async setCurrency(id, currencyCode) {
529
- await this.transport.request({
530
- method: "PUT",
531
- path: `/api/v1/customers/${encodeURIComponent(id)}/currency`,
532
- body: { currencyCode }
533
- });
534
- }
535
- /** Set which credit types the customer is billed against. `PUT …/:id/credits`. */
536
- async setCreditTypes(id, creditIds) {
537
- await this.transport.request({
538
- method: "PUT",
539
- path: `/api/v1/customers/${encodeURIComponent(id)}/credits`,
540
- body: { creditIds }
541
- });
542
- }
552
+ // Note: a customer's currency is set through `update(id, { currencyCode })`
553
+ // (PATCH …/:id) — there is no separate currency endpoint. Credit-type
554
+ // assignment has no public API.
543
555
  // --- Balance adjustments -------------------------------------------------
544
556
  /** Grant or claw back a credit balance. `POST …/:id/credits/adjust`. */
545
557
  async adjustCredit(id, input) {
@@ -648,13 +660,14 @@ var Outcomes = class {
648
660
  });
649
661
  return res.outcome;
650
662
  }
651
- /** Activate or deactivate an outcome type. */
663
+ /** Activate or deactivate an outcome type; returns the updated outcome. */
652
664
  async setActive(id, isActive) {
653
- await this.transport.request({
665
+ const res = await this.transport.request({
654
666
  method: "PATCH",
655
667
  path: `/api/v1/outcomes/${encodeURIComponent(id)}`,
656
668
  body: { isActive }
657
669
  });
670
+ return res.outcome;
658
671
  }
659
672
  /** Delete an outcome type. */
660
673
  async delete(id) {
@@ -828,6 +841,17 @@ var Purchases = class {
828
841
  }
829
842
  };
830
843
 
844
+ // src/idempotency.ts
845
+ function newKey() {
846
+ const c = globalThis.crypto;
847
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
848
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
849
+ const r = Math.random() * 16 | 0;
850
+ const v = ch === "x" ? r : r & 3 | 8;
851
+ return v.toString(16);
852
+ });
853
+ }
854
+
831
855
  // src/resources/signals.ts
832
856
  function signalToWire(signal) {
833
857
  const body = {
@@ -836,17 +860,15 @@ function signalToWire(signal) {
836
860
  model: signal.model
837
861
  };
838
862
  if (signal.member) body.member = signal.member;
839
- if (signal.status) body.status = signal.status;
840
863
  if (signal.custom) body.custom = signal.custom;
841
864
  const t = signal.tokens;
842
- if (t) {
843
- if (t.input != null) body.inputTokens = t.input;
844
- if (t.output != null) body.outputTokens = t.output;
845
- if (t.cache != null) body.cacheTokens = t.cache;
846
- }
865
+ body.inputTokens = t.input;
866
+ body.outputTokens = t.output;
867
+ if (t.cache != null) body.cacheTokens = t.cache;
847
868
  if (signal.type === "credit" || signal.type === "outcome") {
848
869
  body.agentKey = signal.agentKey;
849
870
  }
871
+ if (signal.idempotencyKey) body.idempotencyKey = signal.idempotencyKey;
850
872
  return body;
851
873
  }
852
874
  var Signals = class {
@@ -860,6 +882,7 @@ var Signals = class {
860
882
  cfg;
861
883
  async track(signal, opts = {}) {
862
884
  const body = signalToWire(signal);
885
+ if (body.idempotencyKey == null) body.idempotencyKey = newKey();
863
886
  const sendNow = opts.wait || this.cfg.mode === "sync";
864
887
  if (!sendNow) {
865
888
  this.flusher.enqueue({ body, signal });
@@ -877,6 +900,23 @@ var Signals = class {
877
900
  usageLog: res.usageLog ?? null
878
901
  };
879
902
  }
903
+ /**
904
+ * Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
905
+ * the projected usage log so an integration can preflight before sending real
906
+ * traffic — no inbox row, wallet / outcome writes, or webhooks fire.
907
+ */
908
+ async verify(signal) {
909
+ const body = signalToWire(signal);
910
+ body.dryRun = true;
911
+ const res = await this.transport.request({
912
+ method: "POST",
913
+ path: "/api/v1/usage",
914
+ body,
915
+ // A dry run writes nothing, so it is always safe to retry.
916
+ retry: true
917
+ });
918
+ return res.usageLog ?? null;
919
+ }
880
920
  /** Meter a named credit. */
881
921
  credit(input, opts) {
882
922
  return this.track({ ...input, type: "credit" }, opts);
@@ -899,9 +939,9 @@ var Signals = class {
899
939
  return { customer: res.customer, totals: res.totals, logs: res.logs };
900
940
  }
901
941
  /**
902
- * Record one unit-metered event (a fixed-price meter with no tokens), matched
903
- * to a catalog unit by name. Sent immediately — unit events are not buffered.
904
- * `POST /api/v1/units`.
942
+ * Record one unit-metered event (a fixed-price meter with no tokens),
943
+ * addressed to a catalog unit by its `agentKey`. Sent immediately — unit
944
+ * events are not buffered. `POST /api/v1/units`.
905
945
  */
906
946
  async unit(input) {
907
947
  return this.transport.request({
@@ -909,7 +949,7 @@ var Signals = class {
909
949
  path: "/api/v1/units",
910
950
  body: {
911
951
  customerId: input.customerId,
912
- unit: input.unit,
952
+ agentKey: input.agentKey,
913
953
  ...input.member ? { member: input.member } : {}
914
954
  },
915
955
  // Best-effort metering opts into retries on transient failures.
@@ -1017,6 +1057,33 @@ var Webhooks = class {
1017
1057
  }
1018
1058
  };
1019
1059
 
1060
+ // src/resources/workspace.ts
1061
+ var Workspace = class {
1062
+ constructor(transport) {
1063
+ this.transport = transport;
1064
+ }
1065
+ transport;
1066
+ /** Identify the org + key behind the request, incl. `sandbox` | `live`. */
1067
+ me() {
1068
+ return this.transport.request({
1069
+ method: "GET",
1070
+ path: "/api/v1/me"
1071
+ });
1072
+ }
1073
+ /** The org's enabled models with live prices. `{ active: true }` returns only
1074
+ * the models that can be metered right now. */
1075
+ async models(params = {}) {
1076
+ const res = await this.transport.request({
1077
+ method: "GET",
1078
+ path: "/api/v1/models",
1079
+ query: {
1080
+ active: params.active === void 0 ? void 0 : String(params.active)
1081
+ }
1082
+ });
1083
+ return res.models;
1084
+ }
1085
+ };
1086
+
1020
1087
  // src/client.ts
1021
1088
  var ClockNext = class {
1022
1089
  /** Record + read usage signals (credit / wallet / outcome / unit). */
@@ -1041,6 +1108,8 @@ var ClockNext = class {
1041
1108
  webhooks;
1042
1109
  /** Mint customer-portal embed tokens. */
1043
1110
  portal;
1111
+ /** Identify the workspace (sandbox/live) + list its models. `/me`, `/models`. */
1112
+ workspace;
1044
1113
  cfg;
1045
1114
  flusher;
1046
1115
  constructor(config) {
@@ -1063,6 +1132,7 @@ var ClockNext = class {
1063
1132
  this.units = new Units(transport);
1064
1133
  this.webhooks = new Webhooks(transport);
1065
1134
  this.portal = new Portal(transport);
1135
+ this.workspace = new Workspace(transport);
1066
1136
  }
1067
1137
  /** Number of signals currently buffered (async mode). */
1068
1138
  get pending() {