@clocknext/sdk 0.8.0 → 0.11.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
@@ -119,7 +119,8 @@ var Flusher = class {
119
119
  try {
120
120
  await this.transport.request({
121
121
  method: "POST",
122
- path: "/api/v1/usage",
122
+ // Per-item, not per-Flusher: one route per meter. See `QueuedSignal`.
123
+ path: item.path,
123
124
  body: item.body,
124
125
  // Best-effort metering opts into retries on transient failures.
125
126
  retry: true
@@ -227,7 +228,7 @@ function sleep(ms) {
227
228
  }
228
229
 
229
230
  // src/http.ts
230
- var SDK_VERSION = "0.8.0" ;
231
+ var SDK_VERSION = "0.11.0" ;
231
232
  var Transport = class {
232
233
  constructor(cfg) {
233
234
  this.cfg = cfg;
@@ -322,6 +323,36 @@ var Transport = class {
322
323
  }
323
324
  };
324
325
 
326
+ // src/resources/composites.ts
327
+ var Composites = class {
328
+ constructor(transport) {
329
+ this.transport = transport;
330
+ }
331
+ transport;
332
+ /** List composites. Pass `{ active: true }` for only the ones a signal can
333
+ * still be tagged with — the default list INCLUDES archived ones, which
334
+ * keep every row they already own. */
335
+ async list(params = {}) {
336
+ const res = await this.transport.request({
337
+ method: "GET",
338
+ path: "/api/v1/composites",
339
+ query: {
340
+ active: params.active === void 0 ? void 0 : String(params.active)
341
+ }
342
+ });
343
+ return res.composites;
344
+ }
345
+ /** Create a composite. `POST /api/v1/composites`. */
346
+ async create(input) {
347
+ const res = await this.transport.request({
348
+ method: "POST",
349
+ path: "/api/v1/composites",
350
+ body: input
351
+ });
352
+ return res.composite;
353
+ }
354
+ };
355
+
325
356
  // src/resources/credits.ts
326
357
  var Credits = class {
327
358
  constructor(transport) {
@@ -504,7 +535,7 @@ var Customers = class {
504
535
  });
505
536
  return res.members;
506
537
  }
507
- /** Add a member to a customer. Duplicate `employeeId` → ConflictError. */
538
+ /** Add a member to a customer. Duplicate `email` → ConflictError. */
508
539
  async addMember(id, input) {
509
540
  const res = await this.transport.request({
510
541
  method: "POST",
@@ -513,7 +544,7 @@ var Customers = class {
513
544
  });
514
545
  return res.member;
515
546
  }
516
- /** Update a member's profile (role is fixed). `PATCH …/:id/members/:memberId`. */
547
+ /** Update a member's profile. Duplicate `email` → ConflictError. `PATCH …/:id/members/:memberId`. */
517
548
  async updateMember(id, memberId, input) {
518
549
  const res = await this.transport.request({
519
550
  method: "PATCH",
@@ -522,12 +553,13 @@ var Customers = class {
522
553
  });
523
554
  return res.member;
524
555
  }
525
- /** Remove a member from a customer. `DELETE …/:id/members/:memberId`. */
556
+ /** Remove a member from a customer, returning who was removed. `DELETE …/:id/members/:memberId`. */
526
557
  async removeMember(id, memberId) {
527
- await this.transport.request({
558
+ const res = await this.transport.request({
528
559
  method: "DELETE",
529
560
  path: `/api/v1/customers/${encodeURIComponent(id)}/members/${encodeURIComponent(memberId)}`
530
561
  });
562
+ return res.member;
531
563
  }
532
564
  // --- Wallet & billing config --------------------------------------------
533
565
  /** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
@@ -853,18 +885,28 @@ function newKey() {
853
885
  }
854
886
 
855
887
  // src/resources/signals.ts
888
+ var SIGNAL_PATHS = {
889
+ credit: "/api/v1/signal/credit",
890
+ outcome: "/api/v1/signal/outcome",
891
+ wallet: "/api/v1/signal/wallet"
892
+ };
893
+ var UNIT_PATH = "/api/v1/signal/unit";
894
+ var COMPOSITE_COMPLETE_PATH = "/api/v1/signal/composite/complete";
856
895
  function signalToWire(signal) {
896
+ const t = signal.tokens;
857
897
  const body = {
858
898
  customerId: signal.customerId,
859
- type: signal.type,
860
- model: signal.model
899
+ // `usage` is required on all three model-priced kinds. input/output are
900
+ // always sent; `cacheTokens` only when provided (the server defaults 0).
901
+ usage: {
902
+ model: signal.model,
903
+ inputTokens: t.input,
904
+ outputTokens: t.output,
905
+ ...t.cache != null ? { cacheTokens: t.cache } : {}
906
+ }
861
907
  };
862
908
  if (signal.member) body.member = signal.member;
863
909
  if (signal.custom) body.custom = signal.custom;
864
- const t = signal.tokens;
865
- body.inputTokens = t.input;
866
- body.outputTokens = t.output;
867
- if (t.cache != null) body.cacheTokens = t.cache;
868
910
  if (signal.type === "credit" || signal.type === "outcome") {
869
911
  body.agentKey = signal.agentKey;
870
912
  }
@@ -872,12 +914,18 @@ function signalToWire(signal) {
872
914
  body.runId = signal.runId;
873
915
  if (signal.complete != null) body.complete = signal.complete;
874
916
  }
875
- if (signal.type !== "wallet" && signal.metric) {
876
- body[signal.metric.ref] = signal.metric.value;
917
+ if (signal.type !== "wallet" && signal.composite) {
918
+ body.composite = { [signal.composite.ref]: signal.composite.value };
877
919
  }
878
920
  if (signal.idempotencyKey) body.idempotencyKey = signal.idempotencyKey;
879
921
  return body;
880
922
  }
923
+ function handles(res) {
924
+ return {
925
+ ...res.messageId != null ? { messageId: res.messageId } : {},
926
+ ...res.rawId != null ? { rawId: res.rawId } : {}
927
+ };
928
+ }
881
929
  var Signals = class {
882
930
  constructor(transport, flusher, cfg) {
883
931
  this.transport = transport;
@@ -888,7 +936,8 @@ var Signals = class {
888
936
  flusher;
889
937
  cfg;
890
938
  /**
891
- * Record one usage signal. `POST /api/v1/usage`.
939
+ * Record one usage signal, to the route its `type` selects:
940
+ * `POST /api/v1/signal/credit` | `/signal/outcome` | `/signal/wallet`.
892
941
  *
893
942
  * Returns as soon as the signal is buffered (async mode) or the request has
894
943
  * been answered (`{ wait: true }` / sync mode). See {@link TrackResult}: a
@@ -897,14 +946,15 @@ var Signals = class {
897
946
  async track(signal, opts = {}) {
898
947
  const body = signalToWire(signal);
899
948
  if (body.idempotencyKey == null) body.idempotencyKey = newKey();
949
+ const path = SIGNAL_PATHS[signal.type];
900
950
  const sendNow = opts.wait || this.cfg.mode === "sync";
901
951
  if (!sendNow) {
902
- this.flusher.enqueue({ body, signal });
952
+ this.flusher.enqueue({ path, body, signal });
903
953
  return { queued: true };
904
954
  }
905
955
  const res = await this.transport.request({
906
956
  method: "POST",
907
- path: "/api/v1/usage",
957
+ path,
908
958
  body,
909
959
  // Best-effort metering opts into retries on transient failures.
910
960
  retry: true
@@ -912,39 +962,30 @@ var Signals = class {
912
962
  return {
913
963
  queued: res.queued === true,
914
964
  usageLog: res.usageLog ?? null,
915
- ...res.messageId != null ? { messageId: res.messageId } : {}
965
+ ...handles(res)
916
966
  };
917
967
  }
918
- /**
919
- * Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
920
- * the projected usage log so an integration can preflight before sending real
921
- * traffic — no inbox row, wallet / outcome writes, or webhooks fire.
922
- */
923
- async verify(signal) {
924
- const body = signalToWire(signal);
925
- body.dryRun = true;
926
- const res = await this.transport.request({
927
- method: "POST",
928
- path: "/api/v1/usage",
929
- body,
930
- // A dry run writes nothing, so it is always safe to retry.
931
- retry: true
932
- });
933
- return res.usageLog ?? null;
934
- }
935
- /** Meter a named credit. */
968
+ /** Meter a named credit. `POST /api/v1/signal/credit`. */
936
969
  credit(input, opts) {
937
970
  return this.track({ ...input, type: "credit" }, opts);
938
971
  }
939
- /** Debit the customer's wallet at model cost. */
972
+ /** Debit the customer's wallet at model cost. `POST /api/v1/signal/wallet`. */
940
973
  wallet(input, opts) {
941
974
  return this.track({ ...input, type: "wallet" }, opts);
942
975
  }
943
- /** Advance one step of an outcome workflow. */
976
+ /** Advance one step of an outcome workflow. `POST /api/v1/signal/outcome`. */
944
977
  outcome(input, opts) {
945
978
  return this.track({ ...input, type: "outcome" }, opts);
946
979
  }
947
- /** Recent usage logs + totals for a customer. `GET /api/v1/usage`. */
980
+ /**
981
+ * Recent usage logs + totals for a customer. `GET /api/v1/usage`.
982
+ *
983
+ * Still the legacy path, deliberately. The `/api/v1/signal/*` family is
984
+ * POST-only — it replaced how usage is INGESTED, not how it is read — so
985
+ * `GET /api/v1/usage` has no successor and is not deprecated. It is also the
986
+ * only way to prove a signal landed, which matters more now that ingestion
987
+ * answers `202` for everything.
988
+ */
948
989
  async list(params) {
949
990
  const res = await this.transport.request({
950
991
  method: "GET",
@@ -956,22 +997,94 @@ var Signals = class {
956
997
  /**
957
998
  * Record one unit-metered event (a fixed-price meter with no tokens),
958
999
  * addressed to a catalog unit by its `agentKey`. Sent immediately — unit
959
- * events are not buffered. `POST /api/v1/units`.
1000
+ * events are not buffered. `POST /api/v1/signal/unit`.
1001
+ *
1002
+ * Two things changed with the move off `POST /api/v1/units`, and both show up
1003
+ * in the return type. The route is now durable (the report lands in an inbox
1004
+ * before it is priced, so a failure parks as Pending and can be retried
1005
+ * instead of losing a billable event) and it answers `202` when it queues —
1006
+ * so this resolves {@link UnitTrackResult}, not a bare `{ unitUsage }`.
1007
+ * Branch on `queued`.
960
1008
  */
961
1009
  async unit(input) {
962
- return this.transport.request({
1010
+ const res = await this.transport.request({
963
1011
  method: "POST",
964
- path: "/api/v1/units",
1012
+ path: UNIT_PATH,
965
1013
  body: {
966
1014
  customerId: input.customerId,
967
1015
  agentKey: input.agentKey,
968
- ...input.member ? { member: input.member } : {}
1016
+ ...input.quantity != null ? { quantity: input.quantity } : {},
1017
+ ...input.member ? { member: input.member } : {},
1018
+ ...input.composite ? { composite: { [input.composite.ref]: input.composite.value } } : {},
1019
+ // Assigned HERE, before the send, so the SAME key rides every
1020
+ // transport-level retry below. Without it a single 500 followed by a
1021
+ // success is TWO un-keyed reports, and the server dedups on the key —
1022
+ // so it bills the allowance down twice, up to `retry.maxAttempts`
1023
+ // times. The route gained idempotency precisely to stop that; not
1024
+ // using it by default would leave the double-charge reachable from the
1025
+ // client instead of the server.
1026
+ idempotencyKey: input.idempotencyKey ?? newKey()
969
1027
  },
970
1028
  // Best-effort metering opts into retries on transient failures.
971
1029
  retry: true
972
1030
  });
1031
+ return {
1032
+ queued: res.queued === true,
1033
+ unitUsage: res.unitUsage ?? null,
1034
+ ...handles(res)
1035
+ };
973
1036
  }
974
- /** A customer's unit balances + recent unit events. `GET /api/v1/units`. */
1037
+ /**
1038
+ * Close ONE occurrence of a composite — a distinct tag value.
1039
+ * `POST /api/v1/signal/composite/complete`.
1040
+ *
1041
+ * A composite bundles several credits / outcomes / units and is billed as one
1042
+ * thing. The occurrence opens on the first tagged signal and costs nothing;
1043
+ * this call is the moment it is billed. Only your code knows when the
1044
+ * workflow ended, so this is a DECLARATION, not a hint: tagged signals may
1045
+ * arrive in any order and the occurrence stays open and unbilled until you
1046
+ * say otherwise. Put it on the line before your own `return`.
1047
+ *
1048
+ * An OUTCOME never needs this — its own `complete: true` closes its run and
1049
+ * the matching occurrence together. Credits and units have no "done" signal
1050
+ * of their own, so this is theirs.
1051
+ *
1052
+ * Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
1053
+ * cannot bill again. Calling it EARLY is also safe — before every entitlement
1054
+ * the composite is restricted to has reported, or while an ADVANCE pool is
1055
+ * full — the server parks the ping and applies it when the blocker clears,
1056
+ * rather than failing it.
1057
+ */
1058
+ async completeComposite(input) {
1059
+ const res = await this.transport.request({
1060
+ method: "POST",
1061
+ path: COMPOSITE_COMPLETE_PATH,
1062
+ body: {
1063
+ customerId: input.customerId,
1064
+ composite: { [input.composite.ref]: input.composite.value },
1065
+ // Auto-assigned like the others. A retry here could not double-BILL
1066
+ // even without it — closing a closed occurrence is a server-side
1067
+ // no-op — but that safety comes from occurrence-level dedup, not from
1068
+ // idempotency, and an un-keyed replay still writes a redundant inbox
1069
+ // row. Keying it collapses the retry into the one attempt it is.
1070
+ idempotencyKey: input.idempotencyKey ?? newKey()
1071
+ },
1072
+ retry: true
1073
+ });
1074
+ return {
1075
+ queued: res.queued === true,
1076
+ ...res.status != null ? { status: res.status } : {},
1077
+ ...res.composite !== void 0 ? { composite: res.composite } : {},
1078
+ ...res.value !== void 0 ? { value: res.value } : {},
1079
+ ...handles(res)
1080
+ };
1081
+ }
1082
+ /**
1083
+ * A customer's unit balances + recent unit events. `GET /api/v1/units`.
1084
+ *
1085
+ * Legacy path for the same reason as {@link list}: the signal family is
1086
+ * POST-only, so the read side has no successor and is not deprecated.
1087
+ */
975
1088
  unitUsage(params) {
976
1089
  return this.transport.request({
977
1090
  method: "GET",
@@ -1115,6 +1228,8 @@ var ClockNext = class {
1115
1228
  payments;
1116
1229
  /** Manage the credit-type catalog. */
1117
1230
  credits;
1231
+ /** Manage the composite catalog — bundles billed as one thing. */
1232
+ composites;
1118
1233
  /** Manage the outcome-type catalog. */
1119
1234
  outcomes;
1120
1235
  /** Manage the unit-type catalog. */
@@ -1143,6 +1258,7 @@ var ClockNext = class {
1143
1258
  this.invoices = new Invoices(transport);
1144
1259
  this.payments = new Payments(transport);
1145
1260
  this.credits = new Credits(transport);
1261
+ this.composites = new Composites(transport);
1146
1262
  this.outcomes = new Outcomes(transport);
1147
1263
  this.units = new Units(transport);
1148
1264
  this.webhooks = new Webhooks(transport);