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

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.
@@ -20,84 +20,70 @@
20
20
  * - **Structure goes binary**: Indices, flags, masks benefit from compact encoding
21
21
  * - **Best of both**: Fast parsing + compact size where it matters
22
22
  *
23
- * ## Format Selection
23
+ * ## Variable-Length Encoding
24
24
  *
25
- * Two binary formats based on data size (selected automatically at serialization):
25
+ * All integer values (except fixed-size fields like `scalarMask` and `flags`) use
26
+ * unsigned LEB128 (Little Endian Base 128) variable-length encoding:
26
27
  *
27
- * - **Compact (0x00)**: 16-bit indices, for graphs with ≤65534 items
28
- * - **Wide (0x01)**: 32-bit indices, for larger graphs
28
+ * - Values 0-127: 1 byte
29
+ * - Values 128-16383: 2 bytes
30
+ * - Values 16384-2097151: 3 bytes
31
+ * - And so on...
29
32
  *
30
- * Sentinel values for "none/undefined": 0xFFFF (compact) or 0xFFFFFFFF (wide)
33
+ * Optional values use value+1 encoding: 0 means "none/undefined", N+1 means actual value N.
31
34
  *
32
35
  * ## Binary Blob Layout
33
36
  *
34
- * All multi-byte integers are little-endian.
35
- *
36
37
  * ```
37
38
  * ┌───────────────────────────────────────────────────────────────────┐
38
39
  * │ HEADER │
39
40
  * ├───────────────────────────────────────────────────────────────────┤
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 │
41
+ * │ inputNodeCount: varuint
42
+ * │ outputNodeCount: varuint
43
+ * │ rootCount: varuint
45
44
  * └───────────────────────────────────────────────────────────────────┘
46
45
  *
47
46
  * ┌───────────────────────────────────────────────────────────────────┐
48
47
  * │ INPUT NODES (repeated inputNodeCount times) │
49
48
  * ├───────────────────────────────────────────────────────────────────┤
50
- * │ edgeCount: word Number of edges in this node │
51
- * │ edges[] Edge data (see Input Edge below) │
49
+ * │ edgeCount: varuint
50
+ * │ edges[]
52
51
  * └───────────────────────────────────────────────────────────────────┘
53
52
  *
54
53
  * ┌───────────────────────────────────────────────────────────────────┐
55
- * │ INPUT EDGE (compact: 10 bytes, wide: 20 bytes)
54
+ * │ INPUT EDGE
56
55
  * ├───────────────────────────────────────────────────────────────────┤
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) │
56
+ * │ fieldIndex: varuint
57
+ * │ scalarMask: u16
58
+ * │ childNodeId: varuint (0=none, N+1=actual)
59
+ * │ enumNameIndex: varuint (0=none, N+1=actual)
60
+ * │ flags: u8
64
61
  * └───────────────────────────────────────────────────────────────────┘
65
62
  *
66
63
  * ┌───────────────────────────────────────────────────────────────────┐
67
64
  * │ OUTPUT NODES (repeated outputNodeCount times) │
68
65
  * ├───────────────────────────────────────────────────────────────────┤
69
- * │ edgeCount: word Number of edges in this node │
70
- * │ edges[] Edge data (see Output Edge below) │
66
+ * │ edgeCount: varuint
67
+ * │ edges[]
71
68
  * └───────────────────────────────────────────────────────────────────┘
72
69
  *
73
70
  * ┌───────────────────────────────────────────────────────────────────┐
74
- * │ OUTPUT EDGE (compact: 6 bytes, wide: 12 bytes)
71
+ * │ OUTPUT EDGE
75
72
  * ├───────────────────────────────────────────────────────────────────┤
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)
73
+ * │ fieldIndex: varuint
74
+ * │ argsNodeId: varuint (0=none, N+1=actual)
75
+ * │ outputNodeId: varuint (0=none, N+1=actual)
79
76
  * └───────────────────────────────────────────────────────────────────┘
80
77
  *
81
78
  * ┌───────────────────────────────────────────────────────────────────┐
82
79
  * │ ROOTS (repeated rootCount times) │
83
80
  * ├───────────────────────────────────────────────────────────────────┤
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)
81
+ * │ keyIndex: varuint
82
+ * │ argsNodeId: varuint (0=none, N+1=actual)
83
+ * │ outputNodeId: varuint (0=none, N+1=actual)
87
84
  * └───────────────────────────────────────────────────────────────────┘
88
85
  * ```
89
86
  *
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
87
  * ## Embedding in Generated Client
102
88
  *
