@hypequery/protocol 0.1.0 → 0.2.0

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.
Files changed (101) hide show
  1. package/README.md +50 -9
  2. package/dist/deployments/errors.d.ts +8 -0
  3. package/dist/deployments/errors.d.ts.map +1 -0
  4. package/dist/deployments/errors.js +13 -0
  5. package/dist/deployments/index.d.ts +5 -0
  6. package/dist/deployments/index.d.ts.map +1 -0
  7. package/dist/deployments/index.js +3 -0
  8. package/dist/deployments/limits.d.ts +4 -0
  9. package/dist/deployments/limits.d.ts.map +1 -0
  10. package/dist/deployments/limits.js +23 -0
  11. package/dist/deployments/types.d.ts +148 -0
  12. package/dist/deployments/types.d.ts.map +1 -0
  13. package/dist/deployments/types.js +1 -0
  14. package/dist/deployments/validate.d.ts +4 -0
  15. package/dist/deployments/validate.d.ts.map +1 -0
  16. package/dist/deployments/validate.js +527 -0
  17. package/dist/expressions/errors.d.ts +8 -0
  18. package/dist/expressions/errors.d.ts.map +1 -0
  19. package/dist/expressions/errors.js +13 -0
  20. package/dist/expressions/index.d.ts +5 -0
  21. package/dist/expressions/index.d.ts.map +1 -0
  22. package/dist/expressions/index.js +3 -0
  23. package/dist/expressions/limits.d.ts +4 -0
  24. package/dist/expressions/limits.d.ts.map +1 -0
  25. package/dist/expressions/limits.js +17 -0
  26. package/dist/expressions/types.d.ts +77 -0
  27. package/dist/expressions/types.d.ts.map +1 -0
  28. package/dist/expressions/types.js +1 -0
  29. package/dist/expressions/validate.d.ts +4 -0
  30. package/dist/expressions/validate.d.ts.map +1 -0
  31. package/dist/expressions/validate.js +338 -0
  32. package/dist/identifiers/identifiers.d.ts +14 -0
  33. package/dist/identifiers/identifiers.d.ts.map +1 -0
  34. package/dist/identifiers/identifiers.js +87 -0
  35. package/dist/identifiers/index.d.ts +4 -0
  36. package/dist/identifiers/index.d.ts.map +1 -0
  37. package/dist/identifiers/index.js +2 -0
  38. package/dist/identifiers/types.d.ts +16 -0
  39. package/dist/identifiers/types.d.ts.map +1 -0
  40. package/dist/identifiers/types.js +5 -0
  41. package/dist/index.d.ts +12 -8
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +6 -1
  44. package/dist/query-implementations/errors.d.ts +8 -0
  45. package/dist/query-implementations/errors.d.ts.map +1 -0
  46. package/dist/query-implementations/errors.js +13 -0
  47. package/dist/query-implementations/index.d.ts +5 -0
  48. package/dist/query-implementations/index.d.ts.map +1 -0
  49. package/dist/query-implementations/index.js +3 -0
  50. package/dist/query-implementations/limits.d.ts +4 -0
  51. package/dist/query-implementations/limits.d.ts.map +1 -0
  52. package/dist/query-implementations/limits.js +22 -0
  53. package/dist/query-implementations/types.d.ts +57 -0
  54. package/dist/query-implementations/types.d.ts.map +1 -0
  55. package/dist/query-implementations/types.js +1 -0
  56. package/dist/query-implementations/validate.d.ts +4 -0
  57. package/dist/query-implementations/validate.d.ts.map +1 -0
  58. package/dist/query-implementations/validate.js +248 -0
  59. package/dist/schemas/errors.d.ts +8 -0
  60. package/dist/schemas/errors.d.ts.map +1 -0
  61. package/dist/schemas/errors.js +13 -0
  62. package/dist/schemas/index.d.ts +5 -0
  63. package/dist/schemas/index.d.ts.map +1 -0
  64. package/dist/schemas/index.js +3 -0
  65. package/dist/schemas/limits.d.ts +4 -0
  66. package/dist/schemas/limits.d.ts.map +1 -0
  67. package/dist/schemas/limits.js +20 -0
  68. package/dist/schemas/types.d.ts +60 -0
  69. package/dist/schemas/types.d.ts.map +1 -0
  70. package/dist/schemas/types.js +1 -0
  71. package/dist/schemas/validate.d.ts +3 -0
  72. package/dist/schemas/validate.d.ts.map +1 -0
  73. package/dist/schemas/validate.js +391 -0
  74. package/dist/values/codec.d.ts +13 -0
  75. package/dist/values/codec.d.ts.map +1 -0
  76. package/dist/values/codec.js +39 -0
  77. package/dist/values/errors.d.ts +8 -0
  78. package/dist/values/errors.d.ts.map +1 -0
  79. package/dist/values/errors.js +13 -0
  80. package/dist/values/index.d.ts +7 -0
  81. package/dist/values/index.d.ts.map +1 -0
  82. package/dist/values/index.js +4 -0
  83. package/dist/values/jcs.d.ts +2 -0
  84. package/dist/values/jcs.d.ts.map +1 -0
  85. package/dist/values/jcs.js +23 -0
  86. package/dist/values/limits.d.ts +4 -0
  87. package/dist/values/limits.d.ts.map +1 -0
  88. package/dist/values/limits.js +26 -0
  89. package/dist/values/parser.d.ts +3 -0
  90. package/dist/values/parser.d.ts.map +1 -0
  91. package/dist/values/parser.js +254 -0
  92. package/dist/values/snapshot.d.ts +3 -0
  93. package/dist/values/snapshot.d.ts.map +1 -0
  94. package/dist/values/snapshot.js +96 -0
  95. package/dist/values/types.d.ts +78 -0
  96. package/dist/values/types.d.ts.map +1 -0
  97. package/dist/values/types.js +1 -0
  98. package/dist/values/validate.d.ts +7 -0
  99. package/dist/values/validate.d.ts.map +1 -0
  100. package/dist/values/validate.js +332 -0
  101. package/package.json +4 -1
