verdifax 1.2.0 → 1.3.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.mjs CHANGED
@@ -624,7 +624,7 @@ var VerdifaxJob = class {
624
624
  };
625
625
 
626
626
  // src/version.ts
627
- var SDK_VERSION = "1.2.0";
627
+ var SDK_VERSION = "1.3.0";
628
628
 
629
629
  // src/client.ts
630
630
  var DEFAULT_BASE_URL = "https://api.verdifax.com";
@@ -635,6 +635,10 @@ var SDK_USER_AGENT = `verdifax-sdk-node/${SDK_VERSION}`;
635
635
  var HEADER_API_KEY = "X-Verdifax-Key";
636
636
  var REQUEST_KEY_PATH = "/keys/request-key";
637
637
  var POLICIES_PATH = "/policies";
638
+ var ACCOUNT_PATH = "/account";
639
+ var ACCOUNT_REQUEST_KEY_PATH = "/account/request-key";
640
+ var ACCOUNT_POLICIES_PATH = "/account/policies";
641
+ var ACCOUNT_CHAIN_ID = /^account-[1-9][0-9]*$/;
638
642
  var HEADER_EXPORT_TOTAL = "X-Verdifax-Total";
639
643
  var HEADER_EXPORT_TRUNCATED = "X-Verdifax-Truncated";
