@hasna/domains
Version:
Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL
1,332 lines • 55 kB
JavaScript
// @bun
// ../contracts/dist/client/transport.js
import { isIP } from "net";
import { spawnSync } from "child_process";
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync2 } from "fs";
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
import { hostname as osHostname } from "os";
import { isAbsolute, join as join2 } from "path";
import { createRequire } from "module";
import { fileURLToPath as fileURLToPath2 } from "url";
import { closeSync, fstatSync, lstatSync, openSync, readFileSync, realpathSync, statSync } from "fs";
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
import { basename, dirname, join, relative, sep } from "path";
import { fileURLToPath, pathToFileURL } from "url";
function envToken(name) {
return name.toUpperCase().replace(/-/g, "_");
}
function clientTransportEnvKeys(name) {
const envSegment = envToken(name);
return {
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
};
}
function credentialOverrideEnvKey(name) {
return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
}
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE";
function credentialPointerEnvKey(name) {
return `HASNA_${envToken(name)}_API_KEY_REF`;
}
function e(e2, n, r) {
throw new Error(r ? `No known conditions for "${n}" specifier in "${e2}" package` : `Missing "${n}" specifier in "${e2}" package`);
}
function n(n2, i, o, f) {
let s, u, l = r(n2, o), c = function(e2) {
let n3 = new Set(["default", ...e2.conditions || []]);
return e2.unsafe || n3.add(e2.require ? "require" : "import"), e2.unsafe || n3.add(e2.browser ? "browser" : "node"), n3;
}(f || {}), a = i[l];
if (a === undefined) {
let e2, n3, r, t;
for (t in i)
n3 && t.length < n3.length || (t[t.length - 1] === "/" && l.startsWith(t) ? (u = l.substring(t.length), n3 = t) : t.length > 1 && (r = t.indexOf("*", 1), ~r && (e2 = RegExp("^" + t.substring(0, r) + "(.*)" + t.substring(1 + r) + "$").exec(l), e2 && e2[1] && (u = e2[1], n3 = t))));
a = i[n3];
}
return a || e(n2, l), s = t(a, c), s || e(n2, l, 1), u && function(e2, n3) {
let r, t = 0, i2 = e2.length, o2 = /[*]/g, f2 = /[/]$/;
for (;t < i2; t++)
e2[t] = o2.test(r = e2[t]) ? r.replace(o2, n3) : f2.test(r) ? r + n3 : r;
}(s, u), s;
}
function r(e2, n2, r2) {
if (e2 === n2 || n2 === ".")
return ".";
let t = e2 + "/", i = t.length, o = n2.slice(0, i) === t, f = o ? n2.slice(i) : n2;
return f[0] === "#" ? f : o || !r2 ? f.slice(0, 2) === "./" ? f : "./" + f : f;
}
function t(e2, n2, r2) {
if (e2) {
if (typeof e2 == "string")
return r2 && r2.add(e2), [e2];
let i, o;
if (Array.isArray(e2)) {
for (o = r2 || new Set, i = 0;i < e2.length; i++)
t(e2[i], n2, o);
if (!r2 && o.size)
return [...o];
} else
for (i in e2)
if (n2.has(i))
return t(e2[i], n2, r2);
}
}
function o(e2, r2, t2) {
let i, o2 = e2.exports;
if (o2) {
if (typeof o2 == "string")
o2 = { ".": o2 };
else
for (i in o2) {
i[0] !== "." && (o2 = { ".": o2 });
break;
}
return n(e2.name, o2, r2 || ".", t2);
}
}
var PACKAGE = "@hasna/secrets";
var MAX_PACKAGE_BYTES = 1024 * 1024;
function entryPoint(directory) {
const root = realpathSync(directory);
const fd = openSync(join(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
let pkg;
try {
const stat = fstatSync(fd);
if (!stat.isFile() || stat.size > MAX_PACKAGE_BYTES)
throw new Error("Invalid Secrets package metadata");
pkg = JSON.parse(readFileSync(fd, "utf8"));
} finally {
closeSync(fd);
}
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
throw new Error("Invalid Secrets package identity");
}
const target = o(pkg, ".")?.[0];
if (typeof target !== "string" || !target.startsWith("./"))
throw new Error("Missing Secrets import export");
const parts = target.slice(2).split("/");
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
throw new Error("Invalid Secrets import export");
}
const file = realpathSync(join(root, ...parts));
const within = relative(root, file);
if (!within || within === ".." || within.startsWith(`..${sep}`) || !statSync(file).isFile()) {
throw new Error("Secrets import export escapes its package or is not a file");
}
return pathToFileURL(file).href;
}
function resolveInstalledSecrets(parentUrl) {
let directory = dirname(fileURLToPath(parentUrl));
for (;; ) {
if (basename(directory) !== "node_modules") {
const candidate = join(directory, "node_modules", PACKAGE);
let absent = false;
try {
lstatSync(candidate);
} catch (error) {
if (error.code !== "ENOENT")
throw error;
absent = true;
}
if (!absent)
return entryPoint(candidate);
}
const parent = dirname(directory);
if (parent === directory)
throw new Error("Secrets SDK is not installed for this consumer");
directory = parent;
}
}
class CredentialResolutionError extends Error {
appName;
attempted;
constructor(appName, message, attempted) {
super(message);
this.name = "CredentialResolutionError";
this.appName = appName;
this.attempted = attempted;
}
}
class CredentialFileUnsafeError extends Error {
path;
constructor(path, reason) {
super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
this.name = "CredentialFileUnsafeError";
this.path = path;
}
}
var HASNA_HOME_ENV_KEY = "HASNA_HOME";
var HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME";
var KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION";
var HASNA_HOME_DIR = ".hasna";
var CONFIG_SUBDIR = "config";
var CREDENTIALS_FILE = "credentials";
var KEYCHAIN_SECURITY_BIN = "/usr/bin/security";
var KEYCHAIN_SERVICE_PREFIX = "hasna.credentials";
var KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44;
var KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4;
var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
var SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
var SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
var ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
var VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
function homeDir(env) {
const home = env.HOME?.trim();
return home ? home : null;
}
function absoluteOverride(env, key) {
const value = env[key]?.trim();
return value && isAbsolute(value) ? value : null;
}
function hasnaHomeDir(env) {
const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
if (override)
return override;
const home = homeDir(env);
return home ? join2(home, HASNA_HOME_DIR) : null;
}
function appConfigDir(name, env) {
const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
if (configRoot)
return join2(configRoot, name);
const root = hasnaHomeDir(env);
return root ? join2(root, name, CONFIG_SUBDIR) : null;
}
function credentialDiskSourceList(name, env, profile = null) {
if (!SAFE_APP_SLUG.test(name))
return [];
const directory = appConfigDir(name, env);
if (!directory)
return [];
const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
return [{ path: join2(directory, file), tier: "disk" }];
}
function credentialDiskSources(name, env) {
return credentialDiskSourceList(name, env, null).map((s) => s.path);
}
function profileDiskSources(name, env, profile) {
return credentialDiskSourceList(name, env, profile).map((s) => s.path);
}
function parseEnvFile(text) {
const values = new Map;
const unusable = new Set;
for (const rawLine of text.split(/\r?\n/)) {
const line = rawLine.trim();
if (line.length === 0 || line.startsWith("#"))
continue;
const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
const equals = withoutExport.indexOf("=");
if (equals <= 0)
continue;
const key = withoutExport.slice(0, equals).trim();
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
continue;
let value = withoutExport.slice(equals + 1).trim();
const quote = value[0];
if (quote === '"' || quote === "'") {
if (value.length < 2 || !value.endsWith(quote)) {
unusable.add(key);
continue;
}
value = value.slice(1, -1);
}
if (value.trim().length === 0) {
unusable.add(key);
continue;
}
if (values.has(key) && values.get(key) !== value)
unusable.add(key);
values.set(key, value);
}
return { values, unusable };
}
function configFileModeAllowed(mode) {
const permissions = mode & 4095;
return permissions === 256 || permissions === 384;
}
function configFileReadsCoherent(before, after) {
return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
}
function readAppConfigFile(path) {
const unsafe = (reason) => {
throw new CredentialFileUnsafeError(path, reason);
};
let fd = -1;
try {
fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
} catch (error) {
const code = error.code;
if (code === "ENOENT" || code === "ENOTDIR")
return null;
if (code === "ELOOP")
unsafe("the path is a symlink");
unsafe(`the path could not be opened (${code ?? "unknown error"})`);
}
try {
const before = fstatSync2(fd);
if (!before.isFile())
unsafe("the path is not a regular file");
if (!configFileModeAllowed(before.mode)) {
unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
}
const uid = process.getuid?.() ?? process.geteuid?.();
if (uid !== undefined && before.uid !== uid)
unsafe("the file is not owned by the current user");
if (before.size > MAX_CREDENTIAL_FILE_BYTES)
unsafe("the file exceeds the size limit");
const bytes = readFileSync2(fd);
const after = fstatSync2(fd);
if (!configFileReadsCoherent(before, after)) {
unsafe("the file changed while being read");
}
return parseEnvFile(bytes.toString("utf8"));
} finally {
if (fd !== -1)
closeSync2(fd);
}
}
function readCredentialFile(path, apiKeyKeys, pointerKey) {
const parsed = readAppConfigFile(path);
if (!parsed)
return null;
for (const key of [...apiKeyKeys, pointerKey]) {
if (parsed.unusable.has(key)) {
throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
}
}
const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
const pointer = parsed.values.get(pointerKey)?.trim();
if (pointer !== undefined) {
if (!VAULT_POINTER_SHAPE.test(pointer))
throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
if (values.length)
throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
return { apiKey: "", pointerVaultKey: pointer };
}
if (new Set(values).size > 1) {
throw new CredentialFileUnsafeError(path, "credential aliases disagree");
}
return values[0] === undefined ? null : { apiKey: values[0] };
}
var CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
function appConfigDiskValue(name, env, keys, profile = null) {
if (profile !== null && !SAFE_PROFILE.test(profile)) {
throw new CredentialResolutionError(name, "The selected configuration profile is invalid.", ["profile"]);
}
const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
if (wanted.length === 0)
return null;
for (const path of profileDiskSources(name, env, profile)) {
const parsed = readAppConfigFile(path);
if (!parsed)
continue;
if (wanted.some((key) => parsed.unusable.has(key))) {
return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
}
const values = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
if (new Set(values).size > 1)
throw new CredentialFileUnsafeError(path, "configuration aliases disagree");
for (const key of wanted) {
if (parsed.unusable.has(key))
return { key, value: "", path, unusable: true };
const value = parsed.values.get(key)?.trim();
if (value)
return { key, value, path };
}
}
return null;
}
function assertUsableCredential(appName, source, value) {
if (VAULT_POINTER_SHAPE.test(value)) {
throw new CredentialResolutionError(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
}
if (!ILLEGAL_IN_HEADER_VALUE.test(value))
return;
throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
}
var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
function sealCredential(fields) {
const { apiKey } = fields;
const visible = {
tier: fields.tier,
source: fields.source,
deliberate: fields.deliberate,
diskCandidates: Object.freeze([...fields.diskCandidates]),
warning: fields.warning
};
const sealed = { ...visible };
Object.defineProperty(sealed, "apiKey", {
value: apiKey,
enumerable: false,
writable: false,
configurable: false
});
if (fields.pointerVaultKey !== undefined) {
Object.defineProperty(sealed, "pointerVaultKey", {
value: fields.pointerVaultKey,
enumerable: false,
writable: false,
configurable: false
});
}
Object.defineProperty(sealed, INSPECT_CUSTOM, {
value: () => ({ ...visible, apiKey: "[redacted]" }),
enumerable: false,
writable: false,
configurable: false
});
Object.defineProperty(sealed, CREDENTIAL_SEAL, {
value: true,
enumerable: false,
writable: false,
configurable: false
});
return Object.freeze(sealed);
}
function firstEnvValue(env, keys) {
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(env, key))
continue;
const value = env[key]?.trim();
if (value)
return { key, value };
}
return null;
}
var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
function isAmbientEnvironment(env) {
return env === process.env || env[AMBIENT_ENVIRONMENT] === true;
}
function defaultKeychainRunner(argv) {
const result = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
});
return {
status: result.status,
stdout: result.stdout ?? "",
stderr: result.error ? result.error.message : result.stderr ?? ""
};
}
function keychainTierEnabled(env, options) {
if ((options.platform ?? process.platform) !== "darwin")
return false;
if (options.enabled !== undefined)
return options.enabled;
return options.run !== undefined || isAmbientEnvironment(env);
}
function keychainAccount(env, options) {
const station = env[KEYCHAIN_STATION_ENV_KEY]?.trim();
if (station)
return station;
const host = (options.hostname ?? osHostname)().split(".")[0]?.trim() ?? "";
if (host)
return host;
const user = env.USER?.trim();
return user || null;
}
function keychainFailureHint(text) {
const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
return clean ? `: ${clean}` : "";
}
function readKeychainItem(name, env, kind, options) {
if (!SAFE_APP_SLUG.test(name) || !keychainTierEnabled(env, options))
return null;
const account = keychainAccount(env, options);
if (!account)
return null;
const service = `${KEYCHAIN_SERVICE_PREFIX}.${name}.${kind}`;
const source = `keychain:${service}@${account}`;
const run = options.run ?? defaultKeychainRunner;
let result;
try {
result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
} catch (error) {
const reason = keychainFailureHint(error instanceof Error ? error.message : String(error));
throw new CredentialResolutionError(name, `The Keychain lookup for ${source} could not run${reason}. A Keychain failure is never resolved ` + `around: fix the keychain, or delete the item to fall through to the credential on disk.`, [source]);
}
if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS)
return null;
if (result.status !== 0) {
throw new CredentialResolutionError(name, `The Keychain lookup for ${source} failed (security exited ` + `${result.status ?? "without a status"}${keychainFailureHint(result.stderr)}). A Keychain item that ` + `exists but cannot be read is never resolved around: unlock the keychain, run from a session that ` + `may use it, or delete the item to fall through to the credential on disk.`, [source]);
}
const value = result.stdout.trim();
if (!value) {
throw new CredentialResolutionError(name, `${source} exists but holds an empty value; a declared item never falls through to another ` + `identity. Store a value in it or delete the item.`, [source]);
}
return { value, source };
}
function keychainConfigValue(name, env, options = {}) {
return readKeychainItem(name, env, "api-url", options);
}
function snapshotClientEnvironment(name, env) {
const keys = clientTransportEnvKeys(name);
const ambient = isAmbientEnvironment(env);
const snapshot = Object.create(null);
for (const key of [
...keys.apiUrlKeys,
...keys.apiKeyKeys,
credentialOverrideEnvKey(name),
credentialPointerEnvKey(name),
CREDENTIAL_PROFILE_ENV_KEY,
"HOME",
HASNA_HOME_ENV_KEY,
HASNA_CONFIG_HOME_ENV_KEY,
KEYCHAIN_STATION_ENV_KEY,
"USER"
]) {
const descriptor = Object.getOwnPropertyDescriptor(env, key);
if (!descriptor)
continue;
if (!("value" in descriptor)) {
throw new CredentialResolutionError(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
}
if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
throw new CredentialResolutionError(name, `${key} must be a string data property.`, [key]);
}
snapshot[key] = descriptor.value;
}
if (ambient) {
Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT, {
value: true,
enumerable: false,
writable: false,
configurable: false
});
}
return Object.freeze(snapshot);
}
function resolveCredential(name, env, options = {}) {
env = snapshotClientEnvironment(name, env);
const { apiKeyKeys } = clientTransportEnvKeys(name);
const diskPaths = credentialDiskSources(name, env);
if (options.apiKey !== undefined) {
const explicitKey = options.apiKey.trim();
if (!explicitKey) {
throw new CredentialResolutionError(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
}
assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
return sealCredential({
apiKey: explicitKey,
tier: "argument",
source: "explicit apiKey argument",
deliberate: true,
diskCandidates: diskPaths,
warning: null
});
}
const overrideKeyName = credentialOverrideEnvKey(name);
const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
if (overrideRaw !== undefined) {
const override = overrideRaw.trim();
if (!override) {
throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
}
assertUsableCredential(name, overrideKeyName, override);
return sealCredential({
apiKey: override,
tier: "override",
source: overrideKeyName,
deliberate: true,
diskCandidates: diskPaths,
warning: null
});
}
const pointerKeyName = credentialPointerEnvKey(name);
const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
if (pointerRaw !== undefined) {
const pointer = pointerRaw.trim();
if (!pointer) {
throw new CredentialResolutionError(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
}
if (!VAULT_POINTER_SHAPE.test(pointer)) {
throw new CredentialResolutionError(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
}
return sealCredential({
apiKey: "",
pointerVaultKey: pointer,
tier: "pointer",
source: pointerKeyName,
deliberate: true,
diskCandidates: diskPaths,
warning: null
});
}
if (options.profile !== undefined && !options.profile.trim()) {
throw new CredentialResolutionError(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
}
const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY) ? env[CREDENTIAL_PROFILE_ENV_KEY] : undefined;
if (profileRaw !== undefined && !profileRaw.trim()) {
throw new CredentialResolutionError(name, `${CREDENTIAL_PROFILE_ENV_KEY} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY]);
}
const profile = options.profile?.trim() || profileRaw?.trim();
if (profile) {
const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
if (!SAFE_PROFILE.test(profile)) {
throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
}
const paths = profileDiskSources(name, env, profile);
for (const path of paths) {
const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
if (value) {
if (!value.pointerVaultKey)
assertUsableCredential(name, path, value.apiKey);
return sealCredential({
...value,
tier: value.pointerVaultKey ? "pointer" : "profile",
source: path,
deliberate: true,
diskCandidates: paths,
warning: null
});
}
}
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} or ${pointerKeyName} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
}
const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
if (blankEnv) {
throw new CredentialResolutionError(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
}
if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
throw new CredentialResolutionError(name, `${definedEnvEntries.map((entry) => entry.key).join(" and ")} disagree; credential aliases must be identical or only one may be set.`, definedEnvEntries.map((entry) => entry.key));
}
const envHit = firstEnvValue(env, apiKeyKeys);
const keychainHit = readKeychainItem(name, env, "api-key", options.keychain ?? {});
if (keychainHit) {
assertUsableCredential(name, keychainHit.source, keychainHit.value);
const warning = envHit && envHit.value !== keychainHit.value ? `Credential sources disagree for '${name}': ${keychainHit.source} and ${envHit.key} hold ` + `different keys. ${keychainHit.source} wins, because the Keychain is re-read on every call while ` + `an environment variable is a snapshot. Reconcile them \u2014 a rotation that updated only one leaves ` + `the other to fail 401 wherever it is loaded first.` : null;
return sealCredential({
apiKey: keychainHit.value,
tier: "keychain",
source: keychainHit.source,
deliberate: false,
diskCandidates: diskPaths,
warning
});
}
const diskSourceList = credentialDiskSourceList(name, env, null);
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
if (diskHits.length > 0) {
const winner = diskHits[0];
if (winner.value.pointerVaultKey) {
return sealCredential({
...winner.value,
tier: "pointer",
source: winner.src.path,
deliberate: false,
diskCandidates: diskPaths,
warning: null
});
}
assertUsableCredential(name, winner.src.path, winner.value.apiKey);
const divergentSources = [
...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
];
const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
return sealCredential({
apiKey: winner.value.apiKey,
tier: winner.src.tier,
source: winner.src.path,
deliberate: false,
diskCandidates: diskPaths,
warning
});
}
if (envHit) {
assertUsableCredential(name, envHit.key, envHit.value);
return sealCredential({
apiKey: envHit.value,
tier: "env",
source: envHit.key,
deliberate: false,
diskCandidates: diskPaths,
warning: null
});
}
return null;
}
var requireSecretsSdk = createRequire(import.meta.url);
async function completePointerCredential(name, pointerResolution, env = process.env) {
const secretsEnv = snapshotClientEnvironment("secrets", env);
const vaultKey = pointerResolution.pointerVaultKey;
const pointerEnvKey = pointerResolution.source;
if (!vaultKey) {
throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
}
if (name === "secrets") {
throw new CredentialResolutionError(name, "The Secrets bootstrap credential cannot reference the same hosted vault; configure an independent bootstrap provider.", [pointerEnvKey]);
}
let secretsSdk;
try {
const sdkUrl = resolveInstalledSecrets(import.meta.url);
secretsSdk = requireSecretsSdk(fileURLToPath2(sdkUrl));
} catch {
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
}
let client;
try {
const bootstrap = resolveCredential("secrets", secretsEnv);
if (!bootstrap || bootstrap.tier === "pointer") {
throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap ? [bootstrap.source] : []);
}
client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
} catch {
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
}
let secret;
try {
secret = await client.getSecret({ key: vaultKey });
} catch {
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
}
const value = secret.value;
if (!value) {
throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
}
assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
return sealCredential({
apiKey: value,
tier: "pointer",
source: `${pointerEnvKey} -> vault:${vaultKey}`,
deliberate: pointerResolution.deliberate,
diskCandidates: pointerResolution.diskCandidates,
warning: null
});
}
var DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com";
var DEFAULT_AUTHORITY_SOURCE = "default";
function defaultFleetGatewayBaseUrl(name) {
return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
}
var ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
var DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
function isValidDnsDomain(value) {
if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN.test(value) || /[^\x00-\x7f]/.test(value)) {
return false;
}
return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN.test(label));
}
function validateAppSlug(name) {
if (name.length > 63 || !DNS_LABEL_PATTERN.test(name)) {
throw new Error("App name must be one lowercase DNS label.");
}
return name;
}
function rawAuthority(value) {
const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
if (!match)
throw new Error("API URL must be absolute.");
const afterScheme = value.slice(match[0].length);
const boundary = afterScheme.search(/[/?#]/);
const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
if (!authority)
throw new Error("API URL must include a hostname.");
return authority;
}
function assertCanonicalPort(port) {
if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
}
const numericPort = Number(port);
if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
}
}
function canonicalAuthorityHostname(authority) {
let rawHostname;
if (authority.startsWith("[")) {
const closingBracket = authority.indexOf("]");
if (closingBracket === -1) {
throw new Error("API URL authority must contain a canonical hostname.");
}
rawHostname = authority.slice(0, closingBracket + 1);
const portSuffix = authority.slice(closingBracket + 1);
if (portSuffix) {
if (!portSuffix.startsWith(":")) {
throw new Error("API URL authority must contain a canonical hostname and port.");
}
assertCanonicalPort(portSuffix.slice(1));
}
if (isIP(rawHostname.slice(1, -1)) !== 6) {
throw new Error("API URL authority must contain a canonical IPv6 literal.");
}
} else {
const firstColon = authority.indexOf(":");
const lastColon = authority.lastIndexOf(":");
if (firstColon !== lastColon) {
throw new Error("IPv6 API URL authorities must use brackets.");
}
if (lastColon !== -1) {
const port = authority.slice(lastColon + 1);
assertCanonicalPort(port);
rawHostname = authority.slice(0, lastColon);
} else {
rawHostname = authority;
}
const ipVersion = isIP(rawHostname);
const numericAddressParts = rawHostname.split(".");
const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
throw new Error("API URL authority must contain a canonical ASCII hostname.");
}
}
return rawHostname.toLowerCase();
}
function isDeliberateLoopbackHttpAuthority(authority) {
return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
}
function toV1BaseUrl(apiUrl) {
if (ASCII_CONTROL_PATTERN.test(apiUrl)) {
throw new Error("API URL must not contain ASCII control characters.");
}
const input = apiUrl.trim();
const authority = rawAuthority(input);
if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
throw new Error("API URL authority must be canonical ASCII without credentials.");
}
const canonicalHostname = canonicalAuthorityHostname(authority);
const url = new URL(input);
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new Error("API URL must use http or https.");
}
if (url.username || url.password) {
throw new Error("API URL must not include credentials.");
}
if (!url.hostname || url.hostname.endsWith(".")) {
throw new Error("API URL must include a canonical hostname.");
}
if (url.hostname.toLowerCase() !== canonicalHostname) {
throw new Error("API URL authority must not rely on parser hostname normalization.");
}
if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
throw new Error("API URL must not use IDN or punycode hostnames.");
}
if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority(authority)) {
throw new Error("API URL may use http only for an exact loopback authority.");
}
if (url.search || url.hash) {
throw new Error("API URL must not include a query string or fragment.");
}
let path = url.pathname.replace(/\/+$/, "");
if (path.endsWith("/v1"))
path = path.slice(0, -"/v1".length);
url.pathname = `${path}/v1`;
return url.toString().replace(/\/+$/, "");
}
class ClientTransportConfigurationError extends Error {
appName;
sources;
constructor(appName, message, sources = []) {
super(message);
this.name = "ClientTransportConfigurationError";
this.appName = appName;
this.sources = Object.freeze([...sources]);
}
}
class ClientCredentialMissingError extends ClientTransportConfigurationError {
authorityConfigured;
constructor(name, message, sources, authorityConfigured) {
super(name, message, sources);
this.authorityConfigured = authorityConfigured;
this.name = "ClientCredentialMissingError";
}
}
function resolveClientAuthoritySnapshot(name, env = process.env, options = {}) {
env = snapshotClientEnvironment(name, env);
const keys = clientTransportEnvKeys(name);
const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
if (blankUrl) {
throw new ClientTransportConfigurationError(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
}
const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
if (controlledUrl) {
throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
}
const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
const normalizedAuthority = (entry) => {
try {
return toV1BaseUrl(entry.value);
} catch {
throw new ClientTransportConfigurationError(name, `Invalid API URL from ${entry.key}; use an absolute HTTPS URL or exact loopback HTTP authority without credentials, query or fragment.`, [entry.key]);
}
};
if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map(normalizedAuthority)).size > 1) {
throw new ClientTransportConfigurationError(name, `${usableUrlEntries.map((entry) => entry.key).join(" and ")} disagree; client authority aliases must select the same versioned API authority or only one may be set.`, usableUrlEntries.map((entry) => entry.key));
}
const envUrlHit = usableUrlEntries[0] ?? null;
const keychainUrlHit = keychainConfigValue(name, env, options.credentials?.keychain);
const profile = options.bindUnpairedCredentialsToFleet ? options.credentials?.profile?.trim() || env.HASNA_PROFILE?.trim() || null : null;
const diskConfigUrlHit = appConfigDiskValue(name, env, keys.apiUrlKeys, profile);
if (diskConfigUrlHit?.unusable) {
throw new ClientTransportConfigurationError(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
}
const urlCandidates = [
...envUrlHit ? [envUrlHit] : [],
...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
];
const configuredUrl = urlCandidates[0] ?? null;
const normalizedUrls = urlCandidates.map(normalizedAuthority);
const divergentUrls = urlCandidates.filter((_, index) => normalizedUrls[index] !== normalizedUrls[0]);
if (configuredUrl && divergentUrls.length > 0) {
throw new ClientTransportConfigurationError(name, `${configuredUrl.key} and ${divergentUrls.map((candidate) => candidate.key).join(" and ")} select different service authorities; refusing to send a credential written for one authority to the other.`, urlCandidates.map((candidate) => candidate.key));
}
if (configuredUrl && options.bindUnpairedCredentialsToFleet) {
try {
toV1BaseUrl(configuredUrl.value);
} catch (error) {
throw new ClientTransportConfigurationError(name, `Invalid API URL from ${configuredUrl.key}: ${error instanceof Error ? error.message : String(error)}`, [configuredUrl.key]);
}
}
return { keys, configuredUrl, envUrlHit, keychainUrlHit, diskConfigUrlHit };
}
function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
env = snapshotClientEnvironment(name, env);
const { keys, configuredUrl, envUrlHit, keychainUrlHit, diskConfigUrlHit } = resolveClientAuthoritySnapshot(name, env, options);
const warnings = [];
if (configuredUrl && !envUrlHit) {
warnings.push(`No ${keys.apiUrlKeys[0]} in the environment; the server URL in ${configuredUrl.key} was used, so this client connects to the server. ` + `Keep that entry aligned with the intended service authority.`);
}
const credential = resolveCredential(name, env, options.credentials);
if (!credential) {
const diskHint = credentialDiskSourcesForMessage(name, env);
const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys.apiUrlKeys[0]} is not set and no API key could be resolved for '${name}'; a credential is required before the default fleet gateway authority applies`;
warnings.push(`${lead}; refusing to create an unauthenticated client \u2014 public clients never fall back to SQLite or another local store. ` + `Looked in the Keychain (macOS only), then for a credential file at ${diskHint}, then for ${keys.apiKeyKeys[0]} in the environment.`);
const sources = [configuredUrl?.key ?? keys.apiUrlKeys[0]];
if (options.bindUnpairedCredentialsToFleet)
throw new ClientCredentialMissingError(name, warnings.join(" "), sources, configuredUrl !== null);
throw new ClientTransportConfigurationError(name, warnings.join(" "), sources);
}
if (credential.warning)
warnings.push(credential.warning);
let urlHit;
if (configuredUrl) {
urlHit = configuredUrl;
} else {
try {
urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new ClientTransportConfigurationError(name, `No ${keys.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys.apiUrlKeys[0]]);
}
}
const apiUrlSource = urlHit.key;
let baseUrl;
try {
baseUrl = toV1BaseUrl(urlHit.value);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
}
if (options.bindUnpairedCredentialsToFleet && baseUrl !== toV1BaseUrl(defaultFleetGatewayBaseUrl(name))) {
const pairedDisk = diskConfigUrlHit?.path === credential.source && (credential.tier === "disk" || credential.tier === "profile" || credential.tier === "pointer");
const pairedKeychain = credential.tier === "keychain" && keychainUrlHit?.source === credential.source.replace(".api-key@", ".api-url@");
const deliberatePair = envUrlHit !== null && (credential.tier === "argument" || credential.tier === "override");
if (!pairedDisk && !pairedKeychain && !deliberatePair) {
throw new ClientTransportConfigurationError(name, "An unpaired legacy credential is bound to the fleet gateway. Configure a stored authority/credential pair or an explicit API key override for the intended authority; no credential was sent.");
}
}
return {
resolution: {
transport: "http",
transportSource: urlHit.key,
baseUrl,
apiUrlSource,
apiKeyPresent: true,
apiKeySource: credential.source,
apiKeyTier: credential.tier,
misconfigured: false,
warning: warnings.length > 0 ? warnings.join(" ") : null
},
credential
};
}
function resolveClientTransport(name, env = process.env, options = {}) {
return resolveClientTransportSnapshot(name, env, options).resolution;
}
function credentialDiskSourcesForMessage(name, env) {
const paths = credentialDiskSources(name, env);
return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
}
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
var AUTHORITY_OVERRIDE_HEADERS = new Set([
"host",
":authority",
"forwarded",
"x-forwarded-host",
"x-original-host"
]);
// src/lib/client-storage-policy.ts
var LOCAL_PATH_VARS = [
"HASNA_DOMAINS_DB_PATH",
"DOMAINS_DB_PATH",
"HASNA_DOMAINS_DIR",
"DOMAINS_DIR"
];
function explicitLocalPathVar(env = process.env) {
return LOCAL_PATH_VARS.find((key) => (env[key] ?? "").trim() !== "");
}
function domainsAuthorityEnvKeys() {
const keys = clientTransportEnvKeys("domains");
return [
...keys.apiUrlKeys,
...keys.apiKeyKeys,
credentialOverrideEnvKey("domains"),
credentialPointerEnvKey("domains"),
CREDENTIAL_PROFILE_ENV_KEY
];
}
function assertDomainsClientStorage(env = process.env) {
const pathVar = explicitLocalPathVar(env);
if (pathVar)
throw new Error(`domains: ${pathVar} is no longer supported by clients. Unset local database path variables and configure the shared API with HASNA_DOMAINS_API_KEY or saved credentials. Preserve any existing database until its records have been migrated and verified.`);
}
// src/sdk/client.ts
class ApiError extends Error {
status;
body;
constructor(status, message, body) {
super(message);
this.status = status;
this.body = body;
this.name = "ApiError";
}
}
class DomainsClient {
baseUrl;
apiKey;
fetchImpl;
baseHeaders;
constructor(options) {
if (!options.baseUrl)
throw new Error("DomainsClient requires a baseUrl.");
this.baseUrl = options.baseUrl.replace(/\/$/, "");
this.apiKey = options.apiKey;
this.fetchImpl = options.fetch ?? globalThis.fetch;
this.baseHeaders = options.headers ?? {};
}
async request(method, path, opts) {
const url = new URL(this.baseUrl + path);
if (opts.query) {
for (const [key, value] of Object.entries(opts.query)) {
if (value === undefined || value === null)
continue;
if (Array.isArray(value)) {
for (const item of value) {
if (item !== undefined && item !== null)
url.searchParams.append(key, String(item));
}
} else {
url.searchParams.set(key, String(value));
}
}
}
const headers = { Accept: "application/json", ...this.baseHeaders, ...opts.init?.headers };
if (this.apiKey)
headers["x-api-key"] = this.apiKey;
let payload;
if (opts.body !== undefined) {
headers["Content-Type"] = "application/json";
payload = JSON.stringify(opts.body);
}
const response = await this.fetchImpl(url.toString(), { ...opts.init, method, headers, body: payload });
const text = await response.text();
const data = text ? (() => {
try {
return JSON.parse(text);
} catch {
return text;
}
})() : undefined;
if (!response.ok) {
throw new ApiError(response.status, `${method} ${path} failed: ${response.status}`, data);
}
return data;
}
async getHealth(init) {
return this.request("GET", `/health`, {
body: undefined,
query: undefined,
init
});
}
async getReady(init) {
return this.request("GET", `/ready`, {
body: undefined,
query: undefined,
init
});
}
async checkDomainAvailability(body, init) {
return this.request("POST", `/v1/availability`, {
body,
query: undefined,
init
});
}
async getDnsRecord(id, init) {
return this.request("GET", `/v1/dns/${encodeURIComponent(String(id))}`, {
body: undefined,
query: undefined,
init
});
}
async deleteDnsRecord(id, init) {
return this.request("DELETE", `/v1/dns/${encodeURIComponent(String(id))}`, {
body: undefined,
query: undefined,
init
});
}
async listDomains(query, init) {
return this.request("GET", `/v1/domains`, {
body: undefined,
query,
init
});
}
async createDomain(body, init) {
return this.request("POST", `/v1/domains`, {
body,
query: undefined,
init
});
}
async getDomain(id, init) {
return this.request("GET", `/v1/domains/${encodeURIComponent(String(id))}`, {
body: undefined,
query: undefined,
init
});
}
async deleteDomain(id, init) {
return this.request("DELETE", `/v1/domains/${encodeURIComponent(String(id))}`, {
body: undefined,
query: undefined,
init
});
}
async updateDomain(id, body, init) {
return this.request("PATCH", `/v1/domains/${encodeURIComponent(String(id))}`, {
body,
query: undefined,
init
});
}
async listDnsRecords(id, init) {
return this.request("GET", `/v1/domains/${encodeURIComponent(String(id))}/dns`, {
body: undefined,
query: undefined,
init
});
}
async createDnsRecord(id, body, init) {
return this.request("POST", `/v1/domains/${encodeURIComponent(String(id))}/dns`, {
body,
query: undefined,
init
});
}
async listOffers(id, init) {
return this.request("GET", `/v1/domains/${encodeURIComponent(String(id))}/offers`, {
body: undefined,
query: undefined,
init
});
}
async createOffer(id, body, init) {
return this.request("POST", `/v1/domains/${encodeURIComponent(String(id))}/offers`, {
body,
query: undefined,
init
});
}
async requestDomainProvisioning(body, init) {
return this.request("POST", `/v1/provisioning`, {
body,
query: undefined,
init
});
}
async adoptOwnedDomainProvisioning(body, init) {
return this.request("POST", `/v1/provisioning/adopt`, {
body,
query: undefined,
init
});
}
async getDomainProvisioningByName(name, init) {
return this.request("GET", `/v1/provisioning/by-name/${encodeURIComponent(String(name))}`, {
body: undefined,
query: undefined,
init
});
}
async reconcileProvisionedDomainDns(name, body, init) {
return this.request("POST", `/v1/provisioning/by-name/${encodeURIComponent(String(name))}/dns-reconcile`, {
body,
query: undefined,
init
});
}
async reconcileProvisionedDomainEmailDns(name, body, init) {
return this.request("POST", `/v1/provisioning/by-name/${encodeURIComponent(String(name))}/dns-reconcile/mail`, {
body,
query: undefined,
init
});
}
async listProvisionedDomainDnsReconciliations(name, query, init) {
return this.request("GET", `/v1/provisioning/by-name/${encodeURIComponent(String(name))}/dns-reconciliations`, {
body: undefined,
query,
init
});
}
async getProvisionedDomainDnsState(name, query, init) {
return this.request("GET", `/v1/provisioning/by-name/${encodeURIComponent(String(name))}/dns-state`, {
body: undefined,
query,
init
});
}
async getDomainProvisioning(id, init) {
return this.request("GET", `/v1/provisioning/${encodeURIComponent(String(id))}`, {
body: undefined,
query: undefined,
init
});
}
async advanceDomainProvisioning(id, init) {
return this.request("POST", `/v1/provisioning/${encodeURIComponent(String(id))}/advance`, {
body: undefined,
query: undefined,
init
});
}
async getDomainStats(init) {
return this.request("GET", `/v1/stats`, {
body: undefined,
query: undefined,
init
});
}
async getVersion(init) {
return this.request("GET", `/version`, {
body: undefined,
query: undefined,
init
});
}
}
// ../contracts/dist/client/storage.js
import { createRequire as createRequire2 } from "module";
var MAX_PACKAGE_BYTES2 = 1024 * 1024;
var MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
var INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
var CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
var AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
var requireSecretsSdk2 = createRequire2(import.meta.url);
var IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
var AUTHORITY_OVERRIDE_HEADERS2 = new Set([
"host",
":authority",
"forwarded",
"x-forwarded-host",
"x-original-host"
]);
// src/lib/domains-resolver.ts
var DOMAINS_APP_NAME = "domains";
var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
function isAmbientEnv(env) {
if (typeof process !== "undefined" && env === process.env)
return true;
return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
}
function domainsResolverEnv(env) {
const blanks = domainsAuthorityEnvKeys().filter((key) => (key in env) && (env[key] ?? "").trim() === "");
if (blanks.length === 0)
return env;
const next = { ...env };
for (const key of blanks)
delete next[key];
return next;
}
function domainsResolverInputs(env, credentials = {}) {
assertDomainsClientStorage(env);
const normalised = domainsResolverEnv(env);
if (normalised === env)
return { env: normalised, credentials };
const keychain = { ...credentials.keychain };
if (keychain.enabled === undefined && keychain.run === undefined) {
keychain.enabled = isAmbientEnv(env);
}
return { env: normalised, credentials: { ...credentials, keychain } };
}
// src/sdk/index.ts
function createDomainsClientFromEnv(env = process.env, overrides = {}) {
assertDomainsClientStorage(env);
const { baseUrl: baseUrlOverride, apiKey: apiKeyOverride, profile, keychain, ...rest } = overrides;
if (baseUrlOverride !== undefined) {
return new DomainsClient({
baseUrl: baseUrlOverride,
...apiKeyOverride !== undefined ? { apiKey: apiKeyOverride } : {},
...rest
});
}
const credentials = {
...apiKeyOverride !== undefined ? { apiKey: apiKeyOverride } : {},
...profile ? { profile } : {},
...keychain ? { keychain } : {}
};
const { env: resolverEnv, credentials: resolverCredentials } = domainsResolverInputs(env, credentials);
const resolution = resolveClientTransport(DOMAINS_APP_NAME, resolverEnv, {
credentials: resolverCredentials
});
const credential = resolveCredential(DOMAINS_APP_NAME, resolverEnv, resolverCredentials);
if (!credential) {
throw new Error("domains SDK: no API key resolved from any credential tier; refusing to build an unauthenticated client. " + "Looked at HASNA_DOMAINS_API_KEY_OVERRIDE / HASNA_PROFILE / HASNA_DOMAINS_API_KEY_REF, the Keychain item " + "hasna.credentials.domains.api-key, ~/.hasna/domains/config/credentials, then HASNA_DOMAINS_API_KEY.");
}
const baseUrl = resolution.baseUrl.replace(/\/v1$/, "");
const baseFetch = rest.fetch ?? ((input, init) => fetch(input, init));
const fetchWithFreshCredential = async (input, init) => {
const headers = {};
new Headers(init?.headers ?? {}).forEach((value, key) => {
headers[key] = value;
});
try {
const fresh = resolveCredential(DOMAINS_APP_NAME, resolverEnv, resolverCredentials);
if (fresh) {
const key = fresh.tier === "pointer" ? (await completePointerCredential(DOMAINS_APP_NAME, fresh, resolverEnv)).apiKey : fresh.apiKey;
headers["x-api-key"] = key;
}
} catch {}
return baseFetch(input, { ...init, headers });
};
return new DomainsClient({
baseUrl,
apiKey: credential.apiKey,
...rest,
fetch: fetchWithFreshCredential
});
}
export {
ApiError,
DomainsClient,
createDomainsClientFromEnv
};