@@ -0,0 +1,332 @@
1
+ import { valueError } from './errors.js';
2
+ import { resolveLimits } from './limits.js';
3
+ import { snapshotPlainData } from './snapshot.js';
4
+ const INTEGER_BITS = new Set([8, 16, 32, 64, 128, 256]);
5
+ const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
6
+ const textEncoder = new TextEncoder();
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ function requireRecord(value, path) {
11
+ if (!isRecord(value))
12
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
13
+ return value;
14
+ }
15
+ function assertExactFields(value, fields, path) {
16
+ const allowed = new Set(fields);
17
+ for (const key of Object.keys(value)) {
18
+ if (!allowed.has(key))
19
+ valueError('HQ_VALUE_UNKNOWN_FIELD', `${path}.${key}`);
20
+ }
21
+ for (const field of fields) {
22
+ if (!Object.hasOwn(value, field)) {
23
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.${field}`);
24
+ }
25
+ }
26
+ }
27
+ function requireString(value, path) {
28
+ if (typeof value !== 'string')
29
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
30
+ return value;
31
+ }
32
+ function requireBoolean(value, path) {
33
+ if (typeof value !== 'boolean')
34
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
35
+ return value;
36
+ }
37
+ function requireMetadataInteger(value, path) {
38
+ if (!Number.isSafeInteger(value) || Object.is(value, -0)) {
39
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
40
+ }
41
+ return value;
42
+ }
43
+ function validateUnicode(value, path, maxBytes) {
44
+ for (let index = 0; index < value.length; index += 1) {
45
+ const code = value.charCodeAt(index);
46
+ if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
47
+ valueError('HQ_VALUE_CONTROL_CHARACTER', path);
48
+ }
49
+ if (code >= 0xd800 && code <= 0xdbff) {
50
+ const low = value.charCodeAt(index + 1);
51
+ if (low < 0xdc00 || low > 0xdfff) {
52
+ valueError('HQ_VALUE_INVALID_UNICODE', path);
53
+ }
54
+ index += 1;
55
+ }
56
+ else if (code >= 0xdc00 && code <= 0xdfff) {
57
+ valueError('HQ_VALUE_INVALID_UNICODE', path);
58
+ }
59
+ }
60
+ if (textEncoder.encode(value).byteLength > maxBytes) {
61
+ valueError('HQ_VALUE_TOO_LARGE', path);
62
+ }
63
+ }
64
+ function validateCanonicalIntegerString(value, path) {
65
+ if (!/^(?:0|-[1-9]\d*|[1-9]\d*)$/.test(value)) {
66
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
67
+ }
68
+ return BigInt(value);
69
+ }
70
+ function validateIntegerTag(tag, path, declaredClickHouseType) {
71
+ assertExactFields(tag, ['bits', 'signed', 'type', 'value', 'version'], path);
72
+ const bits = requireMetadataInteger(tag.bits, `${path}.bits`);
73
+ if (!INTEGER_BITS.has(bits))
74
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.bits`);
75
+ const signed = requireBoolean(tag.signed, `${path}.signed`);
76
+ const integer = validateCanonicalIntegerString(requireString(tag.value, `${path}.value`), `${path}.value`);
77
+ const width = BigInt(bits);
78
+ const minimum = signed ? -(1n << (width - 1n)) : 0n;
79
+ const maximum = signed ? (1n << (width - 1n)) - 1n : (1n << width) - 1n;
80
+ if (integer < minimum || integer > maximum) {
81
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.value`);
82
+ }
83
+ const actualClickHouseType = `${signed ? '' : 'U'}Int${bits}`;
84
+ if (declaredClickHouseType && declaredClickHouseType !== actualClickHouseType) {
85
+ valueError('HQ_VALUE_TYPE_MISMATCH', path);
86
+ }
87
+ }
88
+ function validateDecimalTag(tag, path) {
89
+ assertExactFields(tag, ['coefficient', 'precision', 'scale', 'type', 'version'], path);
90
+ const precision = requireMetadataInteger(tag.precision, `${path}.precision`);
91
+ const scale = requireMetadataInteger(tag.scale, `${path}.scale`);
92
+ if (precision < 1 || precision > 76 || scale < 0 || scale > precision) {
93
+ valueError('HQ_VALUE_OUT_OF_RANGE', path);
94
+ }
95
+ const coefficient = requireString(tag.coefficient, `${path}.coefficient`);
96
+ validateCanonicalIntegerString(coefficient, `${path}.coefficient`);
97
+ if (coefficient.replace('-', '').length > precision) {
98
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.coefficient`);
99
+ }
100
+ }
101
+ function parseDate(value, path) {
102
+ const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
103
+ if (!match)
104
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
105
+ const year = Number(match[1]);
106
+ const month = Number(match[2]);
107
+ const day = Number(match[3]);
108
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
109
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
110
+ if (month < 1 || month > 12 || day < 1 || day > days[month - 1]) {
111
+ valueError('HQ_VALUE_INVALID_FORMAT', path);
112
+ }
113
+ return { year, month, day };
114
+ }
115
+ function validateDateTag(tag, path) {
116
+ assertExactFields(tag, ['clickhouseType', 'type', 'value', 'version'], path);
117
+ const clickhouseType = requireString(tag.clickhouseType, `${path}.clickhouseType`);
118
+ if (clickhouseType !== 'Date' && clickhouseType !== 'Date32') {
119
+ valueError('HQ_VALUE_TYPE_MISMATCH', `${path}.clickhouseType`);
120
+ }
121
+ const value = requireString(tag.value, `${path}.value`);
122
+ parseDate(value, `${path}.value`);
123
+ const minimum = clickhouseType === 'Date' ? '1970-01-01' : '1900-01-01';
124
+ const maximum = clickhouseType === 'Date' ? '2149-06-06' : '2299-12-31';
125
+ if (value < minimum || value > maximum) {
126
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.value`);
127
+ }
128
+ }
129
+ function validateDatetimeTag(tag, path) {
130
+ assertExactFields(tag, ['clickhouseType', 'precision', 'timezone', 'type', 'value', 'version'], path);
131
+ const clickhouseType = requireString(tag.clickhouseType, `${path}.clickhouseType`);
132
+ if (clickhouseType !== 'DateTime' && clickhouseType !== 'DateTime64') {
133
+ valueError('HQ_VALUE_TYPE_MISMATCH', `${path}.clickhouseType`);
134
+ }
135
+ const precision = requireMetadataInteger(tag.precision, `${path}.precision`);
136
+ if ((clickhouseType === 'DateTime' && precision !== 0)
137
+ || (clickhouseType === 'DateTime64' && (precision < 0 || precision > 9))) {
138
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.precision`);
139
+ }
140
+ const timezone = requireString(tag.timezone, `${path}.timezone`);
141
+ validateUnicode(timezone, `${path}.timezone`, 64);
142
+ if (timezone !== 'UTC' && !/^[A-Za-z_]+(?:\/[A-Za-z0-9_+-]+)+$/.test(timezone)) {
143
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.timezone`);
144
+ }
145
+ const value = requireString(tag.value, `${path}.value`);
146
+ const fraction = precision === 0 ? '' : `\\.(\\d{${precision}})`;
147
+ const match = value.match(new RegExp(`^(\\d{4}-\\d{2}-\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})${fraction}Z$`));
148
+ if (!match)
149
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.value`);
150
+ parseDate(match[1], `${path}.value`);
151
+ const hour = Number(match[2]);
152
+ const minute = Number(match[3]);
153
+ const second = Number(match[4]);
154
+ if (hour > 23 || minute > 59 || second > 59) {
155
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.value`);
156
+ }
157
+ if (clickhouseType === 'DateTime') {
158
+ if (value < '1970-01-01T00:00:00Z' || value > '2106-02-07T06:28:15Z') {
159
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.value`);
160
+ }
161
+ return;
162
+ }
163
+ const minimum = `1900-01-01T00:00:00${precision === 0 ? '' : `.${'0'.repeat(precision)}`}Z`;
164
+ const maximum = precision === 9
165
+ ? '2262-04-11T23:47:16.854775807Z'
166
+ : `2299-12-31T23:59:59${precision === 0 ? '' : `.${'9'.repeat(precision)}`}Z`;
167
+ if (value < minimum || value > maximum) {
168
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.value`);
169
+ }
170
+ }
171
+ function validateUuidTag(tag, path) {
172
+ assertExactFields(tag, ['type', 'value', 'version'], path);
173
+ const value = requireString(tag.value, `${path}.value`);
174
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value)) {
175
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.value`);
176
+ }
177
+ }
178
+ function validateBytesTag(tag, path, limits) {
179
+ assertExactFields(tag, ['encoding', 'type', 'value', 'version'], path);
180
+ if (tag.encoding !== 'base64url') {
181
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.encoding`);
182
+ }
183
+ const value = requireString(tag.value, `${path}.value`);
184
+ if (!/^[A-Za-z0-9_-]*$/.test(value) || value.length % 4 === 1) {
185
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.value`);
186
+ }
187
+ const remainder = value.length % 4;
188
+ if (remainder === 2 && (BASE64URL_ALPHABET.indexOf(value.at(-1) ?? '') & 0x0f) !== 0) {
189
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.value`);
190
+ }
191
+ if (remainder === 3 && (BASE64URL_ALPHABET.indexOf(value.at(-1) ?? '') & 0x03) !== 0) {
192
+ valueError('HQ_VALUE_INVALID_FORMAT', `${path}.value`);
193
+ }
194
+ if (Math.floor(value.length * 6 / 8) > limits.maxDecodedBytes) {
195
+ valueError('HQ_VALUE_TOO_LARGE', `${path}.value`);
196
+ }
197
+ }
198
+ function validateEnumTag(tag, path, limits) {
199
+ assertExactFields(tag, ['bits', 'code', 'label', 'type', 'version'], path);
200
+ const bits = requireMetadataInteger(tag.bits, `${path}.bits`);
201
+ if (bits !== 8 && bits !== 16)
202
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.bits`);
203
+ const code = requireMetadataInteger(tag.code, `${path}.code`);
204
+ const minimum = -(2 ** (bits - 1));
205
+ const maximum = 2 ** (bits - 1) - 1;
206
+ if (code < minimum || code > maximum)
207
+ valueError('HQ_VALUE_OUT_OF_RANGE', `${path}.code`);
208
+ const label = requireString(tag.label, `${path}.label`);
209
+ validateUnicode(label, `${path}.label`, limits.maxStringBytes);
210
+ }
211
+ function incrementLogicalNode(state, path) {
212
+ state.nodes += 1;
213
+ if (state.nodes > state.limits.maxNodes) {
214
+ valueError('HQ_VALUE_TOO_MANY_NODES', path);
215
+ }
216
+ }
217
+ function validateCompositeDepth(depth, state, path) {
218
+ const next = depth + 1;
219
+ if (next > state.limits.maxDepth)
220
+ valueError('HQ_VALUE_TOO_DEEP', path);
221
+ return next;
222
+ }
223
+ function validateValue(value, state, path, depth, declaredClickHouseType) {
224
+ incrementLogicalNode(state, path);
225
+ if (value === null || typeof value === 'boolean')
226
+ return;
227
+ if (typeof value === 'string') {
228
+ validateUnicode(value, path, state.limits.maxStringBytes);
229
+ return;
230
+ }
231
+ if (typeof value === 'number') {
232
+ if (!Number.isFinite(value))
233
+ valueError('HQ_VALUE_NON_FINITE_FLOAT', path);
234
+ if (Object.is(value, -0))
235
+ valueError('HQ_VALUE_NEGATIVE_ZERO', path);
236
+ if (/^(?:U?Int)(?:8|16|32|64|128|256)$/.test(declaredClickHouseType ?? '')) {
237
+ valueError('HQ_VALUE_INTEGER_TAG_REQUIRED', path);
238
+ }
239
+ return;
240
+ }
241
+ if (Array.isArray(value))
242
+ valueError('HQ_VALUE_RAW_COMPOSITE', path);
243
+ const envelope = requireRecord(value, path);
244
+ assertExactFields(envelope, ['$hypequery'], path);
245
+ const tagPath = `${path}.$hypequery`;
246
+ const tag = requireRecord(envelope.$hypequery, tagPath);
247
+ const type = requireString(tag.type, `${tagPath}.type`);
248
+ const version = requireMetadataInteger(tag.version, `${tagPath}.version`);
249
+ if (version !== 1)
250
+ valueError('HQ_VALUE_UNKNOWN_TAG_VERSION', `${tagPath}.version`);
251
+ switch (type) {
252
+ case 'integer':
253
+ validateIntegerTag(tag, tagPath, declaredClickHouseType);
254
+ return;
255
+ case 'decimal':
256
+ validateDecimalTag(tag, tagPath);
257
+ return;
258
+ case 'date':
259
+ validateDateTag(tag, tagPath);
260
+ return;
261
+ case 'datetime':
262
+ validateDatetimeTag(tag, tagPath);
263
+ return;
264
+ case 'uuid':
265
+ validateUuidTag(tag, tagPath);
266
+ return;
267
+ case 'bytes':
268
+ validateBytesTag(tag, tagPath, state.limits);
269
+ return;
270
+ case 'enum':
271
+ validateEnumTag(tag, tagPath, state.limits);
272
+ return;
273
+ case 'array':
274
+ case 'tuple': {
275
+ assertExactFields(tag, ['type', 'values', 'version'], tagPath);
276
+ if (!Array.isArray(tag.values))
277
+ valueError('HQ_VALUE_INVALID_FORMAT', `${tagPath}.values`);
278
+ if (tag.values.length > state.limits.maxCollectionItems) {
279
+ valueError('HQ_VALUE_TOO_MANY_ITEMS', `${tagPath}.values`);
280
+ }
281
+ const nextDepth = validateCompositeDepth(depth, state, path);
282
+ tag.values.forEach((item, index) => {
283
+ validateValue(item, state, `${tagPath}.values[${index}]`, nextDepth);
284
+ });
285
+ return;
286
+ }
287
+ case 'map': {
288
+ assertExactFields(tag, ['entries', 'type', 'version'], tagPath);
289
+ if (!Array.isArray(tag.entries))
290
+ valueError('HQ_VALUE_INVALID_FORMAT', `${tagPath}.entries`);
291
+ if (tag.entries.length > state.limits.maxCollectionItems) {
292
+ valueError('HQ_VALUE_TOO_MANY_ITEMS', `${tagPath}.entries`);
293
+ }
294
+ const nextDepth = validateCompositeDepth(depth, state, path);
295
+ tag.entries.forEach((entry, index) => {
296
+ const entryPath = `${tagPath}.entries[${index}]`;
297
+ if (!Array.isArray(entry) || entry.length !== 2) {
298
+ valueError('HQ_VALUE_INVALID_FORMAT', entryPath);
299
+ }
300
+ validateValue(entry[0], state, `${entryPath}[0]`, nextDepth);
301
+ validateValue(entry[1], state, `${entryPath}[1]`, nextDepth);
302
+ });
303
+ return;
304
+ }
305
+ default: valueError('HQ_VALUE_UNKNOWN_TAG', `${tagPath}.type`);
306
+ }
307
+ }
308
+ function deepFreeze(value) {
309
+ if (typeof value !== 'object' || value === null || Object.isFrozen(value))
310
+ return;
311
+ for (const child of Object.values(value))
312
+ deepFreeze(child);
313
+ Object.freeze(value);
314
+ }
315
+ export function validateCanonicalValue(input, options = {}) {
316
+ const limits = resolveLimits(options);
317
+ return validateCanonicalValueWithLimits(input, limits, options.declaredClickHouseType);
318
+ }
319
+ function validateSnapshot(snapshot, limits, declaredClickHouseType) {
320
+ validateValue(snapshot, { limits, nodes: 0 }, '$', 0, declaredClickHouseType);
321
+ deepFreeze(snapshot);
322
+ return snapshot;
323
+ }
324
+ /** Package-internal path for arbitrary input when limits are already resolved. */
325
+ export function validateCanonicalValueWithLimits(input, limits, declaredClickHouseType) {
326
+ const snapshot = snapshotPlainData(input, limits);
327
+ return validateSnapshot(snapshot, limits, declaredClickHouseType);
328
+ }
329
+ /** Package-internal path for trees created by the duplicate-aware parser. */
330
+ export function validateParsedCanonicalValue(parsed, limits, declaredClickHouseType) {
331
+ return validateSnapshot(parsed, limits, declaredClickHouseType);
332
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Public contracts and TypeScript reference implementation for portable Hypequery artifacts",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,6 +17,9 @@
17
17
  "dist",
18
18
  "README.md"
19
19
  ],
20
+ "dependencies": {
21
+ "@noble/hashes": "^1.8.0"
22
+ },
20
23
  "devDependencies": {
21
24
  "typescript": "^5.7.3",
22
25
  "vitest": "^3.2.6"