@exortek/apikey 1.1.1 → 1.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @exortek/apikey
2
2
 
3
+ ## 1.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 89aea87: Ship self-contained TypeScript declarations. Every package's emitted `.d.ts` referenced `@exortek/shared`
8
+ (e.g. `import { BaseError } from '@exortek/shared/errors'`), but `@exortek/shared` is a private, never-published
9
+ workspace package that is inlined into each bundle at build time. A TypeScript consumer therefore hit
10
+ `Cannot find module '@exortek/shared/…'` (with `skipLibCheck` off) or silently degraded error-class types like
11
+ `ApiKeyError` — losing its constructor signature and `.code` / `.message` — with `skipLibCheck` on.
12
+
13
+ The build now runs a declaration-bundling pass (`rollup-plugin-dts`) after `tsc`, flattening each entry's `.d.ts` and
14
+ inlining the `@exortek/shared` types so the shipped declarations are fully self-contained. Runtime deps and `node:*`
15
+ stay external. No runtime or API change — types only.
16
+
17
+ ## 1.1.2
18
+
19
+ ### Patch Changes
20
+
21
+ - b9e0647: Publish only the package-root README, CHANGELOG and LICENSE.
22
+
23
+ The `files` list matched those names at any depth rather than just the root, so a nested document was published
24
+ alongside them — `@exortek/oauth2` shipped its `examples/README.md`. The entries are now anchored to the package root.
25
+
26
+ - 0a94f13: Smaller bundles — the internal argument-guard helpers are now tree-shakeable.
27
+
28
+ Each package bundles the guard helpers it uses. They were previously built as one object holding all fourteen, which a
29
+ bundler cannot take apart, so every package shipped all of them regardless of how many it called. They are now
30
+ individually importable, and each package pulls in only what it uses.
31
+
32
+ No API change: the errors, codes and messages raised by argument validation are identical. Published bundles shrink by
33
+ roughly 7-18% depending on the package.
34
+
3
35
  ## 1.1.1
4
36
 
5
37
  ### Patch Changes
