@prisma-next/utils 0.5.0-dev.5 → 0.5.0-dev.51

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.
@@ -0,0 +1,50 @@
1
+ //#region src/canonical-stringify.d.ts
2
+ /**
3
+ * Produces a deterministic, JSON-like string representation of a value.
4
+ *
5
+ * Designed for use as a stable identity / cache key. Two values that are
6
+ * structurally equivalent — regardless of object key insertion order —
7
+ * produce the same string. Two values that differ in any meaningful way
8
+ * (including types that JSON would conflate, like `BigInt(1)` vs `1`)
9
+ * produce different strings.
10
+ *
11
+ * Supported inputs:
12
+ * - `null`, `undefined` (distinguishable: `null` → `"null"`, `undefined` → `"undefined"`)
13
+ * - `boolean`, `string`, `number` (including `NaN`, `Infinity`, `-Infinity`)
14
+ * - `bigint` (suffixed with `n` to disambiguate from `number`)
15
+ * - `Date` (tagged + ISO string)
16
+ * - `Buffer` / `Uint8Array` (tagged + hex-encoded as `Bytes(<hex>)`)
17
+ * - Other `ArrayBuffer` views — `Int8Array`, `Uint16Array`, `Float64Array`,
18
+ * `DataView`, etc. (tagged with the constructor name + hex-encoded over
19
+ * the underlying bytes, e.g. `Uint16Array(<hex>)`). Note that the bytes
20
+ * are read in host byte order, so callers that need cross-platform
21
+ * stability for multi-byte typed arrays should normalize endianness
22
+ * before passing the value in.
23
+ * - Arrays (order-preserving)
24
+ * - Plain objects (key-sorted) — only objects whose prototype is
25
+ * `Object.prototype` or `null`. Non-plain objects (`Map`, `Set`,
26
+ * `RegExp`, class instances, etc.) are rejected so they cannot silently
27
+ * collapse to `{}` and collide with each other.
28
+ *
29
+ * Throws on `function`, `symbol`, circular references, non-plain objects,
30
+ * and objects with symbol-keyed properties (which `Object.keys` would
31
+ * silently drop). Callers that need to canonicalize any of these must
32
+ * convert them to a supported representation first.
33
+ *
34
+ * The output format is intentionally not JSON: the type tags and BigInt
35
+ * suffix mean it cannot be round-tripped via `JSON.parse`. The goal is
36
+ * keying, not serialization.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * canonicalStringify({ a: 1, b: 2 }) === canonicalStringify({ b: 2, a: 1 })
41
+ * // → true
42
+ *
43
+ * canonicalStringify(1n) !== canonicalStringify(1)
44
+ * // → true
45
+ * ```
46
+ */
47
+ declare function canonicalStringify(value: unknown): string;
48
+ //#endregion
49
+ export { canonicalStringify };
50
+ //# sourceMappingURL=canonical-stringify.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-stringify.d.mts","names":[],"sources":["../src/canonical-stringify.ts"],"sourcesContent":[],"mappings":";;AA6CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,kBAAA"}
@@ -0,0 +1,104 @@
1
+ //#region src/canonical-stringify.ts
2
+ /**
3
+ * Produces a deterministic, JSON-like string representation of a value.
4
+ *
5
+ * Designed for use as a stable identity / cache key. Two values that are
6
+ * structurally equivalent — regardless of object key insertion order —
7
+ * produce the same string. Two values that differ in any meaningful way
8
+ * (including types that JSON would conflate, like `BigInt(1)` vs `1`)
9
+ * produce different strings.
10
+ *
11
+ * Supported inputs:
12
+ * - `null`, `undefined` (distinguishable: `null` → `"null"`, `undefined` → `"undefined"`)
13
+ * - `boolean`, `string`, `number` (including `NaN`, `Infinity`, `-Infinity`)
14
+ * - `bigint` (suffixed with `n` to disambiguate from `number`)
15
+ * - `Date` (tagged + ISO string)
16
+ * - `Buffer` / `Uint8Array` (tagged + hex-encoded as `Bytes(<hex>)`)
17
+ * - Other `ArrayBuffer` views — `Int8Array`, `Uint16Array`, `Float64Array`,
18
+ * `DataView`, etc. (tagged with the constructor name + hex-encoded over
19
+ * the underlying bytes, e.g. `Uint16Array(<hex>)`). Note that the bytes
20
+ * are read in host byte order, so callers that need cross-platform
21
+ * stability for multi-byte typed arrays should normalize endianness
22
+ * before passing the value in.
23
+ * - Arrays (order-preserving)
24
+ * - Plain objects (key-sorted) — only objects whose prototype is
25
+ * `Object.prototype` or `null`. Non-plain objects (`Map`, `Set`,
26
+ * `RegExp`, class instances, etc.) are rejected so they cannot silently
27
+ * collapse to `{}` and collide with each other.
28
+ *
29
+ * Throws on `function`, `symbol`, circular references, non-plain objects,
30
+ * and objects with symbol-keyed properties (which `Object.keys` would
31
+ * silently drop). Callers that need to canonicalize any of these must
32
+ * convert them to a supported representation first.
33
+ *
34
+ * The output format is intentionally not JSON: the type tags and BigInt
35
+ * suffix mean it cannot be round-tripped via `JSON.parse`. The goal is
36
+ * keying, not serialization.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * canonicalStringify({ a: 1, b: 2 }) === canonicalStringify({ b: 2, a: 1 })
41
+ * // → true
42
+ *
43
+ * canonicalStringify(1n) !== canonicalStringify(1)
44
+ * // → true
45
+ * ```
46
+ */
47
+ function canonicalStringify(value) {
48
+ return write(value, /* @__PURE__ */ new Set());
49
+ }
50
+ function write(value, seen) {
51
+ if (value === null) return "null";
52
+ if (value === void 0) return "undefined";
53
+ switch (typeof value) {
54
+ case "boolean": return value ? "true" : "false";
55
+ case "number": return writeNumber(value);
56
+ case "bigint": return `${value.toString()}n`;
57
+ case "string": return JSON.stringify(value);
58
+ case "function": throw new TypeError("canonicalStringify: functions are not supported");
59
+ case "symbol": throw new TypeError("canonicalStringify: symbols are not supported");
60
+ }
61
+ const obj = value;
62
+ if (value instanceof Date) return `Date(${value.toISOString()})`;
63
+ if (value instanceof Uint8Array) return `Bytes(${bytesToHex(value)})`;
64
+ if (ArrayBuffer.isView(value)) return `${value.constructor.name}(${bytesToHex(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))})`;
65
+ if (seen.has(obj)) throw new TypeError("canonicalStringify: circular reference detected");
66
+ seen.add(obj);
67
+ try {
68
+ if (Array.isArray(value)) return `[${value.map((item) => write(item, seen)).join(",")}]`;
69
+ return writePlainObject(obj, seen);
70
+ } finally {
71
+ seen.delete(obj);
72
+ }
73
+ }
74
+ function writeNumber(value) {
75
+ if (Number.isNaN(value)) return "NaN";
76
+ if (value === Number.POSITIVE_INFINITY) return "Infinity";
77
+ if (value === Number.NEGATIVE_INFINITY) return "-Infinity";
78
+ if (value === 0 && 1 / value === Number.NEGATIVE_INFINITY) return "-0";
79
+ return String(value);
80
+ }
81
+ function writePlainObject(obj, seen) {
82
+ const proto = Object.getPrototypeOf(obj);
83
+ if (proto !== Object.prototype && proto !== null) {
84
+ const tag = proto?.constructor?.name ?? "unknown";
85
+ throw new TypeError(`canonicalStringify: non-plain objects are not supported (got ${tag})`);
86
+ }
87
+ if (Object.getOwnPropertySymbols(obj).length > 0) throw new TypeError("canonicalStringify: objects with symbol-keyed properties are not supported");
88
+ const keys = Object.keys(obj).sort();
89
+ const parts = [];
90
+ for (const key of keys) parts.push(`${JSON.stringify(key)}:${write(obj[key], seen)}`);
91
+ return `{${parts.join(",")}}`;
92
+ }
93
+ function bytesToHex(bytes) {
94
+ let out = "";
95
+ for (let i = 0; i < bytes.length; i++) {
96
+ const byte = bytes[i];
97
+ out += byte.toString(16).padStart(2, "0");
98
+ }
99
+ return out;
100
+ }
101
+
102
+ //#endregion
103
+ export { canonicalStringify };
104
+ //# sourceMappingURL=canonical-stringify.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-stringify.mjs","names":["parts: string[]"],"sources":["../src/canonical-stringify.ts"],"sourcesContent":["/**\n * Produces a deterministic, JSON-like string representation of a value.\n *\n * Designed for use as a stable identity / cache key. Two values that are\n * structurally equivalent — regardless of object key insertion order —\n * produce the same string. Two values that differ in any meaningful way\n * (including types that JSON would conflate, like `BigInt(1)` vs `1`)\n * produce different strings.\n *\n * Supported inputs:\n * - `null`, `undefined` (distinguishable: `null` → `\"null\"`, `undefined` → `\"undefined\"`)\n * - `boolean`, `string`, `number` (including `NaN`, `Infinity`, `-Infinity`)\n * - `bigint` (suffixed with `n` to disambiguate from `number`)\n * - `Date` (tagged + ISO string)\n * - `Buffer` / `Uint8Array` (tagged + hex-encoded as `Bytes(<hex>)`)\n * - Other `ArrayBuffer` views — `Int8Array`, `Uint16Array`, `Float64Array`,\n * `DataView`, etc. (tagged with the constructor name + hex-encoded over\n * the underlying bytes, e.g. `Uint16Array(<hex>)`). Note that the bytes\n * are read in host byte order, so callers that need cross-platform\n * stability for multi-byte typed arrays should normalize endianness\n * before passing the value in.\n * - Arrays (order-preserving)\n * - Plain objects (key-sorted) — only objects whose prototype is\n * `Object.prototype` or `null`. Non-plain objects (`Map`, `Set`,\n * `RegExp`, class instances, etc.) are rejected so they cannot silently\n * collapse to `{}` and collide with each other.\n *\n * Throws on `function`, `symbol`, circular references, non-plain objects,\n * and objects with symbol-keyed properties (which `Object.keys` would\n * silently drop). Callers that need to canonicalize any of these must\n * convert them to a supported representation first.\n *\n * The output format is intentionally not JSON: the type tags and BigInt\n * suffix mean it cannot be round-tripped via `JSON.parse`. The goal is\n * keying, not serialization.\n *\n * @example\n * ```typescript\n * canonicalStringify({ a: 1, b: 2 }) === canonicalStringify({ b: 2, a: 1 })\n * // → true\n *\n * canonicalStringify(1n) !== canonicalStringify(1)\n * // → true\n * ```\n */\nexport function canonicalStringify(value: unknown): string {\n return write(value, new Set());\n}\n\nfunction write(value: unknown, seen: Set<object>): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n\n switch (typeof value) {\n case 'boolean':\n return value ? 'true' : 'false';\n case 'number':\n return writeNumber(value);\n case 'bigint':\n return `${value.toString()}n`;\n case 'string':\n return JSON.stringify(value);\n case 'function':\n throw new TypeError('canonicalStringify: functions are not supported');\n case 'symbol':\n throw new TypeError('canonicalStringify: symbols are not supported');\n }\n\n // From here, value is a non-null object.\n const obj = value as object;\n\n // Leaf object types are handled before touching `seen`: they can never\n // contain back-references, so cycle tracking is wasted work for them.\n if (value instanceof Date) {\n return `Date(${value.toISOString()})`;\n }\n\n // `Buffer` is a `Uint8Array` subclass; this branch covers both, and\n // emits the legacy `Bytes(<hex>)` tag so a `Buffer` and a same-content\n // `Uint8Array` digest identically.\n if (value instanceof Uint8Array) {\n return `Bytes(${bytesToHex(value)})`;\n }\n\n // Any other `ArrayBuffer` view — typed arrays (`Int8Array`,\n // `Uint16Array`, `Float64Array`, …) and `DataView`. Without this\n // branch they would fall through to the plain-object writer and\n // canonicalize as `{\"0\":1,\"1\":2,...}`, which would silently collide\n // with a same-keyed plain object. Tagging by constructor name keeps\n // distinct view families distinct.\n if (ArrayBuffer.isView(value)) {\n const tag = value.constructor.name;\n const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n return `${tag}(${bytesToHex(bytes)})`;\n }\n\n if (seen.has(obj)) {\n throw new TypeError('canonicalStringify: circular reference detected');\n }\n seen.add(obj);\n try {\n if (Array.isArray(value)) {\n const parts = value.map((item) => write(item, seen));\n return `[${parts.join(',')}]`;\n }\n\n return writePlainObject(obj as Record<string, unknown>, seen);\n } finally {\n seen.delete(obj);\n }\n}\n\nfunction writeNumber(value: number): string {\n if (Number.isNaN(value)) return 'NaN';\n if (value === Number.POSITIVE_INFINITY) return 'Infinity';\n if (value === Number.NEGATIVE_INFINITY) return '-Infinity';\n // Distinguish `+0` from `-0` so they hash differently.\n if (value === 0 && 1 / value === Number.NEGATIVE_INFINITY) return '-0';\n return String(value);\n}\n\nfunction writePlainObject(obj: Record<string, unknown>, seen: Set<object>): string {\n // Only true plain objects are accepted here. Without this guard, anything\n // that fell through the type-tagged branches above (`Map`, `Set`,\n // `RegExp`, class instances, …) would canonicalize to `{}` because\n // `Object.keys` returns no enumerable string keys for them — silently\n // colliding with each other and with the literal `{}`.\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== null) {\n const tag = proto?.constructor?.name ?? 'unknown';\n throw new TypeError(`canonicalStringify: non-plain objects are not supported (got ${tag})`);\n }\n\n // `Object.keys` ignores symbol-keyed properties, so they would be\n // silently dropped from the canonical form. Force callers to handle\n // them explicitly instead of producing a key that omits real data.\n if (Object.getOwnPropertySymbols(obj).length > 0) {\n throw new TypeError(\n 'canonicalStringify: objects with symbol-keyed properties are not supported',\n );\n }\n\n const keys = Object.keys(obj).sort();\n const parts: string[] = [];\n for (const key of keys) {\n parts.push(`${JSON.stringify(key)}:${write(obj[key], seen)}`);\n }\n return `{${parts.join(',')}}`;\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i] as number;\n out += byte.toString(16).padStart(2, '0');\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,mBAAmB,OAAwB;AACzD,QAAO,MAAM,uBAAO,IAAI,KAAK,CAAC;;AAGhC,SAAS,MAAM,OAAgB,MAA2B;AACxD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,UAAU,OAAW,QAAO;AAEhC,SAAQ,OAAO,OAAf;EACE,KAAK,UACH,QAAO,QAAQ,SAAS;EAC1B,KAAK,SACH,QAAO,YAAY,MAAM;EAC3B,KAAK,SACH,QAAO,GAAG,MAAM,UAAU,CAAC;EAC7B,KAAK,SACH,QAAO,KAAK,UAAU,MAAM;EAC9B,KAAK,WACH,OAAM,IAAI,UAAU,kDAAkD;EACxE,KAAK,SACH,OAAM,IAAI,UAAU,gDAAgD;;CAIxE,MAAM,MAAM;AAIZ,KAAI,iBAAiB,KACnB,QAAO,QAAQ,MAAM,aAAa,CAAC;AAMrC,KAAI,iBAAiB,WACnB,QAAO,SAAS,WAAW,MAAM,CAAC;AASpC,KAAI,YAAY,OAAO,MAAM,CAG3B,QAAO,GAFK,MAAM,YAAY,KAEhB,GAAG,WADH,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,WAAW,CAC5C,CAAC;AAGrC,KAAI,KAAK,IAAI,IAAI,CACf,OAAM,IAAI,UAAU,kDAAkD;AAExE,MAAK,IAAI,IAAI;AACb,KAAI;AACF,MAAI,MAAM,QAAQ,MAAM,CAEtB,QAAO,IADO,MAAM,KAAK,SAAS,MAAM,MAAM,KAAK,CAAC,CACnC,KAAK,IAAI,CAAC;AAG7B,SAAO,iBAAiB,KAAgC,KAAK;WACrD;AACR,OAAK,OAAO,IAAI;;;AAIpB,SAAS,YAAY,OAAuB;AAC1C,KAAI,OAAO,MAAM,MAAM,CAAE,QAAO;AAChC,KAAI,UAAU,OAAO,kBAAmB,QAAO;AAC/C,KAAI,UAAU,OAAO,kBAAmB,QAAO;AAE/C,KAAI,UAAU,KAAK,IAAI,UAAU,OAAO,kBAAmB,QAAO;AAClE,QAAO,OAAO,MAAM;;AAGtB,SAAS,iBAAiB,KAA8B,MAA2B;CAMjF,MAAM,QAAQ,OAAO,eAAe,IAAI;AACxC,KAAI,UAAU,OAAO,aAAa,UAAU,MAAM;EAChD,MAAM,MAAM,OAAO,aAAa,QAAQ;AACxC,QAAM,IAAI,UAAU,gEAAgE,IAAI,GAAG;;AAM7F,KAAI,OAAO,sBAAsB,IAAI,CAAC,SAAS,EAC7C,OAAM,IAAI,UACR,6EACD;CAGH,MAAM,OAAO,OAAO,KAAK,IAAI,CAAC,MAAM;CACpC,MAAMA,QAAkB,EAAE;AAC1B,MAAK,MAAM,OAAO,KAChB,OAAM,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,KAAK,GAAG;AAE/D,QAAO,IAAI,MAAM,KAAK,IAAI,CAAC;;AAG7B,SAAS,WAAW,OAA2B;CAC7C,IAAI,MAAM;AACV,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;AACnB,SAAO,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;;AAE3C,QAAO"}
@@ -27,4 +27,4 @@ function ifDefined(key, value) {
27
27
 
28
28
  //#endregion
29
29
  export { ifDefined as t };
30
- //# sourceMappingURL=defined-CV9lG7rM.mjs.map
30
+ //# sourceMappingURL=defined-1jOuAm8c.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"defined-CV9lG7rM.mjs","names":[],"sources":["../src/defined.ts"],"sourcesContent":["/**\n * Returns an object with the key/value if value is defined, otherwise an empty object.\n *\n * Use with spread to conditionally include optional properties while satisfying\n * exactOptionalPropertyTypes. This is explicit about which properties are optional\n * and won't inadvertently strip other undefined values.\n *\n * @example\n * ```typescript\n * // Instead of:\n * const obj = {\n * required: 'value',\n * ...(optional ? { optional } : {}),\n * };\n *\n * // Use:\n * const obj = {\n * required: 'value',\n * ...ifDefined('optional', optional),\n * };\n * ```\n */\nexport function ifDefined<K extends string, V>(\n key: K,\n value: V | undefined,\n): Record<never, never> | { [P in K]: V } {\n return value !== undefined ? ({ [key]: value } as { [P in K]: V }) : {};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,UACd,KACA,OACwC;AACxC,QAAO,UAAU,SAAa,GAAG,MAAM,OAAO,GAAuB,EAAE"}
1
+ {"version":3,"file":"defined-1jOuAm8c.mjs","names":[],"sources":["../src/defined.ts"],"sourcesContent":["/**\n * Returns an object with the key/value if value is defined, otherwise an empty object.\n *\n * Use with spread to conditionally include optional properties while satisfying\n * exactOptionalPropertyTypes. This is explicit about which properties are optional\n * and won't inadvertently strip other undefined values.\n *\n * @example\n * ```typescript\n * // Instead of:\n * const obj = {\n * required: 'value',\n * ...(optional ? { optional } : {}),\n * };\n *\n * // Use:\n * const obj = {\n * required: 'value',\n * ...ifDefined('optional', optional),\n * };\n * ```\n */\nexport function ifDefined<K extends string, V>(\n key: K,\n value: V | undefined,\n): Record<never, never> | { [P in K]: V } {\n return value !== undefined ? ({ [key]: value } as { [P in K]: V }) : {};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,UACd,KACA,OACwC;AACxC,QAAO,UAAU,SAAa,GAAG,MAAM,OAAO,GAAuB,EAAE"}
package/dist/defined.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as ifDefined } from "./defined-CV9lG7rM.mjs";
1
+ import { t as ifDefined } from "./defined-1jOuAm8c.mjs";
2
2
 
3
3
  export { ifDefined };
@@ -0,0 +1,58 @@
1
+ //#region src/hash-content.d.ts
2
+ /**
3
+ * Hashes a canonical-string representation of an execution into a bounded,
4
+ * opaque cache-key digest.
5
+ *
6
+ * Designed for use as the final step of `RuntimeMiddlewareContext.contentHash`
7
+ * implementations: family runtimes compose a canonical string from
8
+ * `meta.storageHash`, the rendered statement (or wire command), and
9
+ * canonicalized parameters via `canonicalStringify`, then pipe the result
10
+ * through this helper.
11
+ *
12
+ * Why hash the canonical string instead of using it directly as a `Map` key:
13
+ *
14
+ * 1. **Bounded memory.** A raw canonical key includes concrete parameter
15
+ * values, so a query bound to a 10 MB JSON column or binary blob produces
16
+ * a 10 MB cache key. With `maxEntries = 1000`, that scales to gigabytes
17
+ * of cache keys alone. SHA-512 pins per-key cost at a fixed digest
18
+ * length regardless of input size.
19
+ *
20
+ * 2. **Sensitive-data isolation.** The canonical string contains parameter
21
+ * values verbatim. Cache keys flow into debug logs, Redis `KEYS`/`MONITOR`
22
+ * output, persistence dumps, monitoring tools, and any user-supplied
23
+ * `CacheStore` implementation. Hashing prevents PII / credentials /
24
+ * tokens that appear in query parameters from showing up in any of those
25
+ * surfaces.
26
+ *
27
+ * Algorithm choice — SHA-512 (`SHA-512` via the WebCrypto API):
28
+ *
29
+ * - **Portability.** WebCrypto (`globalThis.crypto.subtle`) is available in
30
+ * every modern JavaScript runtime — Node, Deno, Bun, browsers, edge
31
+ * workers — without importing a Node-specific module. This keeps the
32
+ * helper usable in non-Node hosts where `node:crypto` is not available.
33
+ * - **Collision space.** 512 bits of output makes accidental collisions
34
+ * astronomically improbable — far beyond what a cache needs, but the
35
+ * incremental cost over 256-bit output is negligible and the headroom
36
+ * is free.
37
+ * - **No additional dependency.** SHA-512 is part of the WebCrypto standard
38
+ * set of digest algorithms; no third-party package needed.
39
+ *
40
+ * The function is `async` because the WebCrypto digest API is async by
41
+ * design. Callers must await the result.
42
+ *
43
+ * Output format: `sha512:HEXDIGEST` (128-char hex with the algorithm tag
44
+ * prefix). Self-describing so a future migration to a different hash
45
+ * produces visibly distinct keys, and consistent with the
46
+ * `sha256:HEXDIGEST` shape already used by `meta.storageHash`.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const canonical = `${exec.meta.storageHash}|${exec.sql}|${canonicalStringify(exec.params)}`;
51
+ * return await hashContent(canonical);
52
+ * // → 'sha512:8f3...e1c' (always 135 chars: 'sha512:' + 128 hex chars)
53
+ * ```
54
+ */
55
+ declare function hashContent(value: string): Promise<string>;
56
+ //#endregion
57
+ export { hashContent };
58
+ //# sourceMappingURL=hash-content.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash-content.d.mts","names":[],"sources":["../src/hash-content.ts"],"sourcesContent":[],"mappings":";;AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,WAAA,iBAA4B"}
@@ -0,0 +1,63 @@
1
+ //#region src/hash-content.ts
2
+ /**
3
+ * Hashes a canonical-string representation of an execution into a bounded,
4
+ * opaque cache-key digest.
5
+ *
6
+ * Designed for use as the final step of `RuntimeMiddlewareContext.contentHash`
7
+ * implementations: family runtimes compose a canonical string from
8
+ * `meta.storageHash`, the rendered statement (or wire command), and
9
+ * canonicalized parameters via `canonicalStringify`, then pipe the result
10
+ * through this helper.
11
+ *
12
+ * Why hash the canonical string instead of using it directly as a `Map` key:
13
+ *
14
+ * 1. **Bounded memory.** A raw canonical key includes concrete parameter
15
+ * values, so a query bound to a 10 MB JSON column or binary blob produces
16
+ * a 10 MB cache key. With `maxEntries = 1000`, that scales to gigabytes
17
+ * of cache keys alone. SHA-512 pins per-key cost at a fixed digest
18
+ * length regardless of input size.
19
+ *
20
+ * 2. **Sensitive-data isolation.** The canonical string contains parameter
21
+ * values verbatim. Cache keys flow into debug logs, Redis `KEYS`/`MONITOR`
22
+ * output, persistence dumps, monitoring tools, and any user-supplied
23
+ * `CacheStore` implementation. Hashing prevents PII / credentials /
24
+ * tokens that appear in query parameters from showing up in any of those
25
+ * surfaces.
26
+ *
27
+ * Algorithm choice — SHA-512 (`SHA-512` via the WebCrypto API):
28
+ *
29
+ * - **Portability.** WebCrypto (`globalThis.crypto.subtle`) is available in
30
+ * every modern JavaScript runtime — Node, Deno, Bun, browsers, edge
31
+ * workers — without importing a Node-specific module. This keeps the
32
+ * helper usable in non-Node hosts where `node:crypto` is not available.
33
+ * - **Collision space.** 512 bits of output makes accidental collisions
34
+ * astronomically improbable — far beyond what a cache needs, but the
35
+ * incremental cost over 256-bit output is negligible and the headroom
36
+ * is free.
37
+ * - **No additional dependency.** SHA-512 is part of the WebCrypto standard
38
+ * set of digest algorithms; no third-party package needed.
39
+ *
40
+ * The function is `async` because the WebCrypto digest API is async by
41
+ * design. Callers must await the result.
42
+ *
43
+ * Output format: `sha512:HEXDIGEST` (128-char hex with the algorithm tag
44
+ * prefix). Self-describing so a future migration to a different hash
45
+ * produces visibly distinct keys, and consistent with the
46
+ * `sha256:HEXDIGEST` shape already used by `meta.storageHash`.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const canonical = `${exec.meta.storageHash}|${exec.sql}|${canonicalStringify(exec.params)}`;
51
+ * return await hashContent(canonical);
52
+ * // → 'sha512:8f3...e1c' (always 135 chars: 'sha512:' + 128 hex chars)
53
+ * ```
54
+ */
55
+ async function hashContent(value) {
56
+ const bytes = new TextEncoder().encode(value);
57
+ const digest = await crypto.subtle.digest("SHA-512", bytes);
58
+ return `sha512:${Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("")}`;
59
+ }
60
+
61
+ //#endregion
62
+ export { hashContent };
63
+ //# sourceMappingURL=hash-content.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash-content.mjs","names":[],"sources":["../src/hash-content.ts"],"sourcesContent":["/**\n * Hashes a canonical-string representation of an execution into a bounded,\n * opaque cache-key digest.\n *\n * Designed for use as the final step of `RuntimeMiddlewareContext.contentHash`\n * implementations: family runtimes compose a canonical string from\n * `meta.storageHash`, the rendered statement (or wire command), and\n * canonicalized parameters via `canonicalStringify`, then pipe the result\n * through this helper.\n *\n * Why hash the canonical string instead of using it directly as a `Map` key:\n *\n * 1. **Bounded memory.** A raw canonical key includes concrete parameter\n * values, so a query bound to a 10 MB JSON column or binary blob produces\n * a 10 MB cache key. With `maxEntries = 1000`, that scales to gigabytes\n * of cache keys alone. SHA-512 pins per-key cost at a fixed digest\n * length regardless of input size.\n *\n * 2. **Sensitive-data isolation.** The canonical string contains parameter\n * values verbatim. Cache keys flow into debug logs, Redis `KEYS`/`MONITOR`\n * output, persistence dumps, monitoring tools, and any user-supplied\n * `CacheStore` implementation. Hashing prevents PII / credentials /\n * tokens that appear in query parameters from showing up in any of those\n * surfaces.\n *\n * Algorithm choice — SHA-512 (`SHA-512` via the WebCrypto API):\n *\n * - **Portability.** WebCrypto (`globalThis.crypto.subtle`) is available in\n * every modern JavaScript runtime — Node, Deno, Bun, browsers, edge\n * workers — without importing a Node-specific module. This keeps the\n * helper usable in non-Node hosts where `node:crypto` is not available.\n * - **Collision space.** 512 bits of output makes accidental collisions\n * astronomically improbable — far beyond what a cache needs, but the\n * incremental cost over 256-bit output is negligible and the headroom\n * is free.\n * - **No additional dependency.** SHA-512 is part of the WebCrypto standard\n * set of digest algorithms; no third-party package needed.\n *\n * The function is `async` because the WebCrypto digest API is async by\n * design. Callers must await the result.\n *\n * Output format: `sha512:HEXDIGEST` (128-char hex with the algorithm tag\n * prefix). Self-describing so a future migration to a different hash\n * produces visibly distinct keys, and consistent with the\n * `sha256:HEXDIGEST` shape already used by `meta.storageHash`.\n *\n * @example\n * ```typescript\n * const canonical = `${exec.meta.storageHash}|${exec.sql}|${canonicalStringify(exec.params)}`;\n * return await hashContent(canonical);\n * // → 'sha512:8f3...e1c' (always 135 chars: 'sha512:' + 128 hex chars)\n * ```\n */\nexport async function hashContent(value: string): Promise<string> {\n const bytes = new TextEncoder().encode(value);\n const digest = await crypto.subtle.digest('SHA-512', bytes);\n const hex = Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');\n return `sha512:${hex}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,eAAsB,YAAY,OAAgC;CAChE,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,MAAM;CAC7C,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,MAAM;AAE3D,QAAO,UADK,MAAM,KAAK,IAAI,WAAW,OAAO,GAAG,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG"}
@@ -1,4 +1,4 @@
1
- import { t as ifDefined } from "./defined-CV9lG7rM.mjs";
1
+ import { t as ifDefined } from "./defined-1jOuAm8c.mjs";
2
2
 
3
3
  //#region src/redact-db-url.ts
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/utils",
3
- "version": "0.5.0-dev.5",
3
+ "version": "0.5.0-dev.51",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Shared utility functions for Prisma Next",
@@ -22,7 +22,9 @@
22
22
  "./abortable": "./dist/abortable.mjs",
23
23
  "./array-equal": "./dist/array-equal.mjs",
24
24
  "./assertions": "./dist/assertions.mjs",
25
+ "./canonical-stringify": "./dist/canonical-stringify.mjs",
25
26
  "./defined": "./dist/defined.mjs",
27
+ "./hash-content": "./dist/hash-content.mjs",
26
28
  "./redact-db-url": "./dist/redact-db-url.mjs",
27
29
  "./result": "./dist/result.mjs",
28
30
  "./simplify-deep": "./dist/simplify-deep.mjs",
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Produces a deterministic, JSON-like string representation of a value.
3
+ *
4
+ * Designed for use as a stable identity / cache key. Two values that are
5
+ * structurally equivalent — regardless of object key insertion order —
6
+ * produce the same string. Two values that differ in any meaningful way
7
+ * (including types that JSON would conflate, like `BigInt(1)` vs `1`)
8
+ * produce different strings.
9
+ *
10
+ * Supported inputs:
11
+ * - `null`, `undefined` (distinguishable: `null` → `"null"`, `undefined` → `"undefined"`)
12
+ * - `boolean`, `string`, `number` (including `NaN`, `Infinity`, `-Infinity`)
13
+ * - `bigint` (suffixed with `n` to disambiguate from `number`)
14
+ * - `Date` (tagged + ISO string)
15
+ * - `Buffer` / `Uint8Array` (tagged + hex-encoded as `Bytes(<hex>)`)
16
+ * - Other `ArrayBuffer` views — `Int8Array`, `Uint16Array`, `Float64Array`,
17
+ * `DataView`, etc. (tagged with the constructor name + hex-encoded over
18
+ * the underlying bytes, e.g. `Uint16Array(<hex>)`). Note that the bytes
19
+ * are read in host byte order, so callers that need cross-platform
20
+ * stability for multi-byte typed arrays should normalize endianness
21
+ * before passing the value in.
22
+ * - Arrays (order-preserving)
23
+ * - Plain objects (key-sorted) — only objects whose prototype is
24
+ * `Object.prototype` or `null`. Non-plain objects (`Map`, `Set`,
25
+ * `RegExp`, class instances, etc.) are rejected so they cannot silently
26
+ * collapse to `{}` and collide with each other.
27
+ *
28
+ * Throws on `function`, `symbol`, circular references, non-plain objects,
29
+ * and objects with symbol-keyed properties (which `Object.keys` would
30
+ * silently drop). Callers that need to canonicalize any of these must
31
+ * convert them to a supported representation first.
32
+ *
33
+ * The output format is intentionally not JSON: the type tags and BigInt
34
+ * suffix mean it cannot be round-tripped via `JSON.parse`. The goal is
35
+ * keying, not serialization.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * canonicalStringify({ a: 1, b: 2 }) === canonicalStringify({ b: 2, a: 1 })
40
+ * // → true
41
+ *
42
+ * canonicalStringify(1n) !== canonicalStringify(1)
43
+ * // → true
44
+ * ```
45
+ */
46
+ export function canonicalStringify(value: unknown): string {
47
+ return write(value, new Set());
48
+ }
49
+
50
+ function write(value: unknown, seen: Set<object>): string {
51
+ if (value === null) return 'null';
52
+ if (value === undefined) return 'undefined';
53
+
54
+ switch (typeof value) {
55
+ case 'boolean':
56
+ return value ? 'true' : 'false';
57
+ case 'number':
58
+ return writeNumber(value);
59
+ case 'bigint':
60
+ return `${value.toString()}n`;
61
+ case 'string':
62
+ return JSON.stringify(value);
63
+ case 'function':
64
+ throw new TypeError('canonicalStringify: functions are not supported');
65
+ case 'symbol':
66
+ throw new TypeError('canonicalStringify: symbols are not supported');
67
+ }
68
+
69
+ // From here, value is a non-null object.
70
+ const obj = value as object;
71
+
72
+ // Leaf object types are handled before touching `seen`: they can never
73
+ // contain back-references, so cycle tracking is wasted work for them.
74
+ if (value instanceof Date) {
75
+ return `Date(${value.toISOString()})`;
76
+ }
77
+
78
+ // `Buffer` is a `Uint8Array` subclass; this branch covers both, and
79
+ // emits the legacy `Bytes(<hex>)` tag so a `Buffer` and a same-content
80
+ // `Uint8Array` digest identically.
81
+ if (value instanceof Uint8Array) {
82
+ return `Bytes(${bytesToHex(value)})`;
83
+ }
84
+
85
+ // Any other `ArrayBuffer` view — typed arrays (`Int8Array`,
86
+ // `Uint16Array`, `Float64Array`, …) and `DataView`. Without this
87
+ // branch they would fall through to the plain-object writer and
88
+ // canonicalize as `{"0":1,"1":2,...}`, which would silently collide
89
+ // with a same-keyed plain object. Tagging by constructor name keeps
90
+ // distinct view families distinct.
91
+ if (ArrayBuffer.isView(value)) {
92
+ const tag = value.constructor.name;
93
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
94
+ return `${tag}(${bytesToHex(bytes)})`;
95
+ }
96
+
97
+ if (seen.has(obj)) {
98
+ throw new TypeError('canonicalStringify: circular reference detected');
99
+ }
100
+ seen.add(obj);
101
+ try {
102
+ if (Array.isArray(value)) {
103
+ const parts = value.map((item) => write(item, seen));
104
+ return `[${parts.join(',')}]`;
105
+ }
106
+
107
+ return writePlainObject(obj as Record<string, unknown>, seen);
108
+ } finally {
109
+ seen.delete(obj);
110
+ }
111
+ }
112
+
113
+ function writeNumber(value: number): string {
114
+ if (Number.isNaN(value)) return 'NaN';
115
+ if (value === Number.POSITIVE_INFINITY) return 'Infinity';
116
+ if (value === Number.NEGATIVE_INFINITY) return '-Infinity';
117
+ // Distinguish `+0` from `-0` so they hash differently.
118
+ if (value === 0 && 1 / value === Number.NEGATIVE_INFINITY) return '-0';
119
+ return String(value);
120
+ }
121
+
122
+ function writePlainObject(obj: Record<string, unknown>, seen: Set<object>): string {
123
+ // Only true plain objects are accepted here. Without this guard, anything
124
+ // that fell through the type-tagged branches above (`Map`, `Set`,
125
+ // `RegExp`, class instances, …) would canonicalize to `{}` because
126
+ // `Object.keys` returns no enumerable string keys for them — silently
127
+ // colliding with each other and with the literal `{}`.
128
+ const proto = Object.getPrototypeOf(obj);
129
+ if (proto !== Object.prototype && proto !== null) {
130
+ const tag = proto?.constructor?.name ?? 'unknown';
131
+ throw new TypeError(`canonicalStringify: non-plain objects are not supported (got ${tag})`);
132
+ }
133
+
134
+ // `Object.keys` ignores symbol-keyed properties, so they would be
135
+ // silently dropped from the canonical form. Force callers to handle
136
+ // them explicitly instead of producing a key that omits real data.
137
+ if (Object.getOwnPropertySymbols(obj).length > 0) {
138
+ throw new TypeError(
139
+ 'canonicalStringify: objects with symbol-keyed properties are not supported',
140
+ );
141
+ }
142
+
143
+ const keys = Object.keys(obj).sort();
144
+ const parts: string[] = [];
145
+ for (const key of keys) {
146
+ parts.push(`${JSON.stringify(key)}:${write(obj[key], seen)}`);
147
+ }
148
+ return `{${parts.join(',')}}`;
149
+ }
150
+
151
+ function bytesToHex(bytes: Uint8Array): string {
152
+ let out = '';
153
+ for (let i = 0; i < bytes.length; i++) {
154
+ const byte = bytes[i] as number;
155
+ out += byte.toString(16).padStart(2, '0');
156
+ }
157
+ return out;
158
+ }
@@ -0,0 +1 @@
1
+ export { canonicalStringify } from '../canonical-stringify';
@@ -0,0 +1 @@
1
+ export { hashContent } from '../hash-content';
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Hashes a canonical-string representation of an execution into a bounded,
3
+ * opaque cache-key digest.
4
+ *
5
+ * Designed for use as the final step of `RuntimeMiddlewareContext.contentHash`
6
+ * implementations: family runtimes compose a canonical string from
7
+ * `meta.storageHash`, the rendered statement (or wire command), and
8
+ * canonicalized parameters via `canonicalStringify`, then pipe the result
9
+ * through this helper.
10
+ *
11
+ * Why hash the canonical string instead of using it directly as a `Map` key:
12
+ *
13
+ * 1. **Bounded memory.** A raw canonical key includes concrete parameter
14
+ * values, so a query bound to a 10 MB JSON column or binary blob produces
15
+ * a 10 MB cache key. With `maxEntries = 1000`, that scales to gigabytes
16
+ * of cache keys alone. SHA-512 pins per-key cost at a fixed digest
17
+ * length regardless of input size.
18
+ *
19
+ * 2. **Sensitive-data isolation.** The canonical string contains parameter
20
+ * values verbatim. Cache keys flow into debug logs, Redis `KEYS`/`MONITOR`
21
+ * output, persistence dumps, monitoring tools, and any user-supplied
22
+ * `CacheStore` implementation. Hashing prevents PII / credentials /
23
+ * tokens that appear in query parameters from showing up in any of those
24
+ * surfaces.
25
+ *
26
+ * Algorithm choice — SHA-512 (`SHA-512` via the WebCrypto API):
27
+ *
28
+ * - **Portability.** WebCrypto (`globalThis.crypto.subtle`) is available in
29
+ * every modern JavaScript runtime — Node, Deno, Bun, browsers, edge
30
+ * workers — without importing a Node-specific module. This keeps the
31
+ * helper usable in non-Node hosts where `node:crypto` is not available.
32
+ * - **Collision space.** 512 bits of output makes accidental collisions
33
+ * astronomically improbable — far beyond what a cache needs, but the
34
+ * incremental cost over 256-bit output is negligible and the headroom
35
+ * is free.
36
+ * - **No additional dependency.** SHA-512 is part of the WebCrypto standard
37
+ * set of digest algorithms; no third-party package needed.
38
+ *
39
+ * The function is `async` because the WebCrypto digest API is async by
40
+ * design. Callers must await the result.
41
+ *
42
+ * Output format: `sha512:HEXDIGEST` (128-char hex with the algorithm tag
43
+ * prefix). Self-describing so a future migration to a different hash
44
+ * produces visibly distinct keys, and consistent with the
45
+ * `sha256:HEXDIGEST` shape already used by `meta.storageHash`.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const canonical = `${exec.meta.storageHash}|${exec.sql}|${canonicalStringify(exec.params)}`;
50
+ * return await hashContent(canonical);
51
+ * // → 'sha512:8f3...e1c' (always 135 chars: 'sha512:' + 128 hex chars)
52
+ * ```
53
+ */
54
+ export async function hashContent(value: string): Promise<string> {
55
+ const bytes = new TextEncoder().encode(value);
56
+ const digest = await crypto.subtle.digest('SHA-512', bytes);
57
+ const hex = Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');
58
+ return `sha512:${hex}`;
59
+ }