@shelbyb/daywalker-sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +38 -24
  2. package/dist/analytics/index.d.ts +26 -3
  3. package/dist/analytics/index.js +107 -23
  4. package/dist/cli.js +136 -29
  5. package/dist/client.d.ts +44 -3
  6. package/dist/client.js +372 -73
  7. package/dist/config.d.ts +14 -2
  8. package/dist/config.js +36 -0
  9. package/dist/crypto/encoding.d.ts +5 -0
  10. package/dist/crypto/encoding.js +14 -0
  11. package/dist/crypto/index.d.ts +3 -3
  12. package/dist/crypto/index.js +2 -2
  13. package/dist/crypto/keys.d.ts +3 -3
  14. package/dist/crypto/keys.js +28 -7
  15. package/dist/crypto/signatures.d.ts +54 -11
  16. package/dist/crypto/signatures.js +310 -94
  17. package/dist/express.d.ts +1 -0
  18. package/dist/express.js +8 -6
  19. package/dist/flags/client.d.ts +34 -5
  20. package/dist/flags/client.js +93 -22
  21. package/dist/flags/common/events.d.ts +9 -0
  22. package/dist/flags/common/events.js +77 -6
  23. package/dist/flags/common/index.d.ts +3 -3
  24. package/dist/flags/common/index.js +3 -3
  25. package/dist/flags/common/sse.d.ts +24 -3
  26. package/dist/flags/common/sse.js +85 -15
  27. package/dist/flags/common/version.d.ts +2 -0
  28. package/dist/flags/common/version.js +2 -0
  29. package/dist/flags/engine/context.js +4 -3
  30. package/dist/flags/engine/index.d.ts +1 -1
  31. package/dist/flags/engine/index.js +1 -1
  32. package/dist/flags/engine/operators.d.ts +25 -0
  33. package/dist/flags/engine/operators.js +64 -8
  34. package/dist/flags/node.d.ts +27 -15
  35. package/dist/flags/node.js +54 -7
  36. package/dist/flags/react.d.ts +6 -4
  37. package/dist/flags/react.js +47 -17
  38. package/dist/index.d.ts +7 -6
  39. package/dist/index.js +4 -4
  40. package/dist/middleware.d.ts +18 -0
  41. package/dist/middleware.js +28 -7
  42. package/dist/next.d.ts +63 -6
  43. package/dist/next.js +182 -36
  44. package/dist/openfeature/server.d.ts +3 -0
  45. package/dist/openfeature/server.js +14 -2
  46. package/dist/openfeature/shared.d.ts +1 -1
  47. package/dist/openfeature/shared.js +22 -2
  48. package/dist/openfeature/web.js +9 -3
  49. package/dist/types.d.ts +58 -3
  50. package/dist/types.js +6 -0
  51. package/docs/analytics.md +1 -1
  52. package/docs/flags-node.md +1 -1
  53. package/docs/flags.md +2 -1
  54. package/package.json +34 -16
  55. package/assets/logo-512.png +0 -0
package/dist/cli.js CHANGED
@@ -7,20 +7,27 @@ import { generateSigningKey, loadSigningKey, registerPublicKey, redactSecrets }
7
7
  const HELP = `daywalker: connect a service to Daywalker Auth (RFC 9421 / Push Architecture)
8
8
 
9
9
  Usage:
10
- daywalker init --service <slug> [--api-key <key>] [--domain <host>] [--auth-url <url>] [--json|--export] [--print-key]
11
- daywalker rotate [--service <slug>] [--api-key <key>] [--auth-url <url>] [--json|--export] [--print-key]
10
+ daywalker init --service <slug> [--api-key-stdin] [--domain <host>] [--auth-url <url>] [--force] [--skip-register] [--json|--export] [--print-key]
11
+ daywalker rotate [--service <slug>] [--api-key-stdin] [--domain <host>] [--auth-url <url>] [--skip-register] [--json|--export] [--print-key]
12
12
  daywalker check
13
13
 
14
14
  init Generates an Ed25519 keypair, registers the public key directly with Daywalker Auth
15
15
  (POST /api/v1/keys/register), and writes DAYWALKER_* settings to .env.local (or outputs JSON with --json).
16
- No public ingress or /.well-known route handlers are required.
16
+ Nothing is written if registration fails. --force replaces an existing key (kept as
17
+ DAYWALKER_SIGNING_KEY_PREVIOUS for rollback). No public ingress or /.well-known route handlers are required.
17
18
  rotate Generates a new Ed25519 key, registers it with Daywalker Auth, and keeps the old one
18
19
  in .env.local as DAYWALKER_SIGNING_KEY_PREVIOUS during deployment transition.
20
+ Nothing is written, and the exit code is non-zero, unless registration succeeds.
19
21
  check Validates configuration and confirms Daywalker Auth connection and signature readiness.
20
22
 
21
23
  Options:
24
+ --api-key-stdin Read the Daywalker API key from stdin (e.g. \`op read ... | daywalker init --api-key-stdin\`).
25
+ The key is otherwise taken from DAYWALKER_API_KEY in the environment or .env.local.
26
+ --api-key <key> also works but exposes the key in shell history and process listings.
27
+ --skip-register Do not register the public key (alias: --no-register); register it yourself before deploying.
22
28
  --json, --export Output configuration as JSON for piping into Vercel, DigitalOcean, Fly.io, AWS SSM, etc.
23
- --print-key Print the private key to stdout (disabled by default for security).
29
+ The JSON includes the private key (DAYWALKER_SIGNING_KEY); nothing is written to .env.local.
30
+ --print-key Print the private key to stdout after writing .env.local (disabled by default for security).
24
31
  `;
