@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/dist/stores.mjs CHANGED
@@ -122,11 +122,42 @@ function assertRedisClient(client, methods, wrap) {
122
122
  * can share a single detection path instead of copy-pasting the same
123
123
  * three-way dispatch.
124
124
  *
125
- * Internal to `@exortek/shared` — consumed by `incr-store.js` and
126
- * `record-store.js`, not re-exported to consuming packages.
125
+ * Consumed by `incr-store.js` and `record-store.js` here, and imported
126
+ * directly by the `oauth2`, `opaque` and `session` stores — so treat this
127
+ * module's surface as shared, not private to `@exortek/shared`.
127
128
  */
128
129
 
129
130
 
131
+ /**
132
+ * Which calling convention a client expects for the commands whose
133
+ * signatures differ between drivers — Lua (`eval`) and `SET` with a TTL.
134
+ *
135
+ * Most helpers here can dispatch on a method name (`mget` vs `mGet`), but
136
+ * these two share a name and differ only in how arguments are passed, so
137
+ * they need an explicit answer.
138
+ *
139
+ * A real `ioredis` instance reports `constructor.name === 'EventEmitter'`
140
+ * — it extends EventEmitter and no class name survives to the instance —
141
+ * so a constructor-name probe alone never matches it. Detect on the API
142
+ * surface instead (`scanStream` / the `status` string), neither of which
143
+ * node-redis has, and keep the name check as a fallback for wrapped
144
+ * clients. Anything unrecognised is treated as node-redis, whose
145
+ * options-object form is also what `@upstash/redis` accepts.
146
+ *
147
+ * @param {any} client
148
+ * @returns {'ioredis' | 'node-redis'}
149
+ */
150
+ function detectDialect(client) {
151
+ if (client && (isFunction(client.scanStream) || isString(client.status))) {
152
+ return 'ioredis';
153
+ }
154
+ const name = (client && client.constructor && client.constructor.name) || '';
155
+ if (name === 'Redis' || name === 'Cluster') {
156
+ return 'ioredis';
157
+ }
158
+ return 'node-redis';
159
+ }
160
+
130
161
  /**
131
162
  * @param {object} client A Redis-compatible client instance.
132
163
  * @returns {{
@@ -140,7 +171,11 @@ function assertRedisClient(client, methods, wrap) {
140
171
  * }}
141
172
  */
142
173
  function createRedisHelpers(client) {
174
+ const dialect = detectDialect(client);
175
+
143
176
  return {
177
+ dialect,
178
+
144
179
  async mget(keys) {
145
180
  if (keys.length === 0) {
146
181
  return [];
@@ -186,9 +221,13 @@ function createRedisHelpers(client) {
186
221
 
187
222
  async setWithTTL(key, value, ttlMs) {
188
223
  const px = Math.max(1, Math.ceil(ttlMs));
189
- try {
224
+ // Must dispatch on the dialect, not on a try/catch: node-redis accepts
225
+ // the ioredis positional form without complaint and simply stores the
226
+ // key with no expiry, so a catch-based fallback never fires and the TTL
227
+ // is silently dropped.
228
+ if (dialect === 'ioredis') {
190
229
  await client.set(key, value, 'PX', px);
191
- } catch {
230
+ } else {
192
231
  await client.set(key, value, { PX: px });
193
232
  }
194
233
  },
@@ -588,20 +627,33 @@ function memoryStore() {
588
627
  * `@exortek/shared/validate`: **schema** for whole options objects,
589
628
  * **asserts** for one-liner argument guards at the call site.
590
629
  *
591
- * The consumer surface is {@link defineGuards} (or the raw
592
- * {@link bindAsserts} for full control). Each package binds the assert
593
- * set to its own typed error class once (in `internal/guards.js`), so
594
- * every argument failure throws that package's class —
595
- * `err instanceof CryptoError` holds and users see at a glance which
596
- * package raised the error. The bound `parse` bridges
597
- * `@exortek/shared/validate` schemas to the same error class.
630
+ * Every guard is exported as a `make…` **factory** taking the binding
631
+ * package's `wrap` and returning the guard itself. A package binds the
632
+ * guards it calls once, in its `internal/guards.js`, so every argument
633
+ * failure throws that package's error class — `err instanceof
634
+ * CryptoError` holds, and users see at a glance which package raised it.
635
+ *
636
+ * // packages/<pkg>/src/internal/guards.js
637
+ * import { makeAssertString, makeInvalidArgument } from '@exortek/shared/asserts';
638
+ * import { CryptoError, ErrorCode } from '../errors.js';
639
+ *
640
+ * const wrap = (message, extra) => new CryptoError(ErrorCode.INVALID_ARGUMENT, message, extra);
641
+ *
642
+ * export const assertString = makeAssertString(wrap);
643
+ * export const invalidArgument = makeInvalidArgument(wrap);
644
+ *
645
+ * Importing each factory by name is what makes the module
646
+ * tree-shakeable. An earlier version handed back one object carrying
647
+ * every guard, so all of them shipped in every package's bundle no
648
+ * matter how few were used — `assertBoolean` reached fifteen published
649
+ * packages without a single call site. Import what you call.
598
650
  *
599
651
  * **Path naming convention** (used as the `name` argument):
600
652
  *
601
653
  * `<publicFunction>[.options|.config][.<field>]`
602
654
  *
603
- * - `assertString(name, 'createUser.name')` — top-level arg
604
- * - `assertPositiveInt(n, 'scrypt.options.r')` — nested option
655
+ * - `assertString(name, 'createUser.name')` — top-level arg
656
+ * - `assertPositiveInt(n, 'scrypt.options.r')` — nested option
605
657
  * - `assertBytesOrString(pwd, 'pepper.wrap.password')` — method arg
606
658
  *
607
659
  * Keep the path short: the emitted error is `"<name> must be <desc>"`,
@@ -610,289 +662,17 @@ function memoryStore() {
610
662
  * encryptSymmetric()"), not for restating the field.
611
663
  */
612
664
 
613
- /**
614
- * @typedef {{ cause?: unknown }} WrapExtra
615
- * Extra options forwarded to the wrap function, so `invalidArgument`
616
- * sites that catch an underlying error can preserve the `cause` chain
617
- * without falling back to a raw `new PackageError(...)`.
618
- *
619
- * @typedef {(message: string, extra?: WrapExtra) => Error} WrapFn
620
- * Constructs (never throws) the binding package's error, e.g.
621
- * `(m, { cause } = {}) => new CryptoError(ErrorCode.INVALID_ARGUMENT, m, { cause })`.
622
- *
623
- * @typedef {{ hint?: string }} AssertOptions
624
- * `hint` is appended to the message after an em-dash — use it for
625
- * actionable guidance ("pass the exact bytes returned by …").
626
- *
627
- * @typedef {{ safeParse: (input: unknown, path?: string) => { ok: true, value: unknown } | { ok: false, errors: string[] } }} ParseableSchema
628
- */
629
-
630
- /**
631
- * Build the failure message. `description` completes the sentence
632
- * `"<name> must be <description>"`; `hint` (optional) follows an
633
- * em-dash.
634
- *
635
- * @param {string} name
636
- * @param {string} description
637
- * @param {string} [hint]
638
- * @returns {string}
639
- */
640
- function message(name, description, hint) {
641
- return `${name} must be ${description}${hint ? ` — ${hint}` : ''}`;
642
- }
643
-
644
- const ENCODINGS = new Set(['hex', 'base64', 'base64url']);
645
-
646
- /**
647
- * Bind the assert set to a package's error class. Call once per
648
- * package (conventionally in `internal/guards.js`) and import the
649
- * bound object everywhere:
650
- *
651
- * import { bindAsserts } from '@exortek/shared/asserts';
652
- * import { CryptoError, ErrorCode } from '../errors.js';
653
- * export const g = bindAsserts((m) => new CryptoError(ErrorCode.INVALID_ARGUMENT, m));
654
- *
655
- * // call sites:
656
- * g.assertPositiveInt(options.iterations, 'options.iterations');
657
- * const cfg = g.parse(OptionsSchema, options, 'options');
658
- *
659
- * @param {WrapFn} wrap
660
- */
661
- function bindAsserts(wrap) {
662
- /**
663
- * @param {string} name
664
- * @param {string} description
665
- * @param {AssertOptions} [opts]
666
- * @returns {never}
667
- */
668
- function fail(name, description, opts) {
669
- throw wrap(message(name, description, opts?.hint));
670
- }
671
-
672
- /**
673
- * Assert that `value` is a plain object (not `null`, not an array,
674
- * not a primitive).
675
- * @param {unknown} value
676
- * @param {string} name
677
- * @param {AssertOptions} [opts]
678
- */
679
- function assertObject(value, name, opts) {
680
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
681
- fail(name, 'an object', opts);
682
- }
683
- }
684
-
685
- return {
686
- /**
687
- * Construct (not throw) the bound error with a free-form message —
688
- * for `throw g.invalidArgument('…')` sites that don't fit the
689
- * `X must be Y` shape (canonicalisation errors, cross-field
690
- * constraint failures). Pass `{ cause }` when the argument-shape
691
- * failure was triggered by an underlying throw whose chain matters
692
- * (e.g. `JSON.stringify` on a bigint).
693
- *
694
- * @param {string} msg
695
- * @param {WrapExtra} [extra]
696
- * @returns {Error}
697
- */
698
- invalidArgument(msg, extra) {
699
- return wrap(msg, extra);
700
- },
701
-
702
- /**
703
- * Validate `input` against a `@exortek/shared/validate` schema;
704
- * failures throw the bound error carrying every collected message.
705
- * The bridge that keeps schema validation on the package's own
706
- * error surface.
707
- *
708
- * @param {ParseableSchema} schema
709
- * @param {unknown} input
710
- * @param {string} [path='options']
711
- * @returns {unknown} the parsed (possibly defaulted) value
712
- */
713
- parse(schema, input, path = 'options') {
714
- const r = schema.safeParse(input, path);
715
- if (!r.ok) {
716
- throw wrap(r.errors.join('; '));
717
- }
718
- return r.value;
719
- },
720
-
721
- /**
722
- * Assert that `value` is a non-negative safe integer (`0, 1, 2, …`).
723
- * @param {unknown} value
724
- * @param {string} name Argument name to include in the error message.
725
- * @param {AssertOptions} [opts]
726
- */
727
- assertNonNegativeInt(value, name, opts) {
728
- if (!Number.isSafeInteger(value) || /** @type {number} */ (value) < 0) {
729
- fail(name, 'a non-negative safe integer', opts);
730
- }
731
- },
732
-
733
- /**
734
- * Assert that `value` is a strictly positive safe integer (`1, 2, 3, …`).
735
- * @param {unknown} value
736
- * @param {string} name
737
- * @param {AssertOptions} [opts]
738
- */
739
- assertPositiveInt(value, name, opts) {
740
- if (!Number.isSafeInteger(value) || /** @type {number} */ (value) <= 0) {
741
- fail(name, 'a positive integer', opts);
742
- }
743
- },
744
-
745
- /**
746
- * Assert that `value` fits in a 48-bit unsigned integer (`0 … 2^48 − 1`).
747
- * Used for Unix millisecond timestamps in UUID v7 / ULID.
748
- * @param {unknown} value
749
- * @param {string} name
750
- * @param {AssertOptions} [opts]
751
- */
752
- assertUint48(value, name, opts) {
753
- if (
754
- !Number.isSafeInteger(value) ||
755
- /** @type {number} */ (value) < 0 ||
756
- /** @type {number} */ (value) > 0xffffffffffff
757
- ) {
758
- fail(name, 'a non-negative safe integer ≤ 2^48 − 1 (Unix ms since epoch)', opts);
759
- }
760
- },
761
-
762
- /**
763
- * Assert that `value` is a string (may be empty).
764
- * @param {unknown} value
765
- * @param {string} name
766
- * @param {AssertOptions} [opts]
767
- */
768
- assertString(value, name, opts) {
769
- if (typeof value !== 'string') {
770
- fail(name, 'a string', opts);
771
- }
772
- },
773
-
774
- /**
775
- * Assert that `value` is a non-empty string.
776
- * @param {unknown} value
777
- * @param {string} name
778
- * @param {AssertOptions} [opts]
779
- */
780
- assertNonEmptyString(value, name, opts) {
781
- if (typeof value !== 'string' || value.length === 0) {
782
- fail(name, 'a non-empty string', opts);
783
- }
784
- },
785
-
786
- /**
787
- * Assert that `value` is a boolean.
788
- * @param {unknown} value
789
- * @param {string} name
790
- * @param {AssertOptions} [opts]
791
- */
792
- assertBoolean(value, name, opts) {
793
- if (typeof value !== 'boolean') {
794
- fail(name, 'a boolean', opts);
795
- }
796
- },
797
-
798
- /**
799
- * Assert that `value` is a function.
800
- * @param {unknown} value
801
- * @param {string} name
802
- * @param {AssertOptions} [opts]
803
- */
804
- assertFunction(value, name, opts) {
805
- if (typeof value !== 'function') {
806
- fail(name, 'a function', opts);
807
- }
808
- },
809
-
810
- assertObject,
811
-
812
- /**
813
- * Assert that `value` is either `undefined` or a plain object —
814
- * the "optional options object" pattern.
815
- * @param {unknown} value
816
- * @param {string} name
817
- * @param {AssertOptions} [opts]
818
- */
819
- assertOptionalObject(value, name, opts) {
820
- if (value === undefined) {
821
- return;
822
- }
823
- assertObject(value, name, opts);
824
- },
825
-
826
- /**
827
- * Assert that `value` is a byte buffer (`Buffer` or `Uint8Array`) —
828
- * strings are NOT accepted. For already-encoded material where a
829
- * string would be ambiguous (ciphertext, signatures, raw key bytes).
830
- * @param {unknown} value
831
- * @param {string} name
832
- * @param {AssertOptions} [opts]
833
- */
834
- assertBytes(value, name, opts) {
835
- if (!Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
836
- fail(name, 'a Buffer or Uint8Array', opts);
837
- }
838
- },
839
-
840
- /**
841
- * Assert that `value` is either a string or a byte buffer (`Buffer`
842
- * or `Uint8Array`).
843
- * @param {unknown} value
844
- * @param {string} name
845
- * @param {AssertOptions} [opts]
846
- */
847
- assertBytesOrString(value, name, opts) {
848
- if (typeof value !== 'string' && !Buffer.isBuffer(value) && !(value instanceof Uint8Array)) {
849
- fail(name, 'a string or Buffer', opts);
850
- }
851
- },
852
-
853
- /**
854
- * Assert that `encoding` is one of the accepted output/input
855
- * encodings. Pass `allowBuffer: false` where a `Buffer` output makes
856
- * no sense (verifying a string signature, decoding a token payload).
857
- *
858
- * @param {unknown} encoding
859
- * @param {string} name
860
- * @param {AssertOptions & { allowBuffer?: boolean }} [opts]
861
- */
862
- assertEncoding(encoding, name, opts) {
863
- const allowBuffer = opts?.allowBuffer !== false;
864
- const valid = (typeof encoding === 'string' && ENCODINGS.has(encoding)) || (allowBuffer && encoding === 'buffer');
865
- if (!valid) {
866
- const list = allowBuffer ? "'hex', 'base64', 'base64url', or 'buffer'" : "'hex', 'base64', or 'base64url'";
867
- fail(name, list, opts);
868
- }
869
- },
870
- };
871
- }
872
665
 
873
666
  /**
874
- * One-line sugar over {@link bindAsserts} — the overwhelmingly common
875
- * pattern where a package binds every argument-shape failure to its
876
- * own error class + `INVALID_ARGUMENT` code. Emits the standard
877
- * `new ErrorClass(code, message, { cause })` shape, so the underlying
878
- * `BaseError` `{ cause, status, details }` options object is respected.
879
- *
880
- * // packages/<pkg>/src/internal/guards.js
881
- * import { defineGuards } from '@exortek/shared/asserts';
882
- * import { PasswordError, ErrorCode } from '../errors.js';
883
- *
884
- * export const { assertString, invalidArgument, parse } =
885
- * defineGuards(PasswordError, ErrorCode.INVALID_ARGUMENT);
667
+ * Construct (not throw) the bound error with a free-form message — for
668
+ * `throw invalidArgument('…')` sites that don't fit the `X must be Y`
669
+ * shape (canonicalisation errors, cross-field constraint failures). Pass
670
+ * `{ cause }` when the failure was triggered by an underlying throw whose
671
+ * chain matters (e.g. `JSON.stringify` on a bigint).
886
672
  *
887
- * Reach for the raw {@link bindAsserts} when you need a different
888
- * mapping (an alt code, a decorated message) — it stays available.
889
- *
890
- * @param {new (code: string, message: string, options?: { cause?: unknown }) => Error} ErrorClass
891
- * @param {string} code
673
+ * @type {(wrap: WrapFn) => (msg: string, extra?: WrapExtra) => Error}
892
674
  */
893
- function defineGuards(ErrorClass, code) {
894
- return bindAsserts((message, extra) => new ErrorClass(code, message, extra));
895
- }
675
+ const makeInvalidArgument = wrap => (msg, extra) => wrap(msg, extra);
896
676
 
897
677
  /**
898
678
  * Shared base error class — the single error structure behind every
@@ -1003,10 +783,15 @@ class ApiKeyError extends BaseError {
1003
783
  */
1004
784
 
1005
785
 
1006
- const { invalidArgument} = defineGuards(
1007
- ApiKeyError,
1008
- ErrorCode.INVALID_ARGUMENT,
1009
- );
786
+ /**
787
+ * Bind every guard this package calls to ApiKeyError, so an argument-shape
788
+ * failure anywhere carries one class and one code to branch on.
789
+ *
790
+ * @type {import('@exortek/shared/asserts').WrapFn}
791
+ */
792
+ const wrap = (message, extra) => new ApiKeyError(ErrorCode.INVALID_ARGUMENT, message, extra);
793
+
794
+ const invalidArgument = makeInvalidArgument(wrap);
1010
795
 
1011
796
  /**
1012
797
  * Redis-backed store for API keys.
@@ -1110,37 +895,99 @@ function redisStore(client, options = {}) {
1110
895
  }
1111
896
 
1112
897
  /**
1113
- * Wrap a user-supplied store into the `ApiKeyStore` interface.
1114
- * Validates that the required methods exist so a misconfiguration
1115
- * surfaces at construction time, not on the first `createApiKey` /
1116
- * `verifyApiKey` call. Sync implementations are wrapped transparently
1117
- * — return a plain value or a Promise, either works.
898
+ * Family factory for the `customStore(impl)` wrapper every store-shipping
899
+ * package exposes.
1118
900
  *
1119
- * Required: `put`, `getById`, `update`, `revoke`, `revokeAllForUser`, `listByUser`.
901
+ * Each package used to hand-roll the same three steps: assert `impl` is an
902
+ * object, assert its required methods are functions, then `Promise.resolve()`
903
+ * -wrap every call so a sync implementation is transparently promoted to the
904
+ * async store interface. Only the method-name arrays and the typed error
905
+ * differ. This collapses all of that into one binding per package.
1120
906
  *
1121
- * @param {Partial<import('../index.js').ApiKeyStore>} impl
1122
- * @returns {import('../index.js').ApiKeyStore}
907
+ * Consumers: `apikey`, `challenge`, `magic-link`, `opaque`, `session`.
1123
908
  */
1124
909
 
1125
- const REQUIRED = ['put', 'getById', 'update', 'revoke', 'revokeAllForUser', 'listByUser'];
1126
910
 
1127
- function customStore(impl) {
911
+ /**
912
+ * Assert `impl` is an object exposing every method in `required` as a function.
913
+ * Throws the caller's typed error (via `wrap`) on the first failure. Used on
914
+ * its own by stores that keep a caller's implementation verbatim (jwt / paseto
915
+ * token registries), and internally by {@link createCustomStoreValidator}.
916
+ *
917
+ * @param {unknown} impl
918
+ * @param {readonly string[]} required
919
+ * @param {(message: string) => Error} wrap
920
+ * The package's error factory; its returned error is thrown. A `wrap` that
921
+ * throws directly also works (the outer `throw` is then unreachable).
922
+ * @returns {void}
923
+ */
924
+ function assertStoreShape(impl, required, wrap) {
1128
925
  if (!isObject(impl)) {
1129
- throw invalidArgument(`customStore(impl) requires an object with { ${REQUIRED.join(', ')} } methods`);
926
+ throw wrap(`customStore(impl) requires an object with { ${required.join(', ')} } methods`);
1130
927
  }
1131
- for (const name of REQUIRED) {
1132
- if (!isFunction(impl[name])) {
1133
- throw invalidArgument(`customStore: impl.${name} is required and must be a function`);
928
+ for (const name of required) {
929
+ if (!isFunction(/** @type {any} */ (impl)[name])) {
930
+ throw wrap(`customStore: impl.${name} is required and must be a function`);
1134
931
  }
1135
932
  }
1136
- return {
1137
- put: record => Promise.resolve(impl.put(record)),
1138
- getById: id => Promise.resolve(impl.getById(id)),
1139
- update: (id, patch) => Promise.resolve(impl.update(id, patch)),
1140
- revoke: (id, reason) => Promise.resolve(impl.revoke(id, reason)),
1141
- revokeAllForUser: (userId, reason) => Promise.resolve(impl.revokeAllForUser(userId, reason)),
1142
- listByUser: userId => Promise.resolve(impl.listByUser(userId)),
933
+ }
934
+
935
+ /**
936
+ * @param {object} spec
937
+ * @param {readonly string[]} spec.required
938
+ * Methods that must exist on `impl`; missing ones fail at construction time.
939
+ * @param {readonly string[]} [spec.optional]
940
+ * Methods wrapped through only when present (feature-gated store extras).
941
+ * @param {(message: string) => Error} spec.wrap
942
+ * The package's error factory (its `invalidArgument`) — called with a
943
+ * diagnostic message; its returned typed error is thrown so
944
+ * `err instanceof <Pkg>Error` holds. A `wrap` that throws directly also
945
+ * works (the outer `throw` is then unreachable).
946
+ * @param {Record<string, (result: any) => any>} [spec.coerce]
947
+ * Optional per-method post-processor applied to the resolved result — e.g.
948
+ * opaque coerces `set` to `undefined` so a native `Map.set` return value
949
+ * can't leak out as `Promise<Map>`.
950
+ * @returns {(impl: object) => Record<string, (...args: any[]) => Promise<any>>}
951
+ */
952
+ function createCustomStoreValidator({ required, optional = [], wrap, coerce = {} }) {
953
+ return function customStore(impl) {
954
+ assertStoreShape(impl, required, wrap);
955
+
956
+ /** @type {Record<string, (...args: any[]) => Promise<any>>} */
957
+ const store = {};
958
+ const bind = name => {
959
+ const post = coerce[name];
960
+ store[name] = post
961
+ ? (...args) => Promise.resolve(impl[name](...args)).then(post)
962
+ : (...args) => Promise.resolve(impl[name](...args));
963
+ };
964
+
965
+ for (const name of required) {
966
+ bind(name);
967
+ }
968
+ for (const name of optional) {
969
+ if (isFunction(impl[name])) {
970
+ bind(name);
971
+ }
972
+ }
973
+ return store;
1143
974
  };
1144
975
  }
1145
976
 
977
+ /**
978
+ * Wrap a user-supplied store into the `ApiKeyStore` interface. Validates the
979
+ * required methods at construction time (not on first use) and promotes sync
980
+ * implementations to the async interface.
981
+ *
982
+ * Required: `put`, `getById`, `update`, `revoke`, `revokeAllForUser`, `listByUser`.
983
+ *
984
+ * @param {Partial<import('../index.js').ApiKeyStore>} impl
985
+ * @returns {import('../index.js').ApiKeyStore}
986
+ */
987
+
988
+ const customStore = createCustomStoreValidator({
989
+ required: ['put', 'getById', 'update', 'revoke', 'revokeAllForUser', 'listByUser'],
990
+ wrap: invalidArgument,
991
+ });
992
+
1146
993
  export { customStore, memoryStore, redisStore };
package/dist/token.d.ts CHANGED
@@ -84,5 +84,6 @@ export function candidateHashesFor(rawSecret: string, peppers: Buffer[] | null):
84
84
  * @returns {string}
85
85
  */
86
86
  export function mask(key: string): string;
87
+ export const _nodeTimingSafeEqual: any;
88
+ export const _isBytes: typeof isBytes;
87
89
  import { isBytes } from '@exortek/shared/predicates';
88
- export { nodeTimingSafeEqual as _nodeTimingSafeEqual, isBytes as _isBytes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exortek/apikey",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Stripe-style prefixed API keys for Node.js — 3-segment tokens (prefix_id_secret), HMAC-SHA256 storage hash with pepper rotation, scope allowlists with wildcard match, opt-in lastUsedAt tracking, memory + Redis stores, Express + Fastify middleware. Server-only. Zero non-@exortek runtime dependencies.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "files": [
33
33
  "dist",
34
- "README.md",
35
- "CHANGELOG.md",
36
- "LICENSE"
34
+ "/README.md",
35
+ "/CHANGELOG.md",
36
+ "/LICENSE"
37
37
  ],
38
38
  "scripts": {
39
- "build": "rm -rf dist tsconfig.tsbuildinfo && rollup -c rollup.config.js && tsc -p tsconfig.json",
39
+ "build": "rm -rf dist tsconfig.tsbuildinfo && rollup -c rollup.config.js && tsc -p tsconfig.json && rollup -c ../../rollup.dts.config.mjs",
40
40
  "build:watch": "rollup -c rollup.config.js --watch",
41
41
  "build:types": "tsc -p tsconfig.json",
42
42
  "typecheck": "tsc -p tsconfig.json --noEmit",