103
89
  * ```js
@@ -28,20 +28,22 @@ function serializeParamGraph(data) {
28
28
  function deserializeParamGraph(serialized) {
29
29
  return new Deserializer(serialized).deserialize();
30
30
  }
31
- const FORMAT_COMPACT = 0;
32
- const FORMAT_WIDE = 1;
33
- const NONE_16 = 65535;
34
- const NONE_32 = 4294967295;
35
- const MAX_COMPACT_INDEX = 65534;
36
31
  function encodeBase64url(bytes) {
37
32
  return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64url");
38
33
  }
39
34
  function decodeBase64url(str) {
40
35
  return Buffer.from(str, "base64url");
41
36
  }
37
+ function varuintSize(value) {
38
+ let size = 1;
39
+ while (value >= 128) {
40
+ size++;
41
+ value >>>= 7;
42
+ }
43
+ return size;
44
+ }
42
45
  class Serializer {
43
46
  #data;
44
- #useWide;
45
47
  #buffer;
46
48
  #view;
47
49
  #offset = 0;
@@ -49,13 +51,6 @@ class Serializer {
49
51
  constructor(data) {
50
52
  this.#data = data;
51
53
  this.#rootKeys = Object.keys(data.roots);
52
- const maxIndex = Math.max(
53
- data.strings.length,
54
- data.inputNodes.length,
55
- data.outputNodes.length,
56
- this.#rootKeys.length
57
- );
58
- this.#useWide = maxIndex > MAX_COMPACT_INDEX;
59
54
  const size = this.#calculateBufferSize();
60
55
  this.#buffer = new ArrayBuffer(size);
61
56
  this.#view = new DataView(this.#buffer);
@@ -67,25 +62,18 @@ class Serializer {
67
62
  this.#writeRoots();
68
63
  return {
69
64
  strings: this.#data.strings,
70
- graph: encodeBase64url(new Uint8Array(this.#buffer))
65
+ graph: encodeBase64url(new Uint8Array(this.#buffer, 0, this.#offset))
71
66
  };
72
67
  }
73
- get #wordSize() {
74
- return this.#useWide ? 4 : 2;
75
- }
76
- get #noneValue() {
77
- return this.#useWide ? NONE_32 : NONE_16;
78
- }
79
- #writeWord(value) {
80
- if (this.#useWide) {
81
- this.#view.setUint32(this.#offset, value, true);
82
- } else {
83
- this.#view.setUint16(this.#offset, value, true);
68
+ #writeVaruint(value) {
69
+ while (value >= 128) {
70
+ this.#view.setUint8(this.#offset++, value & 127 | 128);
71
+ value >>>= 7;
84
72
  }
85
- this.#offset += this.#wordSize;
73
+ this.#view.setUint8(this.#offset++, value);
86
74
  }
87
- #writeOptionalWord(value) {
88
- this.#writeWord(value ?? this.#noneValue);
75
+ #writeOptionalVaruint(value) {
76
+ this.#writeVaruint(value === void 0 ? 0 : value + 1);
89
77
  }
90
78
  #writeByte(value) {
91
79
  this.#view.setUint8(this.#offset, value);
@@ -95,58 +83,70 @@ class Serializer {
95
83
  this.#view.setUint16(this.#offset, value, true);
96
84
  this.#offset += 2;
97
85
  }
98
- #skip(bytes) {
99
- this.#offset += bytes;
100
- }
101
86
  #calculateBufferSize() {
102
- let size = this.#useWide ? 16 : 8;
87
+ let size = 0;
88
+ size += varuintSize(this.#data.inputNodes.length);
89
+ size += varuintSize(this.#data.outputNodes.length);
90
+ size += varuintSize(this.#rootKeys.length);
103
91
  for (const node of this.#data.inputNodes) {
104
- size += this.#wordSize;
105
- const edgeCount = Object.keys(node.edges).length;
106
- size += edgeCount * (this.#useWide ? 20 : 10);
92
+ const fieldIndices = Object.keys(node.edges).map(Number);
93
+ size += varuintSize(fieldIndices.length);
94
+ for (const fieldIndex of fieldIndices) {
95
+ const edge = node.edges[fieldIndex];
96
+ size += varuintSize(fieldIndex);
97
+ size += 2;
98
+ size += varuintSize(edge.childNodeId === void 0 ? 0 : edge.childNodeId + 1);
99
+ size += varuintSize(edge.enumNameIndex === void 0 ? 0 : edge.enumNameIndex + 1);
100
+ size += 1;
101
+ }
107
102
  }
108
103
  for (const node of this.#data.outputNodes) {
109
- size += this.#wordSize;
110
- const edgeCount = Object.keys(node.edges).length;
111
- size += edgeCount * (this.#useWide ? 12 : 6);
104
+ const fieldIndices = Object.keys(node.edges).map(Number);
105
+ size += varuintSize(fieldIndices.length);
106
+ for (const fieldIndex of fieldIndices) {
107
+ const edge = node.edges[fieldIndex];
108
+ size += varuintSize(fieldIndex);
109
+ size += varuintSize(edge.argsNodeId === void 0 ? 0 : edge.argsNodeId + 1);
110
+ size += varuintSize(edge.outputNodeId === void 0 ? 0 : edge.outputNodeId + 1);
111
+ }
112
+ }
113
+ for (const key of this.#rootKeys) {
114
+ const root = this.#data.roots[key];
115
+ const keyIndex = this.#data.strings.indexOf(key);
116
+ size += varuintSize(keyIndex);
117
+ size += varuintSize(root.argsNodeId === void 0 ? 0 : root.argsNodeId + 1);
118
+ size += varuintSize(root.outputNodeId === void 0 ? 0 : root.outputNodeId + 1);
112
119
  }
113
- size += this.#rootKeys.length * (this.#useWide ? 12 : 6);
114
120
  return size;
115
121
  }
116
122
  #writeHeader() {
117
- this.#writeByte(this.#useWide ? FORMAT_WIDE : FORMAT_COMPACT);
118
- this.#skip(this.#useWide ? 3 : 1);
119
- this.#writeWord(this.#data.inputNodes.length);
120
- this.#writeWord(this.#data.outputNodes.length);
121
- this.#writeWord(this.#rootKeys.length);
123
+ this.#writeVaruint(this.#data.inputNodes.length);
124
+ this.#writeVaruint(this.#data.outputNodes.length);
125
+ this.#writeVaruint(this.#rootKeys.length);
122
126
  }
123
127
  #writeInputNodes() {
124
128
  for (const node of this.#data.inputNodes) {
125
129
  const fieldIndices = Object.keys(node.edges).map(Number);
126
- this.#writeWord(fieldIndices.length);
130
+ this.#writeVaruint(fieldIndices.length);
127
131
  for (const fieldIndex of fieldIndices) {
128
132
  const edge = node.edges[fieldIndex];
129
- this.#writeWord(fieldIndex);
133
+ this.#writeVaruint(fieldIndex);
130
134
  this.#writeU16(edge.scalarMask ?? 0);
131
- if (this.#useWide) {
132
- this.#skip(2);
133
- }
134
- this.#writeOptionalWord(edge.childNodeId);
135
- this.#writeOptionalWord(edge.enumNameIndex);
135
+ this.#writeOptionalVaruint(edge.childNodeId);
136
+ this.#writeOptionalVaruint(edge.enumNameIndex);
136
137
  this.#writeByte(edge.flags);
137
- this.#skip(this.#useWide ? 3 : 1);
138
138
  }
139
139
  }
140
140
  }
141
141
  #writeOutputNodes() {
142
142
  for (const node of this.#data.outputNodes) {
143
143
  const fieldIndices = Object.keys(node.edges).map(Number);
144
- this.#writeWord(fieldIndices.length);
144
+ this.#writeVaruint(fieldIndices.length);
145
145
  for (const fieldIndex of fieldIndices) {
146
146
  const edge = node.edges[fieldIndex];
147
- this.#writeWord(fieldIndex);
148
- this.#writeOptionalWord(edge.argsNodeId);
149
- this.#writeOptionalWord(edge.outputNodeId);
147
+ this.#writeVaruint(fieldIndex);
148
+ this.#writeOptionalVaruint(edge.argsNodeId);
149
+ this.#writeOptionalVaruint(edge.outputNodeId);
150
150
  }
151
151
  }
152
152
  }
@@ -157,9 +157,9 @@ class Serializer {
157
157
  if (keyIndex === -1) {
158
158
  throw new Error(`Root key "${key}" not found in strings table`);
159
159
  }
160
- this.#writeWord(keyIndex);
161
- this.#writeOptionalWord(root.argsNodeId);
162
- this.#writeOptionalWord(root.outputNodeId);
160
+ this.#writeVaruint(keyIndex);
161
+ this.#writeOptionalVaruint(root.argsNodeId);
162
+ this.#writeOptionalVaruint(root.outputNodeId);
163
163
  }
164
164
  }
165
165
  }
@@ -167,7 +167,6 @@ class Deserializer {
167
167
  #serialized;
168
168
  #view;
169
169
  #offset = 0;
170
- #useWide = false;
171
170
  constructor(serialized) {
172
171
  this.#serialized = serialized;
173
172
  const bytes = decodeBase64url(serialized.graph);
@@ -185,25 +184,20 @@ class Deserializer {
185
184
  roots
186
185
  };
187
186
  }
188
- get #wordSize() {
189
- return this.#useWide ? 4 : 2;
190
- }
191
- get #noneValue() {
192
- return this.#useWide ? NONE_32 : NONE_16;
193
- }
194
- #readWord() {
195
- let value;
196
- if (this.#useWide) {
197
- value = this.#view.getUint32(this.#offset, true);
198
- } else {
199
- value = this.#view.getUint16(this.#offset, true);
200
- }
201
- this.#offset += this.#wordSize;
187
+ #readVaruint() {
188
+ let value = 0;
189
+ let shift = 0;
190
+ let byte;
191
+ do {
192
+ byte = this.#view.getUint8(this.#offset++);
193
+ value |= (byte & 127) << shift;
194
+ shift += 7;
195
+ } while (byte >= 128);
202
196
  return value;
203
197
  }
204
- #readOptionalWord() {
205
- const value = this.#readWord();
206
- return value === this.#noneValue ? void 0 : value;
198
+ #readOptionalVaruint() {
199
+ const value = this.#readVaruint();
200
+ return value === 0 ? void 0 : value - 1;
207
201
  }
208
202
  #readByte() {
209
203
  const value = this.#view.getUint8(this.#offset);
@@ -215,36 +209,23 @@ class Deserializer {
215
209
  this.#offset += 2;
216
210
  return value;
217
211
  }
218
- #skip(bytes) {
219
- this.#offset += bytes;
220
- }
221
212
  #readHeader() {
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
- }
226
- this.#useWide = format === FORMAT_WIDE;
227
- this.#skip(this.#useWide ? 3 : 1);
228
- const inputNodeCount = this.#readWord();
229
- const outputNodeCount = this.#readWord();
230
- const rootCount = this.#readWord();
213
+ const inputNodeCount = this.#readVaruint();
214
+ const outputNodeCount = this.#readVaruint();
215
+ const rootCount = this.#readVaruint();
231
216
  return { inputNodeCount, outputNodeCount, rootCount };
232
217
  }
233
218
  #readInputNodes(count) {
234
219
  const inputNodes = [];
235
220
  for (let i = 0; i < count; i++) {
236
- const edgeCount = this.#readWord();
221
+ const edgeCount = this.#readVaruint();
237
222
  const edges = {};
238
223
  for (let j = 0; j < edgeCount; j++) {
239
- const fieldIndex = this.#readWord();
224
+ const fieldIndex = this.#readVaruint();
240
225
  const scalarMask = this.#readU16();
241
- if (this.#useWide) {
242
- this.#skip(2);
243
- }
244
- const childNodeId = this.#readOptionalWord();
245
- const enumNameIndex = this.#readOptionalWord();
226
+ const childNodeId = this.#readOptionalVaruint();
227
+ const enumNameIndex = this.#readOptionalVaruint();
246
228
  const flags = this.#readByte();
247
- this.#skip(this.#useWide ? 3 : 1);
248
229
  const edge = { flags };
249
230
  if (scalarMask !== 0) edge.scalarMask = scalarMask;
250
231
  if (childNodeId !== void 0) edge.childNodeId = childNodeId;
@@ -258,12 +239,12 @@ class Deserializer {
258
239
  #readOutputNodes(count) {
259
240
  const outputNodes = [];
260
241
  for (let i = 0; i < count; i++) {
261
- const edgeCount = this.#readWord();
242
+ const edgeCount = this.#readVaruint();
262
243
  const edges = {};
263
244
  for (let j = 0; j < edgeCount; j++) {
264
- const fieldIndex = this.#readWord();
265
- const argsNodeId = this.#readOptionalWord();
266
- const outputNodeId = this.#readOptionalWord();
245
+ const fieldIndex = this.#readVaruint();
246
+ const argsNodeId = this.#readOptionalVaruint();
247
+ const outputNodeId = this.#readOptionalVaruint();
267
248
  const edge = {};
268
249
  if (argsNodeId !== void 0) edge.argsNodeId = argsNodeId;
269
250
  if (outputNodeId !== void 0) edge.outputNodeId = outputNodeId;
@@ -276,9 +257,9 @@ class Deserializer {
276
257
  #readRoots(count) {
277
258
  const roots = {};
278
259
  for (let i = 0; i < count; i++) {
279
- const keyIndex = this.#readWord();
280
- const argsNodeId = this.#readOptionalWord();
281
- const outputNodeId = this.#readOptionalWord();
260
+ const keyIndex = this.#readVaruint();
261
+ const argsNodeId = this.#readOptionalVaruint();
262
+ const outputNodeId = this.#readOptionalVaruint();
282
263
  const key = this.#serialized.strings[keyIndex];
283
264
  const root = {};
284
265
  if (argsNodeId !== void 0) root.argsNodeId = argsNodeId;
@@ -4,20 +4,22 @@ function serializeParamGraph(data) {
4
4
  function deserializeParamGraph(serialized) {
5
5
  return new Deserializer(serialized).deserialize();
6
6
  }
7
- const FORMAT_COMPACT = 0;
8
- const FORMAT_WIDE = 1;
9
- const NONE_16 = 65535;
10
- const NONE_32 = 4294967295;
11
- const MAX_COMPACT_INDEX = 65534;
12
7
  function encodeBase64url(bytes) {
13
8
  return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64url");
14
9
  }
15
10
  function decodeBase64url(str) {
16
11
  return Buffer.from(str, "base64url");
17
12
  }
13
+ function varuintSize(value) {
14
+ let size = 1;
15
+ while (value >= 128) {
16
+ size++;
17
+ value >>>= 7;
18
+ }
19
+ return size;
20
+ }
18
21
  class Serializer {
19
22
  #data;
20
- #useWide;
21
23
  #buffer;
22
24
  #view;
23
25
  #offset = 0;
@@ -25,13 +27,6 @@ class Serializer {
25
27
  constructor(data) {
26
28
  this.#data = data;
27
29
  this.#rootKeys = Object.keys(data.roots);
28
- const maxIndex = Math.max(
29
- data.strings.length,
30
- data.inputNodes.length,
31
- data.outputNodes.length,
32
- this.#rootKeys.length
33
- );
34
- this.#useWide = maxIndex > MAX_COMPACT_INDEX;
35
30
  const size = this.#calculateBufferSize();
36
31
  this.#buffer = new ArrayBuffer(size);
37
32
  this.#view = new DataView(this.#buffer);
@@ -43,25 +38,18 @@ class Serializer {
43
38
  this.#writeRoots();
44
39
  return {
45
40
  strings: this.#data.strings,
46
- graph: encodeBase64url(new Uint8Array(this.#buffer))
41
+ graph: encodeBase64url(new Uint8Array(this.#buffer, 0, this.#offset))
47
42
  };
48
43
  }
49
- get #wordSize() {
50
- return this.#useWide ? 4 : 2;
51
- }
52
- get #noneValue() {
53
- return this.#useWide ? NONE_32 : NONE_16;
54
- }
55
- #writeWord(value) {
56
- if (this.#useWide) {
57
- this.#view.setUint32(this.#offset, value, true);
58
- } else {
59
- this.#view.setUint16(this.#offset, value, true);
44
+ #writeVaruint(value) {
45
+ while (value >= 128) {
46
+ this.#view.setUint8(this.#offset++, value & 127 | 128);
47
+ value >>>= 7;
60
48
  }
61
- this.#offset += this.#wordSize;
49
+ this.#view.setUint8(this.#offset++, value);
62
50
  }
63
- #writeOptionalWord(value) {
64
- this.#writeWord(value ?? this.#noneValue);
51
+ #writeOptionalVaruint(value) {
52
+ this.#writeVaruint(value === void 0 ? 0 : value + 1);
65
53
  }
66
54
  #writeByte(value) {
67
55
  this.#view.setUint8(this.#offset, value);
@@ -71,58 +59,70 @@ class Serializer {
71
59
  this.#view.setUint16(this.#offset, value, true);
72
60
  this.#offset += 2;
73
61
  }
74
- #skip(bytes) {
75
- this.#offset += bytes;
76
- }
77
62
  #calculateBufferSize() {
78
- let size = this.#useWide ? 16 : 8;
63
+ let size = 0;
64
+ size += varuintSize(this.#data.inputNodes.length);
65
+ size += varuintSize(this.#data.outputNodes.length);
66
+ size += varuintSize(this.#rootKeys.length);
79
67
  for (const node of this.#data.inputNodes) {
80
- size += this.#wordSize;
81
- const edgeCount = Object.keys(node.edges).length;
82
- size += edgeCount * (this.#useWide ? 20 : 10);
68
+ const fieldIndices = Object.keys(node.edges).map(Number);
69
+ size += varuintSize(fieldIndices.length);
70
+ for (const fieldIndex of fieldIndices) {
71
+ const edge = node.edges[fieldIndex];
72
+ size += varuintSize(fieldIndex);
73
+ size += 2;
74
+ size += varuintSize(edge.childNodeId === void 0 ? 0 : edge.childNodeId + 1);
75
+ size += varuintSize(edge.enumNameIndex === void 0 ? 0 : edge.enumNameIndex + 1);
76
+ size += 1;
77
+ }
83
78
  }
84
79
  for (const node of this.#data.outputNodes) {
85
- size += this.#wordSize;
86
- const edgeCount = Object.keys(node.edges).length;
87
- size += edgeCount * (this.#useWide ? 12 : 6);
80
+ const fieldIndices = Object.keys(node.edges).map(Number);
81
+ size += varuintSize(fieldIndices.length);
82
+ for (const fieldIndex of fieldIndices) {
83
+ const edge = node.edges[fieldIndex];
84
+ size += varuintSize(fieldIndex);
85
+ size += varuintSize(edge.argsNodeId === void 0 ? 0 : edge.argsNodeId + 1);
86
+ size += varuintSize(edge.outputNodeId === void 0 ? 0 : edge.outputNodeId + 1);
87
+ }
88
+ }
89
+ for (const key of this.#rootKeys) {
90
+ const root = this.#data.roots[key];
91
+ const keyIndex = this.#data.strings.indexOf(key);
92
+ size += varuintSize(keyIndex);
93
+ size += varuintSize(root.argsNodeId === void 0 ? 0 : root.argsNodeId + 1);
94
+ size += varuintSize(root.outputNodeId === void 0 ? 0 : root.outputNodeId + 1);
88
95
  }
89
- size += this.#rootKeys.length * (this.#useWide ? 12 : 6);
90
96
  return size;
91
97
  }
92
98
  #writeHeader() {
93
- this.#writeByte(this.#useWide ? FORMAT_WIDE : FORMAT_COMPACT);
94
- this.#skip(this.#useWide ? 3 : 1);
95
- this.#writeWord(this.#data.inputNodes.length);
96
- this.#writeWord(this.#data.outputNodes.length);
97
- this.#writeWord(this.#rootKeys.length);
99
+ this.#writeVaruint(this.#data.inputNodes.length);
100
+ this.#writeVaruint(this.#data.outputNodes.length);
101
+ this.#writeVaruint(this.#rootKeys.length);
98
102
  }
99
103
  #writeInputNodes() {
100
104
  for (const node of this.#data.inputNodes) {
101
105
  const fieldIndices = Object.keys(node.edges).map(Number);
102
- this.#writeWord(fieldIndices.length);
106
+ this.#writeVaruint(fieldIndices.length);
103
107
  for (const fieldIndex of fieldIndices) {
104
108
  const edge = node.edges[fieldIndex];
105
- this.#writeWord(fieldIndex);
109
+ this.#writeVaruint(fieldIndex);
106
110
  this.#writeU16(edge.scalarMask ?? 0);
107
- if (this.#useWide) {
108
- this.#skip(2);
109
- }
110
- this.#writeOptionalWord(edge.childNodeId);
111
- this.#writeOptionalWord(edge.enumNameIndex);
111
+ this.#writeOptionalVaruint(edge.childNodeId);
112
+ this.#writeOptionalVaruint(edge.enumNameIndex);
112
113
  this.#writeByte(edge.flags);
113
- this.#skip(this.#useWide ? 3 : 1);
114
114
  }
115
115
  }
116
116
  }
117
117
  #writeOutputNodes() {
118
118
  for (const node of this.#data.outputNodes) {
119
119
  const fieldIndices = Object.keys(node.edges).map(Number);
120
- this.#writeWord(fieldIndices.length);
120
+ this.#writeVaruint(fieldIndices.length);
121
121
  for (const fieldIndex of fieldIndices) {
122
122
  const edge = node.edges[fieldIndex];
123
- this.#writeWord(fieldIndex);
124
- this.#writeOptionalWord(edge.argsNodeId);
125
- this.#writeOptionalWord(edge.outputNodeId);
123
+ this.#writeVaruint(fieldIndex);
124
+ this.#writeOptionalVaruint(edge.argsNodeId);
125
+ this.#writeOptionalVaruint(edge.outputNodeId);
126
126
  }
127
127
  }
128
128
  }
@@ -133,9 +133,9 @@ class Serializer {
133
133
  if (keyIndex === -1) {
134
134
  throw new Error(`Root key "${key}" not found in strings table`);
135
135
  }
136
- this.#writeWord(keyIndex);
137
- this.#writeOptionalWord(root.argsNodeId);
138
- this.#writeOptionalWord(root.outputNodeId);
136
+ this.#writeVaruint(keyIndex);
137
+ this.#writeOptionalVaruint(root.argsNodeId);
138
+ this.#writeOptionalVaruint(root.outputNodeId);
139
139
  }
140
140
  }
141
141
  }
@@ -143,7 +143,6 @@ class Deserializer {
143
143
  #serialized;
144
144
  #view;
145
145
  #offset = 0;
146
- #useWide = false;
147
146
  constructor(serialized) {
148
147
  this.#serialized = serialized;
149
148
  const bytes = decodeBase64url(serialized.graph);
@@ -161,25 +160,20 @@ class Deserializer {
161
160
  roots
162
161
  };
163
162
  }
164
- get #wordSize() {
165
- return this.#useWide ? 4 : 2;
166
- }
167
- get #noneValue() {
168
- return this.#useWide ? NONE_32 : NONE_16;
169
- }
170
- #readWord() {
171
- let value;
172
- if (this.#useWide) {
173
- value = this.#view.getUint32(this.#offset, true);
174
- } else {
175
- value = this.#view.getUint16(this.#offset, true);
176
- }
177
- this.#offset += this.#wordSize;
163
+ #readVaruint() {
164
+ let value = 0;
165
+ let shift = 0;
166
+ let byte;
167
+ do {
168
+ byte = this.#view.getUint8(this.#offset++);
169
+ value |= (byte & 127) << shift;
170
+ shift += 7;
171
+ } while (byte >= 128);
178
172
  return value;
179
173
  }
180
- #readOptionalWord() {
181
- const value = this.#readWord();
182
- return value === this.#noneValue ? void 0 : value;
174
+ #readOptionalVaruint() {
175
+ const value = this.#readVaruint();
176
+ return value === 0 ? void 0 : value - 1;
183
177
  }
184
178
  #readByte() {
185
179
  const value = this.#view.getUint8(this.#offset);
@@ -191,36 +185,23 @@ class Deserializer {
191
185
  this.#offset += 2;
192
186
  return value;
193
187
  }
194
- #skip(bytes) {
195
- this.#offset += bytes;
196
- }
197
188
  #readHeader() {
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
- }
202
- this.#useWide = format === FORMAT_WIDE;
203
- this.#skip(this.#useWide ? 3 : 1);
204
- const inputNodeCount = this.#readWord();
205
- const outputNodeCount = this.#readWord();
206
- const rootCount = this.#readWord();
189
+ const inputNodeCount = this.#readVaruint();
190
+ const outputNodeCount = this.#readVaruint();
191
+ const rootCount = this.#readVaruint();
207
192
  return { inputNodeCount, outputNodeCount, rootCount };
208
193
  }
209
194
  #readInputNodes(count) {
210
195
  const inputNodes = [];
211
196
  for (let i = 0; i < count; i++) {
212
- const edgeCount = this.#readWord();
197
+ const edgeCount = this.#readVaruint();
213
198
  const edges = {};
214
199
  for (let j = 0; j < edgeCount; j++) {
215
- const fieldIndex = this.#readWord();
200
+ const fieldIndex = this.#readVaruint();
216
201
  const scalarMask = this.#readU16();
217
- if (this.#useWide) {
218
- this.#skip(2);
219
- }
220
- const childNodeId = this.#readOptionalWord();
221
- const enumNameIndex = this.#readOptionalWord();
202
+ const childNodeId = this.#readOptionalVaruint();
203
+ const enumNameIndex = this.#readOptionalVaruint();
222
204
  const flags = this.#readByte();
223
- this.#skip(this.#useWide ? 3 : 1);
224
205
  const edge = { flags };
225
206
  if (scalarMask !== 0) edge.scalarMask = scalarMask;
226
207
  if (childNodeId !== void 0) edge.childNodeId = childNodeId;
@@ -234,12 +215,12 @@ class Deserializer {
234
215
  #readOutputNodes(count) {
235
216
  const outputNodes = [];
236
217
  for (let i = 0; i < count; i++) {
237
- const edgeCount = this.#readWord();
218
+ const edgeCount = this.#readVaruint();
238
219
  const edges = {};
239
220
  for (let j = 0; j < edgeCount; j++) {
240
- const fieldIndex = this.#readWord();
241
- const argsNodeId = this.#readOptionalWord();
242
- const outputNodeId = this.#readOptionalWord();
221
+ const fieldIndex = this.#readVaruint();
222
+ const argsNodeId = this.#readOptionalVaruint();
223
+ const outputNodeId = this.#readOptionalVaruint();
243
224
  const edge = {};
244
225
  if (argsNodeId !== void 0) edge.argsNodeId = argsNodeId;
245
226
  if (outputNodeId !== void 0) edge.outputNodeId = outputNodeId;
@@ -252,9 +233,9 @@ class Deserializer {
252
233
  #readRoots(count) {
253
234
  const roots = {};
254
235
  for (let i = 0; i < count; i++) {
255
- const keyIndex = this.#readWord();
256
- const argsNodeId = this.#readOptionalWord();
257
- const outputNodeId = this.#readOptionalWord();
236
+ const keyIndex = this.#readVaruint();
237
+ const argsNodeId = this.#readOptionalVaruint();
238
+ const outputNodeId = this.#readOptionalVaruint();
258
239
  const key = this.#serialized.strings[keyIndex];
259
240
  const root = {};
260
241
  if (argsNodeId !== void 0) root.argsNodeId = argsNodeId;
@@ -83,4 +83,105 @@ var import_serialization = require("./serialization");
83
83
  const serialized = (0, import_serialization.serializeParamGraph)(data);
84
84
  (0, import_vitest.expect)(serialized.graph).toMatch(/^[A-Za-z0-9_-]+$/);
85
85
  });
86
+ (0, import_vitest.test)("handles large indices requiring multi-byte varints (128-16383)", () => {
87
+ const strings = Array.from({ length: 200 }, (_, i) => `field${i}`);
88
+ const data = {
89
+ strings,
90
+ inputNodes: [
91
+ {
92
+ edges: {
93
+ // Use indices that require 2-byte encoding (128+)
94
+ 128: { flags: 1, childNodeId: 150 },
95
+ 150: { flags: 2, enumNameIndex: 180 }
96
+ }
97
+ }
98
+ ],
99
+ outputNodes: [{ edges: { 199: { argsNodeId: 0, outputNodeId: 0 } } }],
100
+ roots: { field0: { argsNodeId: 0, outputNodeId: 0 } }
101
+ };
102
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
103
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
104
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[128].flags).toBe(1);
105
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[128].childNodeId).toBe(150);
106
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[150].flags).toBe(2);
107
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[150].enumNameIndex).toBe(180);
108
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[199].argsNodeId).toBe(0);
109
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[199].outputNodeId).toBe(0);
110
+ });
111
+ (0, import_vitest.test)("handles very large indices requiring 3-byte varints (16384+)", () => {
112
+ const strings = Array.from({ length: 17e3 }, (_, i) => `f${i}`);
113
+ const data = {
114
+ strings,
115
+ inputNodes: [
116
+ {
117
+ edges: {
118
+ 16384: { flags: 1, childNodeId: 16500 }
119
+ }
120
+ }
121
+ ],
122
+ outputNodes: [{ edges: { 16999: { argsNodeId: 0, outputNodeId: 0 } } }],
123
+ roots: { f0: { argsNodeId: 0, outputNodeId: 0 } }
124
+ };
125
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
126
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
127
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[16384].flags).toBe(1);
128
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[16384].childNodeId).toBe(16500);
129
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[16999].argsNodeId).toBe(0);
130
+ });
131
+ (0, import_vitest.test)("varint boundary values encode and decode correctly", () => {
132
+ const boundaryValues = [0, 1, 127, 128, 16383, 16384];
133
+ for (const value of boundaryValues) {
134
+ const strings = Array.from({ length: Math.max(value + 1, 2) }, (_, i) => `s${i}`);
135
+ const data = {
136
+ strings,
137
+ inputNodes: [{ edges: { [value]: { flags: 0 } } }],
138
+ outputNodes: [],
139
+ roots: { s0: {} }
140
+ };
141
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
142
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
143
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[value]).toBeDefined();
144
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[value].flags).toBe(0);
145
+ }
146
+ });
147
+ (0, import_vitest.test)("optional value 0 is correctly distinguished from undefined", () => {
148
+ const data = {
149
+ strings: ["root", "field"],
150
+ inputNodes: [
151
+ { edges: { 1: { flags: 0, childNodeId: 0 } } }
152
+ // childNodeId = 0 (not undefined)
153
+ ],
154
+ outputNodes: [
155
+ { edges: { 1: { argsNodeId: 0, outputNodeId: 0 } } }
156
+ // both are 0 (not undefined)
157
+ ],
158
+ roots: { root: { argsNodeId: 0, outputNodeId: 0 } }
159
+ // both are 0 (not undefined)
160
+ };
161
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
162
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
163
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[1].childNodeId).toBe(0);
164
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[1].argsNodeId).toBe(0);
165
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[1].outputNodeId).toBe(0);
166
+ (0, import_vitest.expect)(deserialized.roots["root"].argsNodeId).toBe(0);
167
+ (0, import_vitest.expect)(deserialized.roots["root"].outputNodeId).toBe(0);
168
+ });
169
+ (0, import_vitest.test)("serialization produces more compact output with varint encoding", () => {
170
+ const data = {
171
+ strings: ["findMany", "create", "update", "where", "data", "id", "name", "email"],
172
+ inputNodes: [
173
+ { edges: { 3: { flags: 1, scalarMask: 1 }, 4: { flags: 0, scalarMask: 2 } } },
174
+ { edges: { 5: { flags: 2 }, 6: { flags: 2 }, 7: { flags: 2 } } }
175
+ ],
176
+ outputNodes: [{ edges: { 5: { argsNodeId: 0 }, 6: {}, 7: {} } }],
177
+ roots: {
178
+ findMany: { argsNodeId: 0, outputNodeId: 0 },
179
+ create: { argsNodeId: 1, outputNodeId: 0 },
180
+ update: { argsNodeId: 0, outputNodeId: 0 }
181
+ }
182
+ };
183
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
184
+ const binarySize = Buffer.from(serialized.graph, "base64url").length;
185
+ (0, import_vitest.expect)(binarySize).toBeLessThan(80);
186
+ });
86
187
  });
@@ -82,4 +82,105 @@ describe("param-graph serialization", () => {
82
82
  const serialized = serializeParamGraph(data);
83
83
  expect(serialized.graph).toMatch(/^[A-Za-z0-9_-]+$/);
84
84
  });
85
+ test("handles large indices requiring multi-byte varints (128-16383)", () => {
86
+ const strings = Array.from({ length: 200 }, (_, i) => `field${i}`);
87
+ const data = {
88
+ strings,
89
+ inputNodes: [
90
+ {
91
+ edges: {
92
+ // Use indices that require 2-byte encoding (128+)
93
+ 128: { flags: 1, childNodeId: 150 },
94
+ 150: { flags: 2, enumNameIndex: 180 }
95
+ }
96
+ }
97
+ ],
98
+ outputNodes: [{ edges: { 199: { argsNodeId: 0, outputNodeId: 0 } } }],
99
+ roots: { field0: { argsNodeId: 0, outputNodeId: 0 } }
100
+ };
101
+ const serialized = serializeParamGraph(data);
102
+ const deserialized = deserializeParamGraph(serialized);
103
+ expect(deserialized.inputNodes[0].edges[128].flags).toBe(1);
104
+ expect(deserialized.inputNodes[0].edges[128].childNodeId).toBe(150);
105
+ expect(deserialized.inputNodes[0].edges[150].flags).toBe(2);
106
+ expect(deserialized.inputNodes[0].edges[150].enumNameIndex).toBe(180);
107
+ expect(deserialized.outputNodes[0].edges[199].argsNodeId).toBe(0);
108
+ expect(deserialized.outputNodes[0].edges[199].outputNodeId).toBe(0);
109
+ });
110
+ test("handles very large indices requiring 3-byte varints (16384+)", () => {
111
+ const strings = Array.from({ length: 17e3 }, (_, i) => `f${i}`);
112
+ const data = {
113
+ strings,
114
+ inputNodes: [
115
+ {
116
+ edges: {
117
+ 16384: { flags: 1, childNodeId: 16500 }
118
+ }
119
+ }
120
+ ],
121
+ outputNodes: [{ edges: { 16999: { argsNodeId: 0, outputNodeId: 0 } } }],
122
+ roots: { f0: { argsNodeId: 0, outputNodeId: 0 } }
123
+ };
124
+ const serialized = serializeParamGraph(data);
125
+ const deserialized = deserializeParamGraph(serialized);
126
+ expect(deserialized.inputNodes[0].edges[16384].flags).toBe(1);
127
+ expect(deserialized.inputNodes[0].edges[16384].childNodeId).toBe(16500);
128
+ expect(deserialized.outputNodes[0].edges[16999].argsNodeId).toBe(0);
129
+ });
130
+ test("varint boundary values encode and decode correctly", () => {
131
+ const boundaryValues = [0, 1, 127, 128, 16383, 16384];
132
+ for (const value of boundaryValues) {
133
+ const strings = Array.from({ length: Math.max(value + 1, 2) }, (_, i) => `s${i}`);
134
+ const data = {
135
+ strings,
136
+ inputNodes: [{ edges: { [value]: { flags: 0 } } }],
137
+ outputNodes: [],
138
+ roots: { s0: {} }
139
+ };
140
+ const serialized = serializeParamGraph(data);
141
+ const deserialized = deserializeParamGraph(serialized);
142
+ expect(deserialized.inputNodes[0].edges[value]).toBeDefined();
143
+ expect(deserialized.inputNodes[0].edges[value].flags).toBe(0);
144
+ }
145
+ });
146
+ test("optional value 0 is correctly distinguished from undefined", () => {
147
+ const data = {
148
+ strings: ["root", "field"],
149
+ inputNodes: [
150
+ { edges: { 1: { flags: 0, childNodeId: 0 } } }
151
+ // childNodeId = 0 (not undefined)
152
+ ],
153
+ outputNodes: [
154
+ { edges: { 1: { argsNodeId: 0, outputNodeId: 0 } } }
155
+ // both are 0 (not undefined)
156
+ ],
157
+ roots: { root: { argsNodeId: 0, outputNodeId: 0 } }
158
+ // both are 0 (not undefined)
159
+ };
160
+ const serialized = serializeParamGraph(data);
161
+ const deserialized = deserializeParamGraph(serialized);
162
+ expect(deserialized.inputNodes[0].edges[1].childNodeId).toBe(0);
163
+ expect(deserialized.outputNodes[0].edges[1].argsNodeId).toBe(0);
164
+ expect(deserialized.outputNodes[0].edges[1].outputNodeId).toBe(0);
165
+ expect(deserialized.roots["root"].argsNodeId).toBe(0);
166
+ expect(deserialized.roots["root"].outputNodeId).toBe(0);
167
+ });
168
+ test("serialization produces more compact output with varint encoding", () => {
169
+ const data = {
170
+ strings: ["findMany", "create", "update", "where", "data", "id", "name", "email"],
171
+ inputNodes: [
172
+ { edges: { 3: { flags: 1, scalarMask: 1 }, 4: { flags: 0, scalarMask: 2 } } },
173
+ { edges: { 5: { flags: 2 }, 6: { flags: 2 }, 7: { flags: 2 } } }
174
+ ],
175
+ outputNodes: [{ edges: { 5: { argsNodeId: 0 }, 6: {}, 7: {} } }],
176
+ roots: {
177
+ findMany: { argsNodeId: 0, outputNodeId: 0 },
178
+ create: { argsNodeId: 1, outputNodeId: 0 },
179
+ update: { argsNodeId: 0, outputNodeId: 0 }
180
+ }
181
+ };
182
+ const serialized = serializeParamGraph(data);
183
+ const binarySize = Buffer.from(serialized.graph, "base64url").length;
184
+ expect(binarySize).toBeLessThan(80);
185
+ });
85
186
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/param-graph",
3
- "version": "7.4.0-integration-parameterization.11",
3
+ "version": "7.4.0-integration-parameterization.12",
4
4
  "description": "This package is intended for Prisma's internal use",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",