640
644
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
@@ -820,15 +824,18 @@ var VerdifaxClient = class {
820
824
  * that the client holds the private key. From then on the server
821
825
  * refuses a run on this API key without a request signature. */
822
826
  async registerRequestKey() {
827
+ return this.registerRequestKeyAt(REQUEST_KEY_PATH, "registerRequestKey");
828
+ }
829
+ async registerRequestKeyAt(path, method) {
823
830
  this.requireAPIKey();
824
831
  if (!this.signingKey) {
825
832
  throw new VerdifaxError(
826
- "verdifax: registerRequestKey needs the signingKey client option"
833
+ `verdifax: ${method} needs the signingKey client option`
827
834
  );
828
835
  }
829
836
  return this.request(
830
837
  "POST",
831
- REQUEST_KEY_PATH,
838
+ path,
832
839
  requestKeyRegistrationBody(this.signingKey)
833
840
  );
834
841
  }
@@ -867,10 +874,13 @@ var VerdifaxClient = class {
867
874
  * unsigned requests, because the server refuses a signed run on an API
868
875
  * key with no registered key. If the call fails it keeps the key. */
869
876
  async clearRequestKey() {
877
+ await this.clearRequestKeyAt(REQUEST_KEY_PATH);
878
+ }
879
+ async clearRequestKeyAt(path) {
870
880
  this.requireAPIKey();
871
881
  await this.request(
872
882
  "DELETE",
873
- REQUEST_KEY_PATH,
883
+ path,
874
884
  this.signingKey ? { change: requestKeyChangeApproval(this.signingKey, "") } : void 0
875
885
  );
876
886
  this.signingKey = void 0;
@@ -882,13 +892,7 @@ var VerdifaxClient = class {
882
892
  * not a string: serializing an object here would change the hash.
883
893
  * Verdifax does not write, review or approve a customer's policy. */
884
894
  async uploadPolicy(document) {
885
- this.requireAPIKey();
886
- if (typeof document !== "string") {
887
- throw new VerdifaxError(
888
- "verdifax: document must be the policy's exact text, as a string"
889
- );
890
- }
891
- return this.request("POST", POLICIES_PATH, { document });
895
+ return this.uploadPolicyAt(POLICIES_PATH, document);
892
896
  }
893
897
  /** This key's stored documents, with the chain its changes belong to,
894
898
  * the newest change in it (64 zeros when there is none yet) and the
@@ -899,44 +903,13 @@ var VerdifaxClient = class {
899
903
  }
900
904
  /** One stored document, with its exact text as `document`. */
901
905
  async getPolicy(documentHash) {
902
- this.requireAPIKey();
903
- validateHex64(documentHash, "documentHash");
904
- return this.request(
905
- "GET",
906
- `${POLICIES_PATH}/${documentHash}`
907
- );
906
+ return this.getPolicyAt(POLICIES_PATH, documentHash);
908
907
  }
909
908
  /** Shows which rule would fire on sample inputs, for a document given as
910
909
  * text or one already stored, named by its hash: exactly one of the two.
911
910
  * It seals nothing. */
912
911
  async testPolicy(req) {
913
- this.requireAPIKey();
914
- const { inputs, document, documentHash } = req ?? {};
915
- if (document === void 0 === (documentHash === void 0)) {
916
- throw new VerdifaxError(
917
- "verdifax: testPolicy needs exactly one of document or documentHash"
918
- );
919
- }
920
- if (typeof inputs !== "object" || inputs === null || Array.isArray(inputs)) {
921
- throw new VerdifaxError("verdifax: testPolicy needs inputs as an object");
922
- }
923
- const body = { inputs };
924
- if (document !== void 0) {
925
- if (typeof document !== "string") {
926
- throw new VerdifaxError(
927
- "verdifax: document must be the policy's exact text, as a string"
928
- );
929
- }
930
- body.document = document;
931
- } else {
932
- validateHex64(documentHash, "documentHash");
933
- body.document_hash = documentHash;
934
- }
935
- return this.request(
936
- "POST",
937
- `${POLICIES_PATH}/test`,
938
- body
939
- );
912
+ return this.testPolicyAt(POLICIES_PATH, req, "testPolicy");
940
913
  }
941
914
  /** This key's policy changes, oldest first, as the list the server sends.
942
915
  * Save it as JSON, for example with
@@ -962,7 +935,54 @@ var VerdifaxClient = class {
962
935
  async activatePolicy(documentHash) {
963
936
  this.requireAPIKey();
964
937
  validateHex64(documentHash, "documentHash");
965
- const chain = await this.listPolicies();
938
+ return this.activatePolicyAt(
939
+ POLICIES_PATH,
940
+ documentHash,
941
+ await this.listPolicies()
942
+ );
943
+ }
944
+ async uploadPolicyAt(base, document) {
945
+ this.requireAPIKey();
946
+ if (typeof document !== "string") {
947
+ throw new VerdifaxError(
948
+ "verdifax: document must be the policy's exact text, as a string"
949
+ );
950
+ }
951
+ return this.request("POST", base, { document });
952
+ }
953
+ async getPolicyAt(base, documentHash) {
954
+ this.requireAPIKey();
955
+ validateHex64(documentHash, "documentHash");
956
+ return this.request("GET", `${base}/${documentHash}`);
957
+ }
958
+ async testPolicyAt(base, req, method) {
959
+ this.requireAPIKey();
960
+ const { inputs, document, documentHash } = req ?? {};
961
+ if (document === void 0 === (documentHash === void 0)) {
962
+ throw new VerdifaxError(
963
+ `verdifax: ${method} needs exactly one of document or documentHash`
964
+ );
965
+ }
966
+ if (typeof inputs !== "object" || inputs === null || Array.isArray(inputs)) {
967
+ throw new VerdifaxError(`verdifax: ${method} needs inputs as an object`);
968
+ }
969
+ const body = { inputs };
970
+ if (document !== void 0) {
971
+ if (typeof document !== "string") {
972
+ throw new VerdifaxError(
973
+ "verdifax: document must be the policy's exact text, as a string"
974
+ );
975
+ }
976
+ body.document = document;
977
+ } else {
978
+ validateHex64(documentHash, "documentHash");
979
+ body.document_hash = documentHash;
980
+ }
981
+ return this.request("POST", `${base}/test`, body);
982
+ }
983
+ /** Posts the change that follows the newest one in `chain`, signed by
984
+ * the request key when the client has one. */
985
+ async activatePolicyAt(base, documentHash, chain) {
966
986
  const previous = chain?.newest_activation_hash;
967
987
  if (typeof previous !== "string") {
968
988
  throw new VerdifaxError(
@@ -984,11 +1004,147 @@ var VerdifaxClient = class {
984
1004
  }
985
1005
  const out = await this.request(
986
1006
  "POST",
987
- `${POLICIES_PATH}/${documentHash}/activate`,
1007
+ `${base}/${documentHash}/activate`,
988
1008
  body
989
1009
  );
990
1010
  return out.activation;
991
1011
  }
1012
+ // ── Account, with its owner key (auth) ───────────────────────
1013
+ //
1014
+ // An account is a company on Verdifax. Every key belongs to one account
1015
+ // for life. These methods need the account's owner key, which manages the
1016
+ // account and cannot run anything: the server refuses it on every other
1017
+ // route. The account number is a label Verdifax assigned, not a verified
1018
+ // company identity.
1019
+ /** The owner's account: its number, name, tier, key counts and the
1020
+ * chain of its policy changes. */
1021
+ async getAccount() {
1022
+ this.requireAPIKey();
1023
+ return this.request("GET", ACCOUNT_PATH);
1024
+ }
1025
+ /** Creates a member key in the account. It takes the account's tier and
1026
+ * stays in the account for life. The secret is in the result this once.
1027
+ * On a trial account the key needs a request key before it can run. */
1028
+ async createAccountKey(name, options = {}) {
1029
+ this.requireAPIKey();
1030
+ if (typeof name !== "string" || name.trim() === "") {
1031
+ throw new VerdifaxError(
1032
+ "verdifax: name is required for createAccountKey"
1033
+ );
1034
+ }
1035
+ const body = { name };
1036
+ if (options.expiresAt !== void 0) body.expires_at = options.expiresAt;
1037
+ return this.request(
1038
+ "POST",
1039
+ `${ACCOUNT_PATH}/keys`,
1040
+ body
1041
+ );
1042
+ }
1043
+ /** Every key in the account, owner keys included, without secrets. */
1044
+ async listAccountKeys() {
1045
+ this.requireAPIKey();
1046
+ const out = await this.request(
1047
+ "GET",
1048
+ `${ACCOUNT_PATH}/keys`
1049
+ );
1050
+ return { ...out, keys: out?.keys ?? [] };
1051
+ }
1052
+ /** Revokes one of the account's member keys. A key in another account,
1053
+ * or an owner key, throws {@link NotFoundError}. */
1054
+ async revokeAccountKey(keyId) {
1055
+ this.requireAPIKey();
1056
+ requirePositiveId(keyId, "keyId");
1057
+ return this.request(
1058
+ "DELETE",
1059
+ `${ACCOUNT_PATH}/keys/${keyId}`
1060
+ );
1061
+ }
1062
+ /** Registers this client's public key as the owner key's request key,
1063
+ * which must sign every change to the account's policy. It works as
1064
+ * {@link registerRequestKey} does. */
1065
+ async registerAccountRequestKey() {
1066
+ return this.registerRequestKeyAt(
1067
+ ACCOUNT_REQUEST_KEY_PATH,
1068
+ "registerAccountRequestKey"
1069
+ );
1070
+ }
1071
+ /** Clears the owner key's request key, as {@link clearRequestKey} does
1072
+ * for a key's own. With none registered the account's policy cannot be
1073
+ * changed. A trial owner key's request key cannot be cleared. */
1074
+ async clearAccountRequestKey() {
1075
+ await this.clearRequestKeyAt(ACCOUNT_REQUEST_KEY_PATH);
1076
+ }
1077
+ /** The runs of every key in the account, paged as {@link listRuns}. */
1078
+ async listAccountRuns(params = {}) {
1079
+ return this.listRunsAt(`${ACCOUNT_PATH}/runs`, params);
1080
+ }
1081
+ /** The runs of every key in the account in a period, as
1082
+ * {@link exportRuns} lists a key's, with the account's number, its
1083
+ * policy documents and its chain of changes. The account's view covers
1084
+ * the runs its keys made through Verdifax, not runs made elsewhere. */
1085
+ async exportAccountRuns(params = {}) {
1086
+ this.requireAPIKey();
1087
+ return this.request(
1088
+ "GET",
1089
+ this.exportPath(`${ACCOUNT_PATH}/runs/export`, params, "json")
1090
+ );
1091
+ }
1092
+ /** The same rows as {@link exportAccountRuns} as raw CSV text, checked
1093
+ * as {@link exportRunsCSV} checks a key's. */
1094
+ async exportAccountRunsCSV(params = {}) {
1095
+ return this.exportCSVAt(`${ACCOUNT_PATH}/runs/export`, params);
1096
+ }
1097
+ /** Stores an account policy document, as {@link uploadPolicy} stores a
1098
+ * key's. Every key in the account must pass the account's policy on top
1099
+ * of its own. Verdifax does not write, review or approve it. */
1100
+ async uploadAccountPolicy(document) {
1101
+ return this.uploadPolicyAt(ACCOUNT_POLICIES_PATH, document);
1102
+ }
1103
+ /** The account's stored documents, its chain `account-N`, the newest
1104
+ * change in it and the document in force. */
1105
+ async listAccountPolicies() {
1106
+ this.requireAPIKey();
1107
+ return this.request("GET", ACCOUNT_POLICIES_PATH);
1108
+ }
1109
+ /** One of the account's documents, with its exact text. */
1110
+ async getAccountPolicy(documentHash) {
1111
+ return this.getPolicyAt(ACCOUNT_POLICIES_PATH, documentHash);
1112
+ }
1113
+ /** {@link testPolicy} for the account's documents. It seals nothing. */
1114
+ async testAccountPolicy(req) {
1115
+ return this.testPolicyAt(ACCOUNT_POLICIES_PATH, req, "testAccountPolicy");
1116
+ }
1117
+ /** The account's policy changes, oldest first, in the form
1118
+ * `verdifax-verify --policy-history FILE` reads. */
1119
+ async getAccountPolicyHistory() {
1120
+ this.requireAPIKey();
1121
+ return this.request(
1122
+ "GET",
1123
+ `${ACCOUNT_POLICIES_PATH}/history`
1124
+ );
1125
+ }
1126
+ /** Makes a stored document the account's policy in force, as
1127
+ * {@link activatePolicy} does for a key's. It reads
1128
+ * {@link listAccountPolicies} for the chain and its newest change, and
1129
+ * the owner key's request key signs the change over that chain,
1130
+ * `account-N`. The server refuses an unsigned account change, so this
1131
+ * needs the `signingKey` option and refuses to send without it. */
1132
+ async activateAccountPolicy(documentHash) {
1133
+ this.requireAPIKey();
1134
+ validateHex64(documentHash, "documentHash");
1135
+ if (!this.signingKey) {
1136
+ throw new VerdifaxError(
1137
+ "verdifax: activateAccountPolicy needs the owner key's request key in the signingKey client option, because the server refuses an unsigned account policy change"
1138
+ );
1139
+ }
1140
+ const chain = await this.listAccountPolicies();
1141
+ if (typeof chain?.chain_id !== "string" || !ACCOUNT_CHAIN_ID.test(chain.chain_id)) {
1142
+ throw new VerdifaxError(
1143
+ "verdifax: the server did not report an account chain_id (account-N), so the change cannot be signed"
1144
+ );
1145
+ }
1146
+ return this.activatePolicyAt(ACCOUNT_POLICIES_PATH, documentHash, chain);
1147
+ }
992
1148
  // ── Runs ──────────────────────────────────────────────────────
993
1149
  async getRun(runId) {
994
1150
  this.requireAPIKey();
@@ -1001,6 +1157,9 @@ var VerdifaxClient = class {
1001
1157
  return this.request("GET", `/runs/${runId}/verify`);
1002
1158
  }
1003
1159
  async listRuns(params = {}) {
1160
+ return this.listRunsAt("/runs", params);
1161
+ }
1162
+ async listRunsAt(base, params) {
1004
1163
  this.requireAPIKey();
1005
1164
  const q = new URLSearchParams();
1006
1165
  if (params.limit && params.limit > 0) {
@@ -1010,7 +1169,7 @@ var VerdifaxClient = class {
1010
1169
  q.set("offset", String(params.offset));
1011
1170
  }
1012
1171
  const qs = q.toString();
1013
- const path = qs ? `/runs?${qs}` : "/runs";
1172
+ const path = qs ? `${base}?${qs}` : base;
1014
1173
  const out = await this.request("GET", path);
1015
1174
  return { runs: out.runs ?? [], total: out.total ?? 0 };
1016
1175
  }
@@ -1048,7 +1207,10 @@ var VerdifaxClient = class {
1048
1207
  * call that never reached Verdifax cannot appear in it. */
1049
1208
  async exportRuns(params = {}) {
1050
1209
  this.requireAPIKey();
1051
- return this.request("GET", this.exportPath(params, "json"));
1210
+ return this.request(
1211
+ "GET",
1212
+ this.exportPath("/runs/export", params, "json")
1213
+ );
1052
1214
  }
1053
1215
  /** The same rows as {@link exportRuns}: the raw CSV text with `total`
1054
1216
  * and `truncated` from the response headers. Check `truncated`: when
@@ -1056,9 +1218,12 @@ var VerdifaxClient = class {
1056
1218
  * does not send those headers, so that a cut-off export cannot pass
1057
1219
  * for a complete one. */
1058
1220
  async exportRunsCSV(params = {}) {
1221
+ return this.exportCSVAt("/runs/export", params);
1222
+ }
1223
+ async exportCSVAt(base, params) {
1059
1224
  this.requireAPIKey();
1060
1225
  const { text, headers } = await this.requestRaw(
1061
- this.exportPath(params, "csv")
1226
+ this.exportPath(base, params, "csv")
1062
1227
  );
1063
1228
  const total = Number.parseInt(headers.get(HEADER_EXPORT_TOTAL) ?? "", 10);
1064
1229
  const truncated = headers.get(HEADER_EXPORT_TRUNCATED);
@@ -1069,13 +1234,13 @@ var VerdifaxClient = class {
1069
1234
  }
1070
1235
  return { csv: text, total, truncated: truncated === "true" };
1071
1236
  }
1072
- exportPath(params, format) {
1237
+ exportPath(base, params, format) {
1073
1238
  const q = new URLSearchParams();
1074
1239
  if (params.from) q.set("from", params.from);
1075
1240
  if (params.to) q.set("to", params.to);
1076
1241
  if (params.job_id) q.set("job_id", params.job_id);
1077
1242
  q.set("format", format);
1078
- return `/runs/export?${q.toString()}`;
1243
+ return `${base}?${q.toString()}`;
1079
1244
  }
1080
1245
  // ── Attestation receipts (auth) ──────────────────────────────
1081
1246
  async listAttestations(params = {}) {
@@ -1213,14 +1378,48 @@ var VerdifaxClient = class {
1213
1378
  return this.request("POST", "/dcae/verify", bundle);
1214
1379
  }
1215
1380
  // ── Admin: API keys (auth) ───────────────────────────────────
1216
- async adminCreateKey(name) {
1381
+ /** Creates a customer key. With `accountId` the key joins that existing
1382
+ * account; without it the key gets an account of its own. A key never
1383
+ * changes account. */
1384
+ async adminCreateKey(name, options = {}) {
1217
1385
  this.requireAPIKey();
1218
1386
  if (!name) {
1219
1387
  throw new VerdifaxError("verdifax: name is required for adminCreateKey");
1220
1388
  }
1221
- return this.request("POST", "/admin/keys", {
1222
- name
1223
- });
1389
+ const body = { name };
1390
+ if (options.accountId !== void 0) {
1391
+ requirePositiveId(options.accountId, "accountId");
1392
+ body.account_id = options.accountId;
1393
+ }
1394
+ return this.request("POST", "/admin/keys", body);
1395
+ }
1396
+ /** Creates an account and its first owner key, whose secret is in the
1397
+ * result this once. The owner key creates the member keys. The account
1398
+ * number is a label Verdifax assigned, not a verified company identity. */
1399
+ async adminCreateAccount(name, options = {}) {
1400
+ this.requireAPIKey();
1401
+ if (typeof name !== "string" || name.trim() === "") {
1402
+ throw new VerdifaxError(
1403
+ "verdifax: name is required for adminCreateAccount"
1404
+ );
1405
+ }
1406
+ const body = { name };
1407
+ if (options.tier !== void 0) body.tier = options.tier;
1408
+ if (options.expiresAt !== void 0) body.expires_at = options.expiresAt;
1409
+ return this.request(
1410
+ "POST",
1411
+ "/admin/accounts",
1412
+ body
1413
+ );
1414
+ }
1415
+ /** Every account, with its key counts. */
1416
+ async adminListAccounts() {
1417
+ this.requireAPIKey();
1418
+ const out = await this.request(
1419
+ "GET",
1420
+ "/admin/accounts"
1421
+ );
1422
+ return out?.accounts ?? [];
1224
1423
  }
1225
1424
  async adminListKeys() {
1226
1425
  this.requireAPIKey();
@@ -1399,6 +1598,11 @@ var VerdifaxClient = class {
1399
1598
  return this.signingKey ? signRequestV2(this.signingKey, req) : withExecuteFlags(req);
1400
1599
  }
1401
1600
  };
1601
+ function requirePositiveId(id, name) {
1602
+ if (!Number.isSafeInteger(id) || id <= 0) {
1603
+ throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
1604
+ }
1605
+ }
1402
1606
  function withSignedText(result, sent) {
1403
1607
  return "signed_request" in sent ? { ...result, signed_request: sent.signed_request } : result;
1404
1608
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdifax",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Official Node.js / TypeScript SDK for the Verdifax orchestrator REST API.",
5
5
  "license": "MIT",
6
6
  "author": "Verdifax <team@verdifax.com>",