@prisma/param-graph 7.4.0-integration-parameterization.10 → 7.4.0-integration-parameterization.11

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.
@@ -4,6 +4,108 @@
4
4
  * This module handles compact binary encoding/decoding of the param graph structure.
5
5
  * The format uses a hybrid approach: JSON string array for field names + binary blob
6
6
  * for structural data (nodes, edges, roots).
7
+ *
8
+ * ## Serialized Representation
9
+ *
10
+ * ```
11
+ * SerializedParamGraph {
12
+ * strings: string[] // String table (field names, enum names, root keys)
13
+ * graph: string // Base64url-encoded binary blob
14
+ * }
15
+ * ```
16
+ *
17
+ * ## Why Hybrid?
18
+ *
19
+ * - **Strings stay as JSON**: V8's JSON.parse is highly optimized for string arrays
20
+ * - **Structure goes binary**: Indices, flags, masks benefit from compact encoding
21
+ * - **Best of both**: Fast parsing + compact size where it matters
22
+ *
23
+ * ## Format Selection
24
+ *
25
+ * Two binary formats based on data size (selected automatically at serialization):
26
+ *
27
+ * - **Compact (0x00)**: 16-bit indices, for graphs with ≤65534 items
28
+ * - **Wide (0x01)**: 32-bit indices, for larger graphs
29
+ *
30
+ * Sentinel values for "none/undefined": 0xFFFF (compact) or 0xFFFFFFFF (wide)
31
+ *
32
+ * ## Binary Blob Layout
33
+ *
34
+ * All multi-byte integers are little-endian.
35
+ *
36
+ * ```
37
+ * ┌───────────────────────────────────────────────────────────────────┐
38
+ * │ HEADER │
39
+ * ├───────────────────────────────────────────────────────────────────┤
40
+ * │ format: u8 │ 0x00 = compact (16-bit), 0x01 = wide │
41
+ * │ padding: 1|3 bytes │ Alignment padding (1 compact, 3 wide) │
42
+ * │ inputNodeCount: word │ Number of input nodes │
43
+ * │ outputNodeCount: word │ Number of output nodes │
44
+ * │ rootCount: word │ Number of root entries │
45
+ * └───────────────────────────────────────────────────────────────────┘
46
+ *
47
+ * ┌───────────────────────────────────────────────────────────────────┐
48
+ * │ INPUT NODES (repeated inputNodeCount times) │
49
+ * ├───────────────────────────────────────────────────────────────────┤
50
+ * │ edgeCount: word │ Number of edges in this node │
51
+ * │ edges[] │ Edge data (see Input Edge below) │
52
+ * └───────────────────────────────────────────────────────────────────┘
53
+ *
54
+ * ┌───────────────────────────────────────────────────────────────────┐
55
+ * │ INPUT EDGE (compact: 10 bytes, wide: 20 bytes) │
56
+ * ├───────────────────────────────────────────────────────────────────┤
57
+ * │ fieldIndex: word │ Index into string table │
58
+ * │ scalarMask: u16 │ Scalar type bitmask (0 if none) │
59
+ * │ [padding: 2 bytes] │ (wide format only, for alignment) │
60
+ * │ childNodeId: word │ Child input node ID (sentinel=none) │
61
+ * │ enumNameIndex: word │ Enum name in string table (sentinel=none) │
62
+ * │ flags: u8 │ Edge capability flags │
63
+ * │ padding: 1|3 bytes │ Alignment padding (1 compact, 3 wide) │
64
+ * └───────────────────────────────────────────────────────────────────┘
65
+ *
66
+ * ┌───────────────────────────────────────────────────────────────────┐
67
+ * │ OUTPUT NODES (repeated outputNodeCount times) │
68
+ * ├───────────────────────────────────────────────────────────────────┤
69
+ * │ edgeCount: word │ Number of edges in this node │
70
+ * │ edges[] │ Edge data (see Output Edge below) │
71
+ * └───────────────────────────────────────────────────────────────────┘
72
+ *
73
+ * ┌───────────────────────────────────────────────────────────────────┐
74
+ * │ OUTPUT EDGE (compact: 6 bytes, wide: 12 bytes) │
75
+ * ├───────────────────────────────────────────────────────────────────┤
76
+ * │ fieldIndex: word │ Index into string table │
77
+ * │ argsNodeId: word │ Args input node ID (sentinel=none) │
78
+ * │ outputNodeId: word │ Child output node ID (sentinel=none) │
79
+ * └───────────────────────────────────────────────────────────────────┘
80
+ *
81
+ * ┌───────────────────────────────────────────────────────────────────┐
82
+ * │ ROOTS (repeated rootCount times) │
83
+ * ├───────────────────────────────────────────────────────────────────┤
84
+ * │ keyIndex: word │ Root key index in string table │
85
+ * │ argsNodeId: word │ Args input node ID (sentinel=none) │
86
+ * │ outputNodeId: word │ Output node ID (sentinel=none) │
87
+ * └───────────────────────────────────────────────────────────────────┘
88
+ * ```
89
+ *
90
+ * Where "word" is u16 (compact) or u32 (wide).
91
+ *
92
+ * ## Size Summary
93
+ *
94
+ * | Component | Compact | Wide |
95
+ * |----------------|----------|----------|
96
+ * | Header | 8 bytes | 16 bytes |
97
+ * | Input Edge | 10 bytes | 20 bytes |
98
+ * | Output Edge | 6 bytes | 12 bytes |
99
+ * | Root Entry | 6 bytes | 12 bytes |
100
+ *
101
+ * ## Embedding in Generated Client
102
+ *
103
+ * ```js
104
+ * config.parameterizationSchema = {
105
+ * strings: JSON.parse('["where","id","email",...]'),
106
+ * graph: "base64url_encoded_binary_blob..."
107
+ * }
108
+ * ```
7
109
  */