25
32
  function args(argv) {
26
33
  const out = {};
@@ -42,6 +49,35 @@ function fail(message) {
42
49
  console.error(redactSecrets(`✖ ${message}`));
43
50
  process.exit(1);
44
51
  }
52
+ /** Read all of stdin (for --api-key-stdin), so the key never appears in argv. */
53
+ async function readStdin() {
54
+ if (process.stdin.isTTY)
55
+ fail("--api-key-stdin expects the key on stdin (pipe it in)");
56
+ const chunks = [];
57
+ for await (const chunk of process.stdin)
58
+ chunks.push(Buffer.from(chunk));
59
+ return Buffer.concat(chunks).toString("utf8").trim();
60
+ }
61
+ async function resolveApiKey(opts, existing) {
62
+ if (typeof opts["api-key"] === "string")
63
+ return opts["api-key"].trim() || null;
64
+ if (opts["api-key-stdin"]) {
65
+ const fromStdin = await readStdin();
66
+ if (!fromStdin)
67
+ fail("--api-key-stdin: no API key received on stdin");
68
+ return fromStdin;
69
+ }
70
+ return String(existing.get("DAYWALKER_API_KEY") ?? process.env.DAYWALKER_API_KEY ?? "").trim() || null;
71
+ }
72
+ function skipRegister(opts) {
73
+ return Boolean(opts["skip-register"] || opts["no-register"]);
74
+ }
75
+ /** --json prints the private key by design (for secret stores); say so on stderr so it is never a surprise. */
76
+ function warnJsonIncludesKey(values) {
77
+ if (values.DAYWALKER_SIGNING_KEY) {
78
+ console.warn("! --json output includes the private signing key (DAYWALKER_SIGNING_KEY); pipe it straight into your secret store.");
79
+ }
80
+ }
45
81
  function readEnvFile(file) {
46
82
  const map = new Map();
47
83
  if (!fs.existsSync(file))
@@ -53,8 +89,35 @@ function readEnvFile(file) {
53
89
  }
54
90
  return map;
55
91
  }
56
- function writeEnvFile(file, values) {
57
- const lines = fs.existsSync(file) ? fs.readFileSync(file, "utf8").split("\n") : [];
92
+ /**
93
+ * Values are written as KEY="value". Anything a dotenv parser could interpret (quotes, `$` expansion
94
+ * as done by Next.js / dotenv-expand, backslash escapes, newlines or other control characters) is
95
+ * rejected instead of escaped, since parsers disagree on escaping rules.
96
+ */
97
+ const UNSAFE_ENV_VALUE = /["'`$\\\u0000-\u001f\u007f]/;
98
+ function assertSafeEnvValues(values) {
99
+ for (const [key, value] of Object.entries(values)) {
100
+ if (value !== null && UNSAFE_ENV_VALUE.test(value)) {
101
+ fail(`${key} contains quotes, "$", backslashes, newlines or control characters; refusing to write it to .env.local`);
102
+ }
103
+ }
104
+ }
105
+ function writeEnvFile(file, values, comments = {}) {
106
+ assertSafeEnvValues(values);
107
+ let stat = null;
108
+ try {
109
+ stat = fs.lstatSync(file);
110
+ }
111
+ catch (err) {
112
+ if (err.code !== "ENOENT")
113
+ throw err;
114
+ }
115
+ // Never follow a symlink (it could point anywhere, e.g. a world-readable file); refuse non-files.
116
+ if (stat && stat.isSymbolicLink())
117
+ fail(`${path.basename(file)} is a symlink; refusing to write secrets through it`);
118
+ if (stat && !stat.isFile())
119
+ fail(`${path.basename(file)} is not a regular file`);
120
+ const lines = stat ? fs.readFileSync(file, "utf8").split("\n") : [];
58
121
  const seen = new Set();
59
122
  const out = [];
60
123
  for (const line of lines) {
@@ -73,10 +136,38 @@ function writeEnvFile(file, values) {
73
136
  if (added.length) {
74
137
  if (out.length)
75
138
  out.push("");
76
- out.push("# Daywalker Auth (managed by `daywalker init`)", ...added.map(([k, v]) => `${k}="${v}"`));
139
+ out.push("# Daywalker Auth (managed by `daywalker init`)");
140
+ for (const [k, v] of added) {
141
+ if (comments[k])
142
+ out.push(`# ${comments[k]}`);
143
+ out.push(`${k}="${v}"`);
144
+ }
145
+ }
146
+ // Atomic replace: write a fresh 0600 temp file in the same directory, then rename it over the
147
+ // target, so the secrets never sit in a file with looser permissions and readers never see a
148
+ // half-written file.
149
+ const tmp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
150
+ const fd = fs.openSync(tmp, "wx", 0o600);
151
+ try {
152
+ fs.fchmodSync(fd, 0o600);
153
+ fs.writeFileSync(fd, out.join("\n") + "\n");
154
+ fs.fsyncSync(fd);
155
+ }
156
+ catch (err) {
157
+ fs.closeSync(fd);
158
+ fs.rmSync(tmp, { force: true });
159
+ throw err;
160
+ }
161
+ fs.closeSync(fd);
162
+ try {
163
+ fs.renameSync(tmp, file);
164
+ }
165
+ catch (err) {
166
+ fs.rmSync(tmp, { force: true });
167
+ throw err;
77
168
  }
78
- fs.writeFileSync(file, out.join("\n") + "\n", { mode: 0o600 });
79
169
  }
170
+ const PREVIOUS_KEY_COMMENT = "Previous signing key, kept for rollback while the new key rolls out; remove it once deployed.";
80
171
  function ensureGitignored(dir, entry) {
81
172
  const file = path.join(dir, ".gitignore");
82
173
  const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
@@ -102,15 +193,22 @@ async function init(opts) {
102
193
  if (!domain)
103
194
  fail(`--domain (${rawDomain}) is not a valid public hostname`);
104
195
  }
105
- const apiKey = String(opts["api-key"] ?? existing.get("DAYWALKER_API_KEY") ?? process.env.DAYWALKER_API_KEY ?? "").trim() || null;
196
+ const apiKey = await resolveApiKey(opts, existing);
106
197
  const authUrl = String(opts["auth-url"] ?? existing.get("DAYWALKER_AUTH_URL") ?? process.env.DAYWALKER_AUTH_URL ?? "https://auth.daywalker.dev").replace(/\/+$/, "");
107
- const keyExists = Boolean(existing.get("DAYWALKER_SIGNING_KEY")) && !opts.force;
198
+ const existingKey = existing.get("DAYWALKER_SIGNING_KEY") || null;
199
+ const keyExists = Boolean(existingKey) && !opts.force;
108
200
  const key = keyExists ? null : await generateSigningKey();
109
- const activeKey = key ?? (existing.get("DAYWALKER_SIGNING_KEY") ? await loadSigningKey(existing.get("DAYWALKER_SIGNING_KEY")) : null);
201
+ // --force over an existing key: keep the old one so a deployment can roll back to it.
202
+ const previousKey = key && existingKey ? existingKey : null;
203
+ const activeKey = key ?? (existingKey ? await loadSigningKey(existingKey) : null);
110
204
  const kid = activeKey?.publicJwk.kid ?? String(opts["key-id"] ?? "");
111
- // Register public key directly with Daywalker Auth if API key is provided
205
+ // Register public key directly with Daywalker Auth if API key is provided. Replacing a key
206
+ // requires registration: writing an unregistered key would break request signing.
112
207
  let registered = false;
113
- if (apiKey && activeKey && !opts["skip-register"]) {
208
+ if (previousKey && !apiKey && !skipRegister(opts)) {
209
+ fail("Replacing DAYWALKER_SIGNING_KEY requires registering the new key: set DAYWALKER_API_KEY (or use --api-key-stdin), or pass --skip-register to write it anyway");
210
+ }
211
+ if (apiKey && activeKey && !skipRegister(opts)) {
114
212
  if (!isJson)
115
213
  console.log(`• Registering public key (${activeKey.publicJwk.kid}) with Daywalker Auth...`);
116
214
  const regResult = await registerPublicKey({
@@ -126,13 +224,11 @@ async function init(opts) {
126
224
  console.log(`✔ Public key registered with Daywalker Auth (Push Architecture)`);
127
225
  }
128
226
  else {
129
- if (!isJson) {
130
- console.warn(redactSecrets(`! Key registration warning (${regResult.status}): ${regResult.error}`));
131
- console.warn(` You can register the key later by re-running init with your DAYWALKER_API_KEY.`);
132
- }
227
+ fail(`Key registration failed (${regResult.status}): ${regResult.error}\n` +
228
+ ` Nothing was written. Fix the problem and re-run, or pass --skip-register to write the key without registering it.`);
133
229
  }
134
230
  }
135
- else if (!apiKey && !opts["skip-register"] && !isJson) {
231
+ else if (!apiKey && !skipRegister(opts) && !isJson) {
136
232
  console.log(`• Note: Set DAYWALKER_API_KEY in .env.local to automatically register public keys.`);
137
233
  }
138
234
  const signingKeyStr = key?.privateKeyPkcs8Base64 ?? existing.get("DAYWALKER_SIGNING_KEY") ?? "";
@@ -143,12 +239,14 @@ async function init(opts) {
143
239
  ...(domain ? { DAYWALKER_DOMAIN: domain } : {}),
144
240
  ...(apiKey ? { DAYWALKER_API_KEY: apiKey } : {}),
145
241
  ...(signingKeyStr ? { DAYWALKER_SIGNING_KEY: signingKeyStr } : {}),
242
+ ...(previousKey ? { DAYWALKER_SIGNING_KEY_PREVIOUS: previousKey } : {}),
146
243
  };
147
244
  if (isJson) {
245
+ warnJsonIncludesKey(envValues);
148
246
  console.log(JSON.stringify(envValues, null, 2));
149
247
  return;
150
248
  }
151
- writeEnvFile(envFile, envValues);
249
+ writeEnvFile(envFile, envValues, { DAYWALKER_SIGNING_KEY_PREVIOUS: PREVIOUS_KEY_COMMENT });
152
250
  ensureGitignored(dir, ".env.local");
153
251
  console.log(`✔ Daywalker configured for service "${slug}"${domain ? ` (${domain})` : ""}`);
154
252
  console.log(` • RFC 9421 / RFC 9530 HTTP Message Signing active`);
@@ -157,6 +255,8 @@ async function init(opts) {
157
255
  console.log(` • Key ID (RFC 7638 thumbprint): ${kid}`);
158
256
  if (registered)
159
257
  console.log(` • Zero-ingress push architecture: public key registered directly`);
258
+ if (previousKey)
259
+ console.log(` • Previous key kept as DAYWALKER_SIGNING_KEY_PREVIOUS for rollback; remove it once the new key is deployed.`);
160
260
  if (opts["print-key"] && signingKeyStr)
161
261
  console.log(`\nDAYWALKER_SIGNING_KEY=${signingKeyStr}`);
162
262
  }
@@ -169,11 +269,16 @@ async function rotate(opts) {
169
269
  if (!current)
170
270
  fail("No DAYWALKER_SIGNING_KEY found; run `daywalker init` first");
171
271
  const slug = String(opts.service ?? existing.get("DAYWALKER_SERVICE_SLUG") ?? process.env.DAYWALKER_SERVICE_SLUG ?? "").trim().toLowerCase();
172
- const apiKey = String(opts["api-key"] ?? existing.get("DAYWALKER_API_KEY") ?? process.env.DAYWALKER_API_KEY ?? "").trim() || null;
272
+ const apiKey = await resolveApiKey(opts, existing);
173
273
  const authUrl = String(opts["auth-url"] ?? existing.get("DAYWALKER_AUTH_URL") ?? process.env.DAYWALKER_AUTH_URL ?? "https://auth.daywalker.dev").replace(/\/+$/, "");
174
274
  const rotateDomain = publicHostname(String(opts.domain ?? existing.get("DAYWALKER_DOMAIN") ?? process.env.DAYWALKER_DOMAIN ?? ""));
175
275
  const key = await generateSigningKey();
176
- if (apiKey && slug) {
276
+ // The new key only becomes active once Daywalker Auth knows it, so registration must succeed
277
+ // before anything is written (unless the caller explicitly registers it some other way).
278
+ if (!skipRegister(opts)) {
279
+ if (!apiKey || !slug) {
280
+ fail("rotate needs DAYWALKER_API_KEY and DAYWALKER_SERVICE_SLUG to register the new key (or pass --skip-register to write it unregistered)");
281
+ }
177
282
  if (!isJson)
178
283
  console.log(`• Registering new public key (${key.publicJwk.kid}) with Daywalker Auth...`);
179
284
  const regResult = await registerPublicKey({
@@ -183,25 +288,24 @@ async function rotate(opts) {
183
288
  domain: rotateDomain,
184
289
  publicJwk: key.publicJwk,
185
290
  });
186
- if (regResult.ok) {
187
- if (!isJson)
188
- console.log(`✔ New public key registered with Daywalker Auth`);
189
- }
190
- else {
191
- if (!isJson)
192
- console.warn(redactSecrets(`! Key registration warning: ${regResult.error}`));
291
+ if (!regResult.ok) {
292
+ fail(`Key registration failed (${regResult.status}): ${regResult.error}\n Nothing was written; the current key is still active.`);
193
293
  }
294
+ if (!isJson)
295
+ console.log(`✔ New public key registered with Daywalker Auth`);
194
296
  }
195
297
  const envValues = {
196
298
  DAYWALKER_KEY_ID: key.publicJwk.kid,
197
299
  DAYWALKER_SIGNING_KEY: key.privateKeyPkcs8Base64 ?? "",
300
+ // Kept for rollback: services still signing with the old key keep working during rollout.
198
301
  DAYWALKER_SIGNING_KEY_PREVIOUS: current,
199
302
  };
200
303
  if (isJson) {
304
+ warnJsonIncludesKey(envValues);
201
305
  console.log(JSON.stringify(envValues, null, 2));
202
306
  return;
203
307
  }
204
- writeEnvFile(envFile, envValues);
308
+ writeEnvFile(envFile, envValues, { DAYWALKER_SIGNING_KEY_PREVIOUS: PREVIOUS_KEY_COMMENT });
205
309
  console.log(`✔ New key ${key.publicJwk.kid} written; previous key preserved as DAYWALKER_SIGNING_KEY_PREVIOUS.`);
206
310
  console.log(` After deployment rollout is complete, you may remove DAYWALKER_SIGNING_KEY_PREVIOUS.`);
207
311
  if (opts["print-key"])
@@ -233,6 +337,9 @@ async function check() {
233
337
  async function main() {
234
338
  const [cmd, ...rest] = process.argv.slice(2);
235
339
  const parsedArgs = args(rest);
340
+ if (typeof parsedArgs["api-key"] === "string") {
341
+ console.warn("! --api-key exposes the key in shell history and process listings; prefer DAYWALKER_API_KEY in the environment or .env.local.");
342
+ }
236
343
  if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
237
344
  console.log(HELP);
238
345
  return;
package/dist/client.d.ts CHANGED
@@ -1,26 +1,57 @@
1
1
  import { type ResolvedConfig } from "./config.js";
2
2
  import { type DaywalkerConfig, type PublicJwk, type RetryOptions, type SelfCheckResult, type TokenPayload, type ValidateResult } from "./types.js";
3
+ /** Validation results kept in memory; the least recently used are evicted past this. */
4
+ export declare const MAX_VALIDATION_CACHE_ENTRIES = 1000;
5
+ /** Longest 429 `Retry-After` waited out by default (`retry.maxRetryAfterMs`); longer ones return the 429. */
6
+ export declare const MAX_RETRY_AFTER_MS = 60000;
3
7
  export type DaywalkerFetchInit = Omit<RequestInit, "body"> & {
8
+ /**
9
+ * Plain objects and arrays are sent as JSON. Strings, Uint8Array / ArrayBuffer(View) and URLSearchParams
10
+ * are sent (and digested) byte for byte, and Blobs are read into memory first when signing. FormData and
11
+ * ReadableStream bodies can't be digested, so they throw when a signing key is configured.
12
+ */
4
13
  body?: BodyInit | object | null;
5
14
  retry?: RetryOptions;
15
+ /** Per-attempt timeout overriding `config.timeoutMs`; 0 disables. */
16
+ timeoutMs?: number;
17
+ /** Sent as `Idempotency-Key` (unless already set) and lets non-idempotent requests retry network errors. */
18
+ idempotencyKey?: string;
6
19
  };
7
20
  export declare class Daywalker {
8
21
  private readonly _config;
9
22
  private readonly _rawConfig;
10
23
  private _signingKeyPromise?;
24
+ private _previousSigningKeyPromise?;
11
25
  private readonly _cache;
12
26
  private readonly _inflight;
13
27
  constructor(config?: DaywalkerConfig);
14
28
  get config(): ResolvedConfig;
15
29
  toJSON(): Record<string, unknown>;
16
30
  private getLoadedSigningKey;
31
+ private getLoadedPreviousSigningKey;
17
32
  /**
18
- * Signs and executes requests with automatic retry and exponential backoff on 429 responses.
33
+ * Signs and executes a request to Daywalker Auth (same origin as `authUrl` only).
34
+ * - Redirects are never followed: a 3xx throws a DaywalkerError (code UNEXPECTED_REDIRECT), so the API key,
35
+ * signature and body never reach another URL and no redirect target's answer is trusted.
36
+ * - Each attempt times out after `timeoutMs` (default 5000ms) of waiting for response headers; the caller's
37
+ * `signal` aborts immediately, including during backoff, and is never retried.
38
+ * - 429s are retried with exponential backoff, honoring `Retry-After` up to `retry.maxRetryAfterMs`. Network
39
+ * errors and timeouts are retried only for GET / HEAD / OPTIONS, with an `idempotencyKey`, or when
40
+ * `retry.retryNetworkErrors` is true. Options merge as defaults < `config.retry` < `init.retry`.
41
+ * - An explicit `Authorization` header is sent as given; otherwise `Bearer <apiKey>` is added. The signature
42
+ * covers method, path, query, authority and Content-Digest, never Authorization.
19
43
  */
20
44
  fetch(path: string, init?: DaywalkerFetchInit): Promise<Response>;
45
+ private _cacheGet;
46
+ private _cacheSet;
21
47
  /**
22
- * Validates incoming bearer tokens with memory caching and discriminated union return.
48
+ * Validates an incoming bearer token with memory caching and discriminated union return.
23
49
  * Supports zero-egress offline verification for JWT token leases when Daywalker's public key is configured.
50
+ * A missing token is always rejected (401); it never falls back to this service's own API key —
51
+ * use `validateServiceKey()` for that.
52
+ * Online validation fails closed: only a 2xx JSON answer with `valid: true` and a sane shape is accepted.
53
+ * Outages (network errors, timeouts, 5xx, malformed answers) return status 503 with error NETWORK_ERROR or
54
+ * SERVICE_UNAVAILABLE and are never cached. Each call gets its own copy of the (cached) result.
24
55
  */
25
56
  validateToken(tokenOrOptions?: string | {
26
57
  token?: string;
@@ -33,6 +64,14 @@ export declare class Daywalker {
33
64
  offlineOnly?: boolean;
34
65
  }): Promise<ValidateResult>;
35
66
  private _validateOnline;
67
+ /**
68
+ * Service health check: validates this service's own configured API key (DAYWALKER_API_KEY).
69
+ * Never use this to authenticate an incoming request.
70
+ */
71
+ validateServiceKey(options?: {
72
+ serviceSlug?: string;
73
+ forceRefresh?: boolean;
74
+ }): Promise<ValidateResult>;
36
75
  /**
37
76
  * Throws DaywalkerAuthError or DaywalkerRateLimitError if token is invalid or rate limited.
38
77
  */
@@ -55,12 +94,14 @@ export declare class Daywalker {
55
94
  }>;
56
95
  /**
57
96
  * Fetches Daywalker's public JWKS (GET /.well-known/jwks.json) for offline lease verification.
97
+ * Uses the configured `fetch`, sends no credentials, never follows redirects and times out after `timeoutMs`.
58
98
  */
59
99
  fetchJwks(): Promise<{
60
100
  keys: PublicJwk[];
61
101
  }>;
62
102
  /**
63
- * The public JWK(s) configured for this service.
103
+ * The public JWK(s) configured for this service: the current signing key, then the previous one
104
+ * (`previousSigningKey`) during a rotation, so requests from instances still on the old key keep verifying.
64
105
  */
65
106
  keys(): Promise<{
66
107
  keys: PublicJwk[];