@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.
@@ -198,20 +198,33 @@ function fastifyPlugin(fn, options) {
198
198
  * `@exortek/shared/validate`: **schema** for whole options objects,
199
199
  * **asserts** for one-liner argument guards at the call site.
200
200
  *
201
- * The consumer surface is {@link defineGuards} (or the raw
202
- * {@link bindAsserts} for full control). Each package binds the assert
203
- * set to its own typed error class once (in `internal/guards.js`), so
204
- * every argument failure throws that package's class —
205
- * `err instanceof CryptoError` holds and users see at a glance which
206
- * package raised the error. The bound `parse` bridges
207
- * `@exortek/shared/validate` schemas to the same error class.
201
+ * Every guard is exported as a `make…` **factory** taking the binding
202
+ * package's `wrap` and returning the guard itself. A package binds the
203
+ * guards it calls once, in its `internal/guards.js`, so every argument
204
+ * failure throws that package's error class — `err instanceof
205
+ * CryptoError` holds, and users see at a glance which package raised it.
206
+ *
207
+ * // packages/<pkg>/src/internal/guards.js
208
+ * import { makeAssertString, makeInvalidArgument } from '@exortek/shared/asserts';
209
+ * import { CryptoError, ErrorCode } from '../errors.js';
210
+ *
211
+ * const wrap = (message, extra) => new CryptoError(ErrorCode.INVALID_ARGUMENT, message, extra);
212
+ *
213
+ * export const assertString = makeAssertString(wrap);
214
+ * export const invalidArgument = makeInvalidArgument(wrap);
215
+ *
216
+ * Importing each factory by name is what makes the module
217
+ * tree-shakeable. An earlier version handed back one object carrying
218
+ * every guard, so all of them shipped in every package's bundle no
219
+ * matter how few were used — `assertBoolean` reached fifteen published
220
+ * packages without a single call site. Import what you call.
208
221
  *
209
222
  * **Path naming convention** (used as the `name` argument):
210
223
  *
211
224
  * `<publicFunction>[.options|.config][.<field>]`
212
225
  *
213
- * - `assertString(name, 'createUser.name')` — top-level arg
214
- * - `assertPositiveInt(n, 'scrypt.options.r')` — nested option
226
+ * - `assertString(name, 'createUser.name')` — top-level arg
227
+ * - `assertPositiveInt(n, 'scrypt.options.r')` — nested option
215
228
  * - `assertBytesOrString(pwd, 'pepper.wrap.password')` — method arg
216
229
  *
217
230
  * Keep the path short: the emitted error is `"<name> must be <desc>"`,
@@ -220,289 +233,17 @@ function fastifyPlugin(fn, options) {
220
233
  * encryptSymmetric()"), not for restating the field.
221
234
  */
222
235
 
223
- /**
224
- * @typedef {{ cause?: unknown }} WrapExtra
225
- * Extra options forwarded to the wrap function, so `invalidArgument`
226
- * sites that catch an underlying error can preserve the `cause` chain
227
- * without falling back to a raw `new PackageError(...)`.
228
- *
229
- * @typedef {(message: string, extra?: WrapExtra) => Error} WrapFn
230
- * Constructs (never throws) the binding package's error, e.g.
231
- * `(m, { cause } = {}) => new CryptoError(ErrorCode.INVALID_ARGUMENT, m, { cause })`.
232
- *
233
- * @typedef {{ hint?: string }} AssertOptions
234
- * `hint` is appended to the message after an em-dash — use it for
235
- * actionable guidance ("pass the exact bytes returned by …").
236
- *
237
- * @typedef {{ safeParse: (input: unknown, path?: string) => { ok: true, value: unknown } | { ok: false, errors: string[] } }} ParseableSchema
238
- */
239
-
240
- /**
241
- * Build the failure message. `description` completes the sentence
242
- * `"<name> must be <description>"`; `hint` (optional) follows an
243
- * em-dash.
244
- *
245
- * @param {string} name
246
- * @param {string} description
247
- * @param {string} [hint]
248
- * @returns {string}
249
- */
250
- function message(name, description, hint) {
251
- return `${name} must be ${description}${hint ? ` — ${hint}` : ''}`;
252
- }
253
-
254
- const ENCODINGS = new Set(['hex', 'base64', 'base64url']);
255
-
256
- /**
257
- * Bind the assert set to a package's error class. Call once per
258
- * package (conventionally in `internal/guards.js`) and import the
259
- * bound object everywhere:
260
- *
261
- * import { bindAsserts } from '@exortek/shared/asserts';
262
- * import { CryptoError, ErrorCode } from '../errors.js';
263
- * export const g = bindAsserts((m) => new CryptoError(ErrorCode.INVALID_ARGUMENT, m));
264
- *
265
- * // call sites:
266
- * g.assertPositiveInt(options.iterations, 'options.iterations');
267
- * const cfg = g.parse(OptionsSchema, options, 'options');
268
- *
269
- * @param {WrapFn} wrap
270
- */
271
- function bindAsserts(wrap) {
272
- /**
273
- * @param {string} name
274
- * @param {string} description
275
- * @param {AssertOptions} [opts]
276
- * @returns {never}
277
- */
278
- function fail(name, description, opts) {
279
- throw wrap(message(name, description, opts?.hint));
280
- }
281
-
282
- /**
283
- * Assert that `value` is a plain object (not `null`, not an array,
284
- * not a primitive).
285
- * @param {unknown} value
286
- * @param {string} name
287
- * @param {AssertOptions} [opts]
288
- */
289
- function assertObject(value, name, opts) {
290
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
291
- fail(name, 'an object', opts);
292
- }
293
- }
294
-
295
- return {
296
- /**
297
- * Construct (not throw) the bound error with a free-form message —
298
- * for `throw g.invalidArgument('…')` sites that don't fit the
299
- * `X must be Y` shape (canonicalisation errors, cross-field
300
- * constraint failures). Pass `{ cause }` when the argument-shape
301
- * failure was triggered by an underlying throw whose chain matters
302
- * (e.g. `JSON.stringify` on a bigint).
303
- *
304
- * @param {string} msg
305
- * @param {WrapExtra} [extra]
306
- * @returns {Error}
307
- */
308
- invalidArgument(msg, extra) {
309
- return wrap(msg, extra);
310
- },
311
-
312
- /**
313
- * Validate `input` against a `@exortek/shared/validate` schema;
314
- * failures throw the bound error carrying every collected message.
315
- * The bridge that keeps schema validation on the package's own
316
- * error surface.
317
- *
318
- * @param {ParseableSchema} schema
319
- * @param {unknown} input
320
- * @param {string} [path='options']
321
- * @returns {unknown} the parsed (possibly defaulted) value
322
- */
323
- parse(schema, input, path = 'options') {
324
- const r = schema.safeParse(input, path);
325
- if (!r.ok) {
326
- throw wrap(r.errors.join('; '));
327
- }
328
- return r.value;
329
- },
330
-
331
- /**
332
- * Assert that `value` is a non-negative safe integer (`0, 1, 2, …`).
333
- * @param {unknown} value
334
- * @param {string} name Argument name to include in the error message.
335
- * @param {AssertOptions} [opts]
336
- */
337
- assertNonNegativeInt(value, name, opts) {
338
- if (!Number.isSafeInteger(value) || /** @type {number} */ (value) < 0) {
339
- fail(name, 'a non-negative safe integer', opts);
340
- }
341
- },
342
-
343
- /**
344
- * Assert that `value` is a strictly positive safe integer (`1, 2, 3, …`).
345
- * @param {unknown} value
346
- * @param {string} name
347
- * @param {AssertOptions} [opts]
348
- */
349
- assertPositiveInt(value, name, opts) {
350
- if (!Number.isSafeInteger(value) || /** @type {number} */ (value) <= 0) {
351
- fail(name, 'a positive integer', opts);
352
- }
353
- },
354
-
355
- /**
356
- * Assert that `value` fits in a 48-bit unsigned integer (`0 … 2^48 − 1`).
357
- * Used for Unix millisecond timestamps in UUID v7 / ULID.
358
- * @param {unknown} value
359
- * @param {string} name
360
- * @param {AssertOptions} [opts]
361
- */
362
- assertUint48(value, name, opts) {
363
- if (
364
- !Number.isSafeInteger(value) ||
365
- /** @type {number} */ (value) < 0 ||
366
- /** @type {number} */ (value) > 0xffffffffffff
367
- ) {
368
- fail(name, 'a non-negative safe integer ≤ 2^48 − 1 (Unix ms since epoch)', opts);
369
- }
370
- },
371
-
372
- /**
373
- * Assert that `value` is a string (may be empty).
374
- * @param {unknown} value
375
- * @param {string} name
376
- * @param {AssertOptions} [opts]
377
- */
378
- assertString(value, name, opts) {
379
- if (typeof value !== 'string') {
380
- fail(name, 'a string', opts);
381
- }
382
- },
383
-
384
- /**
385
- * Assert that `value` is a non-empty string.
386
- * @param {unknown} value
387
- * @param {string} name
388
- * @param {AssertOptions} [opts]
389
- */
390
- assertNonEmptyString(value, name, opts) {
391
- if (typeof value !== 'string' || value.length === 0) {
392
- fail(name, 'a non-empty string', opts);
393
- }
394
- },
395
-
396
- /**
397
- * Assert that `value` is a boolean.
398
- * @param {unknown} value
399
- * @param {string} name
400
- * @param {AssertOptions} [opts]
401
- */
402
- assertBoolean(value, name, opts) {
403
- if (typeof value !== 'boolean') {
404
- fail(name, 'a boolean', opts);
405
- }
406
- },
407
-
408
- /**
409
- * Assert that `value` is a function.
410
- * @param {unknown} value
411
- * @param {string} name
412
- * @param {AssertOptions} [opts]
413
- */
414
- assertFunction(value, name, opts) {
415
- if (typeof value !== 'function') {
416
- fail(name, 'a function', opts);
417
- }
418
- },
419
-
420
- assertObject,
421
-
422
- /**
423
- * Assert that `value` is either `undefined` or a plain object —
424
- * the "optional options object" pattern.
425
- * @param {unknown} value
426
- * @param {string} name
427
- * @param {AssertOptions} [opts]
428
- */
429
- assertOptionalObject(value, name, opts) {
430
- if (value === undefined) {
431
- return;
432
- }
433
- assertObject(value, name, opts);
434
- },
435
-
436
- /**
437
- * Assert that `value` is a byte buffer (`Buffer` or `Uint8Array`) —
438
- * strings are NOT accepted. For already-encoded material where a
439
- * string would be ambiguous (ciphertext, signatures, raw key bytes).
440
- * @param {unknown} value
441
- * @param {string} name
442
- * @param {AssertOptions} [opts]
443
- */
444
- assertBytes(value, name, opts) {
445
- if (!Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
446
- fail(name, 'a Buffer or Uint8Array', opts);
447
- }
448
- },
449
-
450
- /**
451
- * Assert that `value` is either a string or a byte buffer (`Buffer`
452
- * or `Uint8Array`).
453
- * @param {unknown} value
454
- * @param {string} name
455
- * @param {AssertOptions} [opts]
456
- */
457
- assertBytesOrString(value, name, opts) {
458
- if (typeof value !== 'string' && !Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
459
- fail(name, 'a string or Buffer', opts);
460
- }
461
- },
462
-
463
- /**
464
- * Assert that `encoding` is one of the accepted output/input
465
- * encodings. Pass `allowBuffer: false` where a `Buffer` output makes
466
- * no sense (verifying a string signature, decoding a token payload).
467
- *
468
- * @param {unknown} encoding
469
- * @param {string} name
470
- * @param {AssertOptions & { allowBuffer?: boolean }} [opts]
471
- */
472
- assertEncoding(encoding, name, opts) {
473
- const allowBuffer = opts?.allowBuffer !== false;
474
- const valid = (typeof encoding === 'string' && ENCODINGS.has(encoding)) || (allowBuffer && encoding === 'buffer');
475
- if (!valid) {
476
- const list = allowBuffer ? "'hex', 'base64', 'base64url', or 'buffer'" : "'hex', 'base64', or 'base64url'";
477
- fail(name, list, opts);
478
- }
479
- },
480
- };
481
- }
482
236
 
483
237
  /**
484
- * One-line sugar over {@link bindAsserts} — the overwhelmingly common
485
- * pattern where a package binds every argument-shape failure to its
486
- * own error class + `INVALID_ARGUMENT` code. Emits the standard
487
- * `new ErrorClass(code, message, { cause })` shape, so the underlying
488
- * `BaseError` `{ cause, status, details }` options object is respected.
489
- *
490
- * // packages/<pkg>/src/internal/guards.js
491
- * import { defineGuards } from '@exortek/shared/asserts';
492
- * import { PasswordError, ErrorCode } from '../errors.js';
493
- *
494
- * export const { assertString, invalidArgument, parse } =
495
- * defineGuards(PasswordError, ErrorCode.INVALID_ARGUMENT);
238
+ * Construct (not throw) the bound error with a free-form message — for
239
+ * `throw invalidArgument('…')` sites that don't fit the `X must be Y`
240
+ * shape (canonicalisation errors, cross-field constraint failures). Pass
241
+ * `{ cause }` when the failure was triggered by an underlying throw whose
242
+ * chain matters (e.g. `JSON.stringify` on a bigint).
496
243
  *
497
- * Reach for the raw {@link bindAsserts} when you need a different
498
- * mapping (an alt code, a decorated message) — it stays available.
499
- *
500
- * @param {new (code: string, message: string, options?: { cause?: unknown }) => Error} ErrorClass
501
- * @param {string} code
244
+ * @type {(wrap: WrapFn) => (msg: string, extra?: WrapExtra) => Error}
502
245
  */
503
- function defineGuards(ErrorClass, code) {
504
- return bindAsserts((message, extra) => new ErrorClass(code, message, extra));
505
- }
246
+ const makeInvalidArgument = wrap => (msg, extra) => wrap(msg, extra);
506
247
 
507
248
  /**
508
249
  * Shared base error class — the single error structure behind every
@@ -613,10 +354,15 @@ class ApiKeyError extends BaseError {
613
354
  */
614
355
 
615
356
 
616
- const { invalidArgument} = defineGuards(
617
- ApiKeyError,
618
- ErrorCode.INVALID_ARGUMENT,
619
- );
357
+ /**
358
+ * Bind every guard this package calls to ApiKeyError, so an argument-shape
359
+ * failure anywhere carries one class and one code to branch on.
360
+ *
361
+ * @type {import('@exortek/shared/asserts').WrapFn}
362
+ */
363
+ const wrap = (message, extra) => new ApiKeyError(ErrorCode.INVALID_ARGUMENT, message, extra);
364
+
365
+ const invalidArgument = makeInvalidArgument(wrap);
620
366
 
621
367
  /**
622
368
  * Scope match helpers.
@@ -1 +1,3 @@
1
- export const apiKeyPlugin: Function;
1
+ declare const apiKeyPlugin: Function;
2
+
3
+ export { apiKeyPlugin };