8
110
  import type { ParamGraphData } from './types';
9
111
  /**
@@ -99,8 +99,7 @@ class Serializer {
99
99
  this.#offset += bytes;
100
100
  }
101
101
  #calculateBufferSize() {
102
- let size = 1;
103
- size += this.#useWide ? 12 : 6;
102
+ let size = this.#useWide ? 16 : 8;
104
103
  for (const node of this.#data.inputNodes) {
105
104
  size += this.#wordSize;
106
105
  const edgeCount = Object.keys(node.edges).length;
@@ -116,6 +115,7 @@ class Serializer {
116
115
  }
117
116
  #writeHeader() {
118
117
  this.#writeByte(this.#useWide ? FORMAT_WIDE : FORMAT_COMPACT);
118
+ this.#skip(this.#useWide ? 3 : 1);
119
119
  this.#writeWord(this.#data.inputNodes.length);
120
120
  this.#writeWord(this.#data.outputNodes.length);
121
121
  this.#writeWord(this.#rootKeys.length);
@@ -154,6 +154,9 @@ class Serializer {
154
154
  for (const key of this.#rootKeys) {
155
155
  const root = this.#data.roots[key];
156
156
  const keyIndex = this.#data.strings.indexOf(key);
157
+ if (keyIndex === -1) {
158
+ throw new Error(`Root key "${key}" not found in strings table`);
159
+ }
157
160
  this.#writeWord(keyIndex);
158
161
  this.#writeOptionalWord(root.argsNodeId);
159
162
  this.#writeOptionalWord(root.outputNodeId);
@@ -217,7 +220,11 @@ class Deserializer {
217
220
  }
218
221
  #readHeader() {
219
222
  const format = this.#readByte();
223
+ if (format !== FORMAT_COMPACT && format !== FORMAT_WIDE) {
224
+ throw new Error(`Unknown param graph format: 0x${format.toString(16).padStart(2, "0")}`);
225
+ }
220
226
  this.#useWide = format === FORMAT_WIDE;
227
+ this.#skip(this.#useWide ? 3 : 1);
221
228
  const inputNodeCount = this.#readWord();
222
229
  const outputNodeCount = this.#readWord();
223
230
  const rootCount = this.#readWord();
@@ -75,8 +75,7 @@ class Serializer {
75
75
  this.#offset += bytes;
76
76
  }
77
77
  #calculateBufferSize() {
78
- let size = 1;
79
- size += this.#useWide ? 12 : 6;
78
+ let size = this.#useWide ? 16 : 8;
80
79
  for (const node of this.#data.inputNodes) {
81
80
  size += this.#wordSize;
82
81
  const edgeCount = Object.keys(node.edges).length;
@@ -92,6 +91,7 @@ class Serializer {
92
91
  }
93
92
  #writeHeader() {
94
93
  this.#writeByte(this.#useWide ? FORMAT_WIDE : FORMAT_COMPACT);
94
+ this.#skip(this.#useWide ? 3 : 1);
95
95
  this.#writeWord(this.#data.inputNodes.length);
96
96
  this.#writeWord(this.#data.outputNodes.length);
97
97
  this.#writeWord(this.#rootKeys.length);
@@ -130,6 +130,9 @@ class Serializer {
130
130
  for (const key of this.#rootKeys) {
131
131
  const root = this.#data.roots[key];
132
132
  const keyIndex = this.#data.strings.indexOf(key);
133
+ if (keyIndex === -1) {
134
+ throw new Error(`Root key "${key}" not found in strings table`);
135
+ }
133
136
  this.#writeWord(keyIndex);
134
137
  this.#writeOptionalWord(root.argsNodeId);
135
138
  this.#writeOptionalWord(root.outputNodeId);
@@ -193,7 +196,11 @@ class Deserializer {
193
196
  }
194
197
  #readHeader() {
195
198
  const format = this.#readByte();
199
+ if (format !== FORMAT_COMPACT && format !== FORMAT_WIDE) {
200
+ throw new Error(`Unknown param graph format: 0x${format.toString(16).padStart(2, "0")}`);
201
+ }
196
202
  this.#useWide = format === FORMAT_WIDE;
203
+ this.#skip(this.#useWide ? 3 : 1);
197
204
  const inputNodeCount = this.#readWord();
198
205
  const outputNodeCount = this.#readWord();
199
206
  const rootCount = this.#readWord();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/param-graph",
3
- "version": "7.4.0-integration-parameterization.10",
3
+ "version": "7.4.0-integration-parameterization.11",
4
4
  "description": "This package is intended for Prisma's internal use",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",