package/README.md CHANGED
@@ -3,11 +3,11 @@
3
3
  > Stripe-style prefixed API keys for Node.js 22+ — 3-segment tokens (`sk_live_<id>_<secret>`), HMAC-SHA256 storage hash with optional pepper rotation, scope allowlists, memory + Redis stores, Express + Fastify middleware. Zero non-`@exortek/*` runtime dependencies.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@exortek/apikey.svg?color=cb3837)](https://www.npmjs.com/package/@exortek/apikey)
6
- [![tests](https://img.shields.io/badge/tests-passing-brightgreen)](https://github.com/ExorTek/auth/actions/workflows/ci.yml)
6
+ [![tests](https://github.com/ExorTek/auth/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ExorTek/auth/actions/workflows/ci.yml)
7
7
  [![node](https://img.shields.io/node/v/@exortek/apikey.svg?color=339933)](https://nodejs.org)
8
8
  [![install size](https://packagephobia.com/badge?p=@exortek/apikey)](https://packagephobia.com/result?p=@exortek/apikey)
9
9
  [![types](https://img.shields.io/badge/types-included-3178C6)](./dist/index.d.ts)
10
- [![license](https://img.shields.io/npm/l/@exortek/apikey.svg?color=blue)](./LICENSE)
10
+ [![license](https://img.shields.io/npm/l/@exortek/apikey.svg?color=blue)](https://github.com/ExorTek/auth/blob/master/LICENSE)
11
11
 
12
12
  📖 **Docs:** [**auth.memet.dev/apikey**](https://auth.memet.dev/apikey)
13
13
 
@@ -325,4 +325,4 @@ Expected verify failures do NOT throw — they surface as `{ valid: false, reaso
325
325
 
326
326
  ## License
327
327
 
328
- MIT © ExorTek — see [LICENSE](./LICENSE).
328
+ MIT © ExorTek — see [LICENSE](https://github.com/ExorTek/auth/blob/master/LICENSE).
package/dist/index.cjs CHANGED
@@ -351,20 +351,33 @@ class ApiKeyError extends BaseError {
351
351
  * `@exortek/shared/validate`: **schema** for whole options objects,
352
352
  * **asserts** for one-liner argument guards at the call site.
353
353
  *
354
- * The consumer surface is {@link defineGuards} (or the raw
355
- * {@link bindAsserts} for full control). Each package binds the assert
356
- * set to its own typed error class once (in `internal/guards.js`), so
357
- * every argument failure throws that package's class —
358
- * `err instanceof CryptoError` holds and users see at a glance which
359
- * package raised the error. The bound `parse` bridges
360
- * `@exortek/shared/validate` schemas to the same error class.
354
+ * Every guard is exported as a `make…` **factory** taking the binding
355
+ * package's `wrap` and returning the guard itself. A package binds the
356
+ * guards it calls once, in its `internal/guards.js`, so every argument
357
+ * failure throws that package's error class — `err instanceof
358
+ * CryptoError` holds, and users see at a glance which package raised it.
359
+ *
360
+ * // packages/<pkg>/src/internal/guards.js
361
+ * import { makeAssertString, makeInvalidArgument } from '@exortek/shared/asserts';
362
+ * import { CryptoError, ErrorCode } from '../errors.js';
363
+ *
364
+ * const wrap = (message, extra) => new CryptoError(ErrorCode.INVALID_ARGUMENT, message, extra);
365
+ *
366
+ * export const assertString = makeAssertString(wrap);
367
+ * export const invalidArgument = makeInvalidArgument(wrap);
368
+ *
369
+ * Importing each factory by name is what makes the module
370
+ * tree-shakeable. An earlier version handed back one object carrying
371
+ * every guard, so all of them shipped in every package's bundle no
372
+ * matter how few were used — `assertBoolean` reached fifteen published
373
+ * packages without a single call site. Import what you call.
361
374
  *
362
375
  * **Path naming convention** (used as the `name` argument):
363
376
  *
364
377
  * `<publicFunction>[.options|.config][.<field>]`
365
378
  *
366
- * - `assertString(name, 'createUser.name')` — top-level arg
367
- * - `assertPositiveInt(n, 'scrypt.options.r')` — nested option
379
+ * - `assertString(name, 'createUser.name')` — top-level arg
380
+ * - `assertPositiveInt(n, 'scrypt.options.r')` — nested option
368
381
  * - `assertBytesOrString(pwd, 'pepper.wrap.password')` — method arg
369
382
  *
370
383
  * Keep the path short: the emitted error is `"<name> must be <desc>"`,
@@ -373,301 +386,34 @@ class ApiKeyError extends BaseError {
373
386
  * encryptSymmetric()"), not for restating the field.
374
387
  */
375
388
 
376
- /**
377
- * @typedef {{ cause?: unknown }} WrapExtra
378
- * Extra options forwarded to the wrap function, so `invalidArgument`
379
- * sites that catch an underlying error can preserve the `cause` chain
380
- * without falling back to a raw `new PackageError(...)`.
381
- *
382
- * @typedef {(message: string, extra?: WrapExtra) => Error} WrapFn
383
- * Constructs (never throws) the binding package's error, e.g.
384
- * `(m, { cause } = {}) => new CryptoError(ErrorCode.INVALID_ARGUMENT, m, { cause })`.
385
- *
386
- * @typedef {{ hint?: string }} AssertOptions
387
- * `hint` is appended to the message after an em-dash — use it for
388
- * actionable guidance ("pass the exact bytes returned by …").
389
- *
390
- * @typedef {{ safeParse: (input: unknown, path?: string) => { ok: true, value: unknown } | { ok: false, errors: string[] } }} ParseableSchema
391
- */
392
389
 
393
390
  /**
394
- * Build the failure message. `description` completes the sentence
395
- * `"<name> must be <description>"`; `hint` (optional) follows an
396
- * em-dash.
391
+ * Construct (not throw) the bound error with a free-form message — for
392
+ * `throw invalidArgument('…')` sites that don't fit the `X must be Y`
393
+ * shape (canonicalisation errors, cross-field constraint failures). Pass
394
+ * `{ cause }` when the failure was triggered by an underlying throw whose
395
+ * chain matters (e.g. `JSON.stringify` on a bigint).
397
396
  *
398
- * @param {string} name
399
- * @param {string} description
400
- * @param {string} [hint]
401
- * @returns {string}
397
+ * @type {(wrap: WrapFn) => (msg: string, extra?: WrapExtra) => Error}
402
398
  */
403
- function message(name, description, hint) {
404
- return `${name} must be ${description}${hint ? ` — ${hint}` : ''}`;
405
- }
406
-
407
- const ENCODINGS = new Set(['hex', 'base64', 'base64url']);
399
+ const makeInvalidArgument = wrap => (msg, extra) => wrap(msg, extra);
408
400
 
409
401
  /**
410
- * Bind the assert set to a package's error class. Call once per
411
- * package (conventionally in `internal/guards.js`) and import the
412
- * bound object everywhere:
413
- *
414
- * import { bindAsserts } from '@exortek/shared/asserts';
415
- * import { CryptoError, ErrorCode } from '../errors.js';
416
- * export const g = bindAsserts((m) => new CryptoError(ErrorCode.INVALID_ARGUMENT, m));
417
- *
418
- * // call sites:
419
- * g.assertPositiveInt(options.iterations, 'options.iterations');
420
- * const cfg = g.parse(OptionsSchema, options, 'options');
421
- *
422
- * @param {WrapFn} wrap
402
+ * Argument guards bound to `ApiKeyError` — the package-wide binding of
403
+ * `@exortek/shared/asserts`. Import guards from here, never from the
404
+ * shared module directly.
423
405
  */
424
- function bindAsserts(wrap) {
425
- /**
426
- * @param {string} name
427
- * @param {string} description
428
- * @param {AssertOptions} [opts]
429
- * @returns {never}
430
- */
431
- function fail(name, description, opts) {
432
- throw wrap(message(name, description, opts?.hint));
433
- }
434
406
 
435
- /**
436
- * Assert that `value` is a plain object (not `null`, not an array,
437
- * not a primitive).
438
- * @param {unknown} value
439
- * @param {string} name
440
- * @param {AssertOptions} [opts]
441
- */
442
- function assertObject(value, name, opts) {
443
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
444
- fail(name, 'an object', opts);
445
- }
446
- }
447
-
448
- return {
449
- /**
450
- * Construct (not throw) the bound error with a free-form message —
451
- * for `throw g.invalidArgument('…')` sites that don't fit the
452
- * `X must be Y` shape (canonicalisation errors, cross-field
453
- * constraint failures). Pass `{ cause }` when the argument-shape
454
- * failure was triggered by an underlying throw whose chain matters
455
- * (e.g. `JSON.stringify` on a bigint).
456
- *
457
- * @param {string} msg
458
- * @param {WrapExtra} [extra]
459
- * @returns {Error}
460
- */
461
- invalidArgument(msg, extra) {
462
- return wrap(msg, extra);
463
- },
464
-
465
- /**
466
- * Validate `input` against a `@exortek/shared/validate` schema;
467
- * failures throw the bound error carrying every collected message.
468
- * The bridge that keeps schema validation on the package's own
469
- * error surface.
470
- *
471
- * @param {ParseableSchema} schema
472
- * @param {unknown} input
473
- * @param {string} [path='options']
474
- * @returns {unknown} the parsed (possibly defaulted) value
475
- */
476
- parse(schema, input, path = 'options') {
477
- const r = schema.safeParse(input, path);
478
- if (!r.ok) {
479
- throw wrap(r.errors.join('; '));
480
- }
481
- return r.value;
482
- },
483
-
484
- /**
485
- * Assert that `value` is a non-negative safe integer (`0, 1, 2, …`).
486
- * @param {unknown} value
487
- * @param {string} name Argument name to include in the error message.
488
- * @param {AssertOptions} [opts]
489
- */
490
- assertNonNegativeInt(value, name, opts) {
491
- if (!Number.isSafeInteger(value) || /** @type {number} */ (value) < 0) {
492
- fail(name, 'a non-negative safe integer', opts);
493
- }
494
- },
495
-
496
- /**
497
- * Assert that `value` is a strictly positive safe integer (`1, 2, 3, …`).
498
- * @param {unknown} value
499
- * @param {string} name
500
- * @param {AssertOptions} [opts]
501
- */
502
- assertPositiveInt(value, name, opts) {
503
- if (!Number.isSafeInteger(value) || /** @type {number} */ (value) <= 0) {
504
- fail(name, 'a positive integer', opts);
505
- }
506
- },
507
-
508
- /**
509
- * Assert that `value` fits in a 48-bit unsigned integer (`0 … 2^48 − 1`).
510
- * Used for Unix millisecond timestamps in UUID v7 / ULID.
511
- * @param {unknown} value
512
- * @param {string} name
513
- * @param {AssertOptions} [opts]
514
- */
515
- assertUint48(value, name, opts) {
516
- if (
517
- !Number.isSafeInteger(value) ||
518
- /** @type {number} */ (value) < 0 ||
519
- /** @type {number} */ (value) > 0xffffffffffff
520
- ) {
521
- fail(name, 'a non-negative safe integer ≤ 2^48 − 1 (Unix ms since epoch)', opts);
522
- }
523
- },
524
-
525
- /**
526
- * Assert that `value` is a string (may be empty).
527
- * @param {unknown} value
528
- * @param {string} name
529
- * @param {AssertOptions} [opts]
530
- */
531
- assertString(value, name, opts) {
532
- if (typeof value !== 'string') {
533
- fail(name, 'a string', opts);
534
- }
535
- },
536
-
537
- /**
538
- * Assert that `value` is a non-empty string.
539
- * @param {unknown} value
540
- * @param {string} name
541
- * @param {AssertOptions} [opts]
542
- */
543
- assertNonEmptyString(value, name, opts) {
544
- if (typeof value !== 'string' || value.length === 0) {
545
- fail(name, 'a non-empty string', opts);
546
- }
547
- },
548
-
549
- /**
550
- * Assert that `value` is a boolean.
551
- * @param {unknown} value
552
- * @param {string} name
553
- * @param {AssertOptions} [opts]
554
- */
555
- assertBoolean(value, name, opts) {
556
- if (typeof value !== 'boolean') {
557
- fail(name, 'a boolean', opts);
558
- }
559
- },
560
-
561
- /**
562
- * Assert that `value` is a function.
563
- * @param {unknown} value
564
- * @param {string} name
565
- * @param {AssertOptions} [opts]
566
- */
567
- assertFunction(value, name, opts) {
568
- if (typeof value !== 'function') {
569
- fail(name, 'a function', opts);
570
- }
571
- },
572
-
573
- assertObject,
574
-
575
- /**
576
- * Assert that `value` is either `undefined` or a plain object —
577
- * the "optional options object" pattern.
578
- * @param {unknown} value
579
- * @param {string} name
580
- * @param {AssertOptions} [opts]
581
- */
582
- assertOptionalObject(value, name, opts) {
583
- if (value === undefined) {
584
- return;
585
- }
586
- assertObject(value, name, opts);
587
- },
588
-
589
- /**
590
- * Assert that `value` is a byte buffer (`Buffer` or `Uint8Array`) —
591
- * strings are NOT accepted. For already-encoded material where a
592
- * string would be ambiguous (ciphertext, signatures, raw key bytes).
593
- * @param {unknown} value
594
- * @param {string} name
595
- * @param {AssertOptions} [opts]
596
- */
597
- assertBytes(value, name, opts) {
598
- if (!Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
599
- fail(name, 'a Buffer or Uint8Array', opts);
600
- }
601
- },
602
-
603
- /**
604
- * Assert that `value` is either a string or a byte buffer (`Buffer`
605
- * or `Uint8Array`).
606
- * @param {unknown} value
607
- * @param {string} name
608
- * @param {AssertOptions} [opts]
609
- */
610
- assertBytesOrString(value, name, opts) {
611
- if (typeof value !== 'string' && !Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
612
- fail(name, 'a string or Buffer', opts);
613
- }
614
- },
615
-
616
- /**
617
- * Assert that `encoding` is one of the accepted output/input
618
- * encodings. Pass `allowBuffer: false` where a `Buffer` output makes
619
- * no sense (verifying a string signature, decoding a token payload).
620
- *
621
- * @param {unknown} encoding
622
- * @param {string} name
623
- * @param {AssertOptions & { allowBuffer?: boolean }} [opts]
624
- */
625
- assertEncoding(encoding, name, opts) {
626
- const allowBuffer = opts?.allowBuffer !== false;
627
- const valid = (typeof encoding === 'string' && ENCODINGS.has(encoding)) || (allowBuffer && encoding === 'buffer');
628
- if (!valid) {
629
- const list = allowBuffer ? "'hex', 'base64', 'base64url', or 'buffer'" : "'hex', 'base64', or 'base64url'";
630
- fail(name, list, opts);
631
- }
632
- },
633
- };
634
- }
635
407
 
636
408
  /**
637
- * One-line sugar over {@link bindAsserts} — the overwhelmingly common
638
- * pattern where a package binds every argument-shape failure to its
639
- * own error class + `INVALID_ARGUMENT` code. Emits the standard
640
- * `new ErrorClass(code, message, { cause })` shape, so the underlying
641
- * `BaseError` `{ cause, status, details }` options object is respected.
642
- *
643
- * // packages/<pkg>/src/internal/guards.js
644
- * import { defineGuards } from '@exortek/shared/asserts';
645
- * import { PasswordError, ErrorCode } from '../errors.js';
409
+ * Bind every guard this package calls to ApiKeyError, so an argument-shape
410
+ * failure anywhere carries one class and one code to branch on.
646
411
  *
647
- * export const { assertString, invalidArgument, parse } =
648
- * defineGuards(PasswordError, ErrorCode.INVALID_ARGUMENT);
649
- *
650
- * Reach for the raw {@link bindAsserts} when you need a different
651
- * mapping (an alt code, a decorated message) — it stays available.
652
- *
653
- * @param {new (code: string, message: string, options?: { cause?: unknown }) => Error} ErrorClass
654
- * @param {string} code
655
- */
656
- function defineGuards(ErrorClass, code) {
657
- return bindAsserts((message, extra) => new ErrorClass(code, message, extra));
658
- }
659
-
660
- /**
661
- * Argument guards bound to `ApiKeyError` — the package-wide binding of
662
- * `@exortek/shared/asserts`. Import guards from here, never from the
663
- * shared module directly.
412
+ * @type {import('@exortek/shared/asserts').WrapFn}
664
413
  */
414
+ const wrap = (message, extra) => new ApiKeyError(ErrorCode.INVALID_ARGUMENT, message, extra);
665
415
 
666
-
667
- const { invalidArgument} = defineGuards(
668
- ApiKeyError,
669
- ErrorCode.INVALID_ARGUMENT,
670
- );
416
+ const invalidArgument = makeInvalidArgument(wrap);
671
417
 
672
418
  /**
673
419
  * Scope match helpers.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,131 @@
1
+ /**
2
+ * Parse a raw key without verifying it. Returns the segments, or
3
+ * `null` when the shape is wrong. Never throws.
4
+ *
5
+ * Callers can use `parseApiKey(key).prefix` to route by key family
6
+ * in a middleware before hitting the store — but the parse result
7
+ * MUST NOT be trusted as authenticated. Only `verifyApiKey` proves
8
+ * the holder possesses the real secret.
9
+ *
10
+ * @param {string} key
11
+ * @returns {{ prefix: string, id: string, secret: string } | null}
12
+ */
13
+ declare function parseApiKey(key: string): {
14
+ prefix: string;
15
+ id: string;
16
+ secret: string;
17
+ } | null;
18
+ /**
19
+ * Log-safe display for a key. Returns `<prefix>_<first-N-of-id>…<last-M-of-secret>`
20
+ * so the value is recognisable in an audit log without disclosing the
21
+ * secret. The default masks all but the first 6 chars of the id and
22
+ * the last 4 chars of the secret.
23
+ *
24
+ * @param {string} key
25
+ * @returns {string}
26
+ */
27
+ declare function mask(key: string): string;
28
+
29
+ /**
30
+ * Shared base error class — the single error structure behind every
31
+ * `@exortek/*` package's `errors.js`.
32
+ *
33
+ * Every package keeps its own class identity with a one-liner subclass;
34
+ * codes stay per-package frozen maps, status mapping is declared as a
35
+ * static field:
36
+ *
37
+ * import { BaseError } from '@exortek/shared/errors';
38
+ *
39
+ * export const ErrorCode = Object.freeze({
40
+ * INVALID_ARGUMENT: 'INVALID_ARGUMENT',
41
+ * INVALID_TOKEN: 'INVALID_TOKEN',
42
+ * });
43
+ *
44
+ * export class JwtError extends BaseError {
45
+ * static statuses = { INVALID_ARGUMENT: 400, INVALID_TOKEN: 401 };
46
+ * static defaultStatus = 500;
47
+ * }
48
+ *
49
+ * Instances carry a stable machine-readable `code` (branch on this,
50
+ * never on the message), an optional HTTP `status`, an optional
51
+ * `details` object, and the standard `cause` chain.
52
+ */
53
+ declare class BaseError extends Error {
54
+ /**
55
+ * Optional `code → HTTP status` map declared on the subclass. When
56
+ * absent the instance carries no `status` at all — for HTTP-agnostic
57
+ * packages like `@exortek/crypto`.
58
+ *
59
+ * @type {Record<string, number> | undefined}
60
+ */
61
+ static statuses: Record<string, number> | undefined;
62
+ /**
63
+ * Fallback status for codes missing from `statuses`.
64
+ *
65
+ * @type {number}
66
+ */
67
+ static defaultStatus: number;
68
+ /**
69
+ * @param {string} code Stable machine-readable code; branch on this.
70
+ * @param {string} message Human-readable diagnostic. Free-form; may
71
+ * change across versions.
72
+ * @param {{ cause?: unknown, status?: number, details?: Record<string, unknown> }} [options]
73
+ */
74
+ constructor(code: string, message: string, options?: {
75
+ cause?: unknown;
76
+ status?: number;
77
+ details?: Record<string, unknown>;
78
+ });
79
+ /** @type {string} */
80
+ code: string;
81
+ /** @type {number | undefined} */
82
+ status: number | undefined;
83
+ /** @type {Record<string, unknown> | undefined} */
84
+ details: Record<string, unknown> | undefined;
85
+ }
86
+
87
+ declare const ErrorCode: Readonly<{
88
+ INVALID_ARGUMENT: "INVALID_ARGUMENT";
89
+ INVALID_PREFIX: "INVALID_PREFIX";
90
+ INVALID_PEPPER: "INVALID_PEPPER";
91
+ STORE_ERROR: "STORE_ERROR";
92
+ }>;
93
+ /**
94
+ * Every recoverable failure raised by this package. Carries a stable
95
+ * `code` (from {@link ErrorCode}) and a `status` — the HTTP response
96
+ * status a middleware layer would use when translating the error.
97
+ */
98
+ declare class ApiKeyError extends BaseError {
99
+ static statuses: {
100
+ INVALID_ARGUMENT: number;
101
+ INVALID_PREFIX: number;
102
+ INVALID_PEPPER: number;
103
+ STORE_ERROR: number;
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Does `granted` cover a single `required` scope?
109
+ * @param {string[]} granted
110
+ * @param {string} required
111
+ * @returns {boolean}
112
+ */
113
+ declare function covers(granted: string[], required: string): boolean;
114
+ /**
115
+ * All required scopes must be covered by granted.
116
+ * @param {string[]} granted
117
+ * @param {string[]} required
118
+ * @returns {boolean}
119
+ */
120
+ declare function hasAll(granted: string[], required: string[]): boolean;
121
+ /**
122
+ * At least one required scope must be covered.
123
+ * @param {string[]} granted
124
+ * @param {string[]} required
125
+ * @returns {boolean}
126
+ */
127
+ declare function hasAny(granted: string[], required: string[]): boolean;
128
+
1
129
  /**
2
130
  * @typedef {object} ApiKeyRecord
3
131
  * @property {string} id Plaintext lookup key (128-bit b64u).
@@ -80,7 +208,7 @@
80
208
  * @param {CreateApiKeyOptions} options
81
209
  * @returns {Promise<CreateApiKeyResult>}
82
210
  */
83
- export function createApiKey(options: CreateApiKeyOptions): Promise<CreateApiKeyResult>;
211
+ declare function createApiKey(options: CreateApiKeyOptions): Promise<CreateApiKeyResult>;
84
212
  /**
85
213
  * Verify a raw API key. Returns `{ valid: true, ... }` on success, or
86
214
  * `{ valid: false, reason }` on any expected failure. Never throws on
@@ -96,7 +224,7 @@ export function createApiKey(options: CreateApiKeyOptions): Promise<CreateApiKey
96
224
  * @param {VerifyApiKeyOptions} options
97
225
  * @returns {Promise<VerifyApiKeyResult>}
98
226
  */
99
- export function verifyApiKey(rawKey: string, options: VerifyApiKeyOptions): Promise<VerifyApiKeyResult>;
227
+ declare function verifyApiKey(rawKey: string, options: VerifyApiKeyOptions): Promise<VerifyApiKeyResult>;
100
228
  /**
101
229
  * Revoke an API key by id or by the raw wire key. Returns `true` if
102
230
  * a record was actually revoked, `false` if the id was unknown or
@@ -106,7 +234,7 @@ export function verifyApiKey(rawKey: string, options: VerifyApiKeyOptions): Prom
106
234
  * @param {{ store: ApiKeyStore, reason?: string }} options
107
235
  * @returns {Promise<boolean>}
108
236
  */
109
- export function revokeApiKey(keyOrId: string, options: {
237
+ declare function revokeApiKey(keyOrId: string, options: {
110
238
  store: ApiKeyStore;
111
239
  reason?: string;
112
240
  }): Promise<boolean>;
@@ -119,7 +247,7 @@ export function revokeApiKey(keyOrId: string, options: {
119
247
  * @param {{ store: ApiKeyStore, reason?: string }} options
120
248
  * @returns {Promise<number>}
121
249
  */
122
- export function revokeAllForUser(userId: string, options: {
250
+ declare function revokeAllForUser(userId: string, options: {
123
251
  store: ApiKeyStore;
124
252
  reason?: string;
125
253
  }): Promise<number>;
@@ -133,7 +261,7 @@ export function revokeAllForUser(userId: string, options: {
133
261
  * @param {{ store: ApiKeyStore }} options
134
262
  * @returns {Promise<ApiKeyRecord[]>}
135
263
  */
136
- export function listApiKeys(userId: string, options: {
264
+ declare function listApiKeys(userId: string, options: {
137
265
  store: ApiKeyStore;
138
266
  }): Promise<ApiKeyRecord[]>;
139
267
  /**
@@ -146,12 +274,12 @@ export function listApiKeys(userId: string, options: {
146
274
  * @param {{ store: ApiKeyStore, peppers: (Buffer | Uint8Array | string)[] }} options
147
275
  * @returns {Promise<boolean>}
148
276
  */
149
- export function rehashApiKey(rawKey: string, options: {
277
+ declare function rehashApiKey(rawKey: string, options: {
150
278
  store: ApiKeyStore;
151
279
  peppers: (Buffer | Uint8Array | string)[];
152
280
  }): Promise<boolean>;
153
- export { maskKey as maskApiKey };
154
- export type ApiKeyRecord = {
281
+
282
+ type ApiKeyRecord = {
155
283
  /**
156
284
  * Plaintext lookup key (128-bit b64u).
157
285
  */
@@ -197,7 +325,7 @@ export type ApiKeyRecord = {
197
325
  */
198
326
  pepperVersion?: number | undefined;
199
327
  };
200
- export type ApiKeyStore = {
328
+ type ApiKeyStore = {
201
329
  put: (record: ApiKeyRecord) => Promise<void>;
202
330
  getById: (id: string) => Promise<ApiKeyRecord | null>;
203
331
  update: (id: string, patch: Partial<ApiKeyRecord>) => Promise<ApiKeyRecord | null>;
@@ -205,7 +333,7 @@ export type ApiKeyStore = {
205
333
  revokeAllForUser: (userId: string, reason?: string) => Promise<number>;
206
334
  listByUser: (userId: string) => Promise<ApiKeyRecord[]>;
207
335
  };
208
- export type CreateApiKeyOptions = {
336
+ type CreateApiKeyOptions = {
209
337
  /**
210
338
  * Stripe-style; `sk_live`, `pk_test`, `svc_prod_v2`.
211
339
  */
@@ -229,7 +357,7 @@ export type CreateApiKeyOptions = {
229
357
  */
230
358
  now?: number | undefined;
231
359
  };
232
- export type CreateApiKeyResult = {
360
+ type CreateApiKeyResult = {
233
361
  /**
234
362
  * Wire token — show ONCE to the caller and never again.
235
363
  */
@@ -243,7 +371,7 @@ export type CreateApiKeyResult = {
243
371
  */
244
372
  record: ApiKeyRecord;
245
373
  };
246
- export type VerifyApiKeyOptions = {
374
+ type VerifyApiKeyOptions = {
247
375
  store: ApiKeyStore;
248
376
  peppers?: any[] | undefined;
249
377
  requiredScopes?: string[] | undefined;
@@ -257,8 +385,8 @@ export type VerifyApiKeyOptions = {
257
385
  updateLastUsed?: boolean | undefined;
258
386
  now?: number | undefined;
259
387
  };
260
- export type VerifyApiKeyFailureReason = "malformed" | "not_found" | "expired" | "revoked" | "bad_secret" | "prefix_mismatch" | "missing_scope" | "store_unavailable";
261
- export type VerifyApiKeyResult = {
388
+ type VerifyApiKeyFailureReason = "malformed" | "not_found" | "expired" | "revoked" | "bad_secret" | "prefix_mismatch" | "missing_scope" | "store_unavailable";
389
+ type VerifyApiKeyResult = {
262
390
  valid: true;
263
391
  id: string;
264
392
  userId: string;
@@ -272,7 +400,6 @@ export type VerifyApiKeyResult = {
272
400
  valid: false;
273
401
  reason: VerifyApiKeyFailureReason;
274
402
  };
275
- import { mask as maskKey } from './token.js';
276
- export { ApiKeyError, ErrorCode } from "./errors.js";
277
- export { covers, hasAll, hasAny } from "./scopes.js";
278
- export { parseApiKey, mask } from "./token.js";
403
+
404
+ export { ApiKeyError, ErrorCode, covers, createApiKey, hasAll, hasAny, listApiKeys, mask, mask as maskApiKey, parseApiKey, rehashApiKey, revokeAllForUser, revokeApiKey, verifyApiKey };
405
+ export type { ApiKeyRecord, ApiKeyStore, CreateApiKeyOptions, CreateApiKeyResult, VerifyApiKeyFailureReason, VerifyApiKeyOptions, VerifyApiKeyResult };