@vouchington/utils 0.5.2 → 0.5.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/README.md CHANGED
@@ -11,12 +11,16 @@ import { buildSignatureHeaders, verifySignature } from '@vouchington/utils/http-
11
11
  import { bestAcceptLanguageMatch } from '@vouchington/utils/language-tags'
12
12
  import { createMessageTranslator } from '@vouchington/utils/message-catalog'
13
13
  import { createReportInputParser } from '@vouchington/utils/moderation'
14
+ import { cacheTag, decodeCacheTagPathSegment } from '@vouchington/utils/cache-tags'
14
15
  import { getSentryEnvelopeDiagnostics } from '@vouchington/utils/sentry-envelope'
15
16
  import { createScopeCatalog } from '@vouchington/utils/scopes'
16
17
  ```
17
18
 
18
19
  ## Subpaths
19
20
 
21
+ - `cache-tags`: canonical printable-ASCII wire encoding, one-segment URL decoding, validation,
22
+ and escape-boundary truncation. Applications own family names, route recognition, and purge policy.
23
+ Single-tag validation rejects spaces and comma separators; raw commas are percent-encoded.
20
24
  - `sentry-envelope`: bounded UTF-8 envelope record scanning and safe item-type diagnostics.
21
25
  Callers choose the byte and type-list limits; DSN, tunnel, request, and logging policy stay local.
22
26
  - `scopes`: caller-owned scope catalog validation, prerequisite expansion, audience and surface
@@ -0,0 +1,10 @@
1
+ /** Maximum accepted Cache-Tag byte length at the wire boundary. */
2
+ export declare const MAX_CACHE_TAG_BYTES = 1024;
3
+ /** Check whether one tag is printable ASCII without spaces or commas and fits the wire limit. */
4
+ export declare function isValidCacheTag(tag: string): boolean;
5
+ /** Decode exactly one URL pathname segment. Never pass raw database values through this function. */
6
+ export declare function decodeCacheTagPathSegment(segment: string): string;
7
+ /** Encode a raw identifier; percent escapes in raw values remain literal identifier text. */
8
+ export declare function encodeCacheTagValue(value: string): string;
9
+ /** Mint a bounded tag for a caller supplied family and raw identifier. */
10
+ export declare function cacheTag(family: string, value: string): string;
@@ -0,0 +1,40 @@
1
+ import { normalizeKey } from './strings.mjs';
2
+ /** Maximum accepted Cache-Tag byte length at the wire boundary. */
3
+ export const MAX_CACHE_TAG_BYTES = 1024;
4
+ const CACHE_TAG_CHARSET = /^[\x21-\x2b\x2d-\x7e]+$/;
5
+ /** Check whether one tag is printable ASCII without spaces or commas and fits the wire limit. */
6
+ export function isValidCacheTag(tag) {
7
+ return tag.length > 0 && tag.length <= MAX_CACHE_TAG_BYTES && CACHE_TAG_CHARSET.test(tag);
8
+ }
9
+ /** Decode exactly one URL pathname segment. Never pass raw database values through this function. */
10
+ export function decodeCacheTagPathSegment(segment) {
11
+ if (!segment.includes('%'))
12
+ return segment;
13
+ try {
14
+ return decodeURIComponent(segment);
15
+ }
16
+ catch {
17
+ return segment;
18
+ }
19
+ }
20
+ /** Encode a raw identifier; percent escapes in raw values remain literal identifier text. */
21
+ export function encodeCacheTagValue(value) {
22
+ return encodeURIComponent(normalizeKey(value)).toLowerCase();
23
+ }
24
+ /** Mint a bounded tag for a caller supplied family and raw identifier. */
25
+ export function cacheTag(family, value) {
26
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(family) || family.length >= MAX_CACHE_TAG_BYTES) {
27
+ throw new TypeError('Cache tag family must be a bounded lowercase ASCII token');
28
+ }
29
+ return truncateCacheTag(`${family}:${encodeCacheTagValue(value)}`);
30
+ }
31
+ function truncateCacheTag(tag) {
32
+ if (tag.length <= MAX_CACHE_TAG_BYTES)
33
+ return tag;
34
+ let end = MAX_CACHE_TAG_BYTES;
35
+ if (tag[end - 1] === '%')
36
+ end -= 1;
37
+ else if (tag[end - 2] === '%')
38
+ end -= 2;
39
+ return tag.slice(0, end);
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vouchington/utils",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Dependency-free runtime utilities for Vouchington-style applications.",
5
5
  "homepage": "https://github.com/vouchington/vouchington-platform/tree/main/packages/utils#readme",
6
6
  "bugs": {
@@ -20,6 +20,11 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "exports": {
23
+ "./cache-tags": {
24
+ "types": "./dist/cache-tags.d.mts",
25
+ "import": "./dist/cache-tags.mjs",
26
+ "default": "./dist/cache-tags.mjs"
27
+ },
23
28
  "./sentry-envelope": {
24
29
  "types": "./dist/sentry-envelope.d.mts",
25
30
  "import": "./dist/sentry-envelope.mjs",