terminusdb 12.0.2

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 (86) hide show
  1. package/Contributing.md +36 -0
  2. package/LICENSE +201 -0
  3. package/README.md +175 -0
  4. package/RELEASE_NOTES.md +462 -0
  5. package/dist/index.html +22 -0
  6. package/dist/terminusdb-client.min.js +3 -0
  7. package/dist/terminusdb-client.min.js.LICENSE.txt +188 -0
  8. package/dist/terminusdb-client.min.js.map +1 -0
  9. package/dist/typescript/index.d.ts +14 -0
  10. package/dist/typescript/lib/accessControl.d.ts +554 -0
  11. package/dist/typescript/lib/axiosInstance.d.ts +2 -0
  12. package/dist/typescript/lib/connectionConfig.d.ts +381 -0
  13. package/dist/typescript/lib/const.d.ts +54 -0
  14. package/dist/typescript/lib/dispatchRequest.d.ts +17 -0
  15. package/dist/typescript/lib/errorMessage.d.ts +25 -0
  16. package/dist/typescript/lib/query/woqlBuilder.d.ts +75 -0
  17. package/dist/typescript/lib/query/woqlCore.d.ts +341 -0
  18. package/dist/typescript/lib/query/woqlDoc.d.ts +63 -0
  19. package/dist/typescript/lib/query/woqlLibrary.d.ts +718 -0
  20. package/dist/typescript/lib/query/woqlPrinter.d.ts +71 -0
  21. package/dist/typescript/lib/query/woqlQuery.d.ts +833 -0
  22. package/dist/typescript/lib/typedef.d.ts +624 -0
  23. package/dist/typescript/lib/utils.d.ts +199 -0
  24. package/dist/typescript/lib/valueHash.d.ts +146 -0
  25. package/dist/typescript/lib/viewer/chartConfig.d.ts +62 -0
  26. package/dist/typescript/lib/viewer/chooserConfig.d.ts +38 -0
  27. package/dist/typescript/lib/viewer/documentFrame.d.ts +44 -0
  28. package/dist/typescript/lib/viewer/frameConfig.d.ts +74 -0
  29. package/dist/typescript/lib/viewer/frameRule.d.ts +145 -0
  30. package/dist/typescript/lib/viewer/graphConfig.d.ts +73 -0
  31. package/dist/typescript/lib/viewer/objectFrame.d.ts +212 -0
  32. package/dist/typescript/lib/viewer/streamConfig.d.ts +23 -0
  33. package/dist/typescript/lib/viewer/tableConfig.d.ts +66 -0
  34. package/dist/typescript/lib/viewer/terminusRule.d.ts +75 -0
  35. package/dist/typescript/lib/viewer/viewConfig.d.ts +47 -0
  36. package/dist/typescript/lib/viewer/woqlChart.d.ts +1 -0
  37. package/dist/typescript/lib/viewer/woqlChooser.d.ts +56 -0
  38. package/dist/typescript/lib/viewer/woqlGraph.d.ts +26 -0
  39. package/dist/typescript/lib/viewer/woqlPaging.d.ts +1 -0
  40. package/dist/typescript/lib/viewer/woqlResult.d.ts +128 -0
  41. package/dist/typescript/lib/viewer/woqlRule.d.ts +96 -0
  42. package/dist/typescript/lib/viewer/woqlStream.d.ts +31 -0
  43. package/dist/typescript/lib/viewer/woqlTable.d.ts +102 -0
  44. package/dist/typescript/lib/viewer/woqlView.d.ts +49 -0
  45. package/dist/typescript/lib/woql.d.ts +1267 -0
  46. package/dist/typescript/lib/woqlClient.d.ts +1216 -0
  47. package/index.js +28 -0
  48. package/lib/.eslintrc +1 -0
  49. package/lib/accessControl.js +988 -0
  50. package/lib/axiosInstance.js +5 -0
  51. package/lib/connectionConfig.js +765 -0
  52. package/lib/const.js +59 -0
  53. package/lib/dispatchRequest.js +236 -0
  54. package/lib/errorMessage.js +110 -0
  55. package/lib/query/woqlBuilder.js +234 -0
  56. package/lib/query/woqlCore.js +934 -0
  57. package/lib/query/woqlDoc.js +177 -0
  58. package/lib/query/woqlLibrary.js +1015 -0
  59. package/lib/query/woqlPrinter.js +476 -0
  60. package/lib/query/woqlQuery.js +1865 -0
  61. package/lib/typedef.js +248 -0
  62. package/lib/utils.js +817 -0
  63. package/lib/valueHash.js_old +581 -0
  64. package/lib/viewer/chartConfig.js +411 -0
  65. package/lib/viewer/chooserConfig.js +234 -0
  66. package/lib/viewer/documentFrame.js +206 -0
  67. package/lib/viewer/frameConfig.js +469 -0
  68. package/lib/viewer/frameRule.js +519 -0
  69. package/lib/viewer/graphConfig.js +345 -0
  70. package/lib/viewer/objectFrame.js +1550 -0
  71. package/lib/viewer/streamConfig.js +82 -0
  72. package/lib/viewer/tableConfig.js +310 -0
  73. package/lib/viewer/terminusRule.js +196 -0
  74. package/lib/viewer/viewConfig.js +219 -0
  75. package/lib/viewer/woqlChart.js +17 -0
  76. package/lib/viewer/woqlChooser.js +171 -0
  77. package/lib/viewer/woqlGraph.js +295 -0
  78. package/lib/viewer/woqlPaging.js +148 -0
  79. package/lib/viewer/woqlResult.js +258 -0
  80. package/lib/viewer/woqlRule.js +312 -0
  81. package/lib/viewer/woqlStream.js +27 -0
  82. package/lib/viewer/woqlTable.js +332 -0
  83. package/lib/viewer/woqlView.js +107 -0
  84. package/lib/woql.js +1693 -0
  85. package/lib/woqlClient.js +2091 -0
  86. package/package.json +110 -0
@@ -0,0 +1,581 @@
1
+ /* eslint-disable no-plusplus */
2
+ /* eslint-disable no-continue */
3
+ /* eslint-disable no-restricted-syntax */
4
+ /**
5
+ * @file ValueHash - Client-side ValueHash computation for TerminusDB documents
6
+ * @license Apache Version 2
7
+ *
8
+ * This module provides functions to compute ValueHash IDs for TerminusDB documents
9
+ * on the client side, matching the server's algorithm exactly.
10
+ *
11
+ * The ValueHash algorithm:
12
+ * 1. Extract all path-value pairs from the document
13
+ * 2. Format them in Prolog's ~q (quoted) format
14
+ * 3. Compute SHA256 hash of the formatted string
15
+ * 4. Append hash to the base IRI (e.g., "Type/" -> "Type/<hash>")
16
+ */
17
+
18
+ const crypto = require('node:crypto');
19
+
20
+ /**
21
+ * Standard XSD type mappings for value elaboration
22
+ */
23
+ const XSD_TYPES = {
24
+ string: 'http://www.w3.org/2001/XMLSchema#string',
25
+ integer: 'http://www.w3.org/2001/XMLSchema#integer',
26
+ decimal: 'http://www.w3.org/2001/XMLSchema#decimal',
27
+ double: 'http://www.w3.org/2001/XMLSchema#double',
28
+ float: 'http://www.w3.org/2001/XMLSchema#float',
29
+ boolean: 'http://www.w3.org/2001/XMLSchema#boolean',
30
+ date: 'http://www.w3.org/2001/XMLSchema#date',
31
+ dateTime: 'http://www.w3.org/2001/XMLSchema#dateTime',
32
+ time: 'http://www.w3.org/2001/XMLSchema#time',
33
+ gYear: 'http://www.w3.org/2001/XMLSchema#gYear',
34
+ gMonth: 'http://www.w3.org/2001/XMLSchema#gMonth',
35
+ gDay: 'http://www.w3.org/2001/XMLSchema#gDay',
36
+ gYearMonth: 'http://www.w3.org/2001/XMLSchema#gYearMonth',
37
+ gMonthDay: 'http://www.w3.org/2001/XMLSchema#gMonthDay',
38
+ duration: 'http://www.w3.org/2001/XMLSchema#duration',
39
+ anyURI: 'http://www.w3.org/2001/XMLSchema#anyURI',
40
+ base64Binary: 'http://www.w3.org/2001/XMLSchema#base64Binary',
41
+ hexBinary: 'http://www.w3.org/2001/XMLSchema#hexBinary',
42
+ long: 'http://www.w3.org/2001/XMLSchema#long',
43
+ short: 'http://www.w3.org/2001/XMLSchema#short',
44
+ byte: 'http://www.w3.org/2001/XMLSchema#byte',
45
+ unsignedLong: 'http://www.w3.org/2001/XMLSchema#unsignedLong',
46
+ unsignedInt: 'http://www.w3.org/2001/XMLSchema#unsignedInt',
47
+ unsignedShort: 'http://www.w3.org/2001/XMLSchema#unsignedShort',
48
+ unsignedByte: 'http://www.w3.org/2001/XMLSchema#unsignedByte',
49
+ positiveInteger: 'http://www.w3.org/2001/XMLSchema#positiveInteger',
50
+ negativeInteger: 'http://www.w3.org/2001/XMLSchema#negativeInteger',
51
+ nonPositiveInteger: 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger',
52
+ nonNegativeInteger: 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger',
53
+ };
54
+
55
+ /**
56
+ * Default context prefixes
57
+ */
58
+ const DEFAULT_CONTEXT = {
59
+ '@base': 'terminusdb:///data/',
60
+ '@schema': 'terminusdb:///schema#',
61
+ };
62
+
63
+ /**
64
+ * Expand a prefixed IRI using the context
65
+ * @param {string} iri - The IRI to expand (may be prefixed like "xsd:string")
66
+ * @param {object} context - The context object with prefix mappings
67
+ * @returns {string} The expanded IRI
68
+ */
69
+ function expandIRI(iri, context) {
70
+ if (!iri || typeof iri !== 'string') return iri;
71
+
72
+ // Already expanded
73
+ if (iri.startsWith('http://') || iri.startsWith('https://') || iri.startsWith('terminusdb:')) {
74
+ return iri;
75
+ }
76
+
77
+ // Handle xsd: prefix specially
78
+ if (iri.startsWith('xsd:')) {
79
+ const localPart = iri.substring(4);
80
+ return XSD_TYPES[localPart] || `http://www.w3.org/2001/XMLSchema#${localPart}`;
81
+ }
82
+
83
+ // Handle prefixed IRIs
84
+ const colonIndex = iri.indexOf(':');
85
+ if (colonIndex > 0) {
86
+ const prefix = iri.substring(0, colonIndex);
87
+ const localPart = iri.substring(colonIndex + 1);
88
+ if (context[prefix]) {
89
+ return context[prefix] + localPart;
90
+ }
91
+ }
92
+
93
+ // Use schema prefix for type names
94
+ const schemaBase = context['@schema'] || DEFAULT_CONTEXT['@schema'];
95
+ return schemaBase + iri;
96
+ }
97
+
98
+ /**
99
+ * Get the base IRI for a type
100
+ * @param {string} type - The type name
101
+ * @param {object} context - The context object
102
+ * @returns {string} The base IRI for document IDs of this type
103
+ */
104
+ function getTypeBase(type, context) {
105
+ const base = context['@base'] || DEFAULT_CONTEXT['@base'];
106
+ // Extract just the type name without namespace
107
+ let typeName = type;
108
+ if (type.includes('#')) {
109
+ typeName = type.split('#').pop();
110
+ } else if (type.includes('/')) {
111
+ typeName = type.split('/').pop();
112
+ }
113
+ return `${base}${typeName}/`;
114
+ }
115
+
116
+ /**
117
+ * Infer XSD type from JavaScript value
118
+ * @param {any} value - The value to infer type for
119
+ * @returns {string} The XSD type IRI
120
+ */
121
+ function inferXsdType(value) {
122
+ if (typeof value === 'string') {
123
+ return XSD_TYPES.string;
124
+ }
125
+ if (typeof value === 'number') {
126
+ if (Number.isInteger(value)) {
127
+ return XSD_TYPES.integer;
128
+ }
129
+ return XSD_TYPES.decimal;
130
+ }
131
+ if (typeof value === 'boolean') {
132
+ return XSD_TYPES.boolean;
133
+ }
134
+ return XSD_TYPES.string;
135
+ }
136
+
137
+ /**
138
+ * Format a string for Prolog quoted output (escape special characters)
139
+ * @param {string} str - The string to format
140
+ * @returns {string} The formatted string
141
+ */
142
+ function formatPrologString(str) {
143
+ // In Prolog ~q format, strings are double-quoted and special chars are escaped
144
+ let result = str;
145
+ // Escape backslashes first
146
+ result = result.replace(/\\/g, '\\\\');
147
+ // Escape double quotes
148
+ result = result.replace(/"/g, '\\"');
149
+ return `"${result}"`;
150
+ }
151
+
152
+ /**
153
+ * Format an atom for Prolog quoted output
154
+ * Atoms containing special characters or starting with uppercase need quoting
155
+ * @param {string} atom - The atom to format
156
+ * @returns {string} The formatted atom
157
+ */
158
+ function formatPrologAtom(atom) {
159
+ // In Prolog, atoms are single-quoted if they contain special characters
160
+ // or start with uppercase, etc.
161
+ // For URIs and special names, we always quote
162
+ if (typeof atom !== 'string') {
163
+ return String(atom);
164
+ }
165
+
166
+ // Escape single quotes within the atom
167
+ const escaped = atom.replace(/'/g, "\\'");
168
+ return `'${escaped}'`;
169
+ }
170
+
171
+ /**
172
+ * Format a typed value in Prolog format: (value^^type)
173
+ * For strings: ("value"^^'type')
174
+ * For numbers: (42^^'type')
175
+ * For booleans: (true^^'type')
176
+ * @param {any} value - The value
177
+ * @param {string} type - The XSD type IRI
178
+ * @returns {string} The formatted typed value
179
+ */
180
+ function formatTypedValue(value, type) {
181
+ let formattedValue;
182
+
183
+ // Numbers are NOT quoted in Prolog
184
+ if (typeof value === 'number') {
185
+ formattedValue = String(value);
186
+ } else if (typeof value === 'boolean') {
187
+ formattedValue = value ? 'true' : 'false';
188
+ } else {
189
+ // Strings are double-quoted
190
+ formattedValue = formatPrologString(String(value));
191
+ }
192
+
193
+ const formattedType = formatPrologAtom(type);
194
+ return `(${formattedValue}^^${formattedType})`;
195
+ }
196
+
197
+ /**
198
+ * Format a path (list of keys) in Prolog format: [key1,key2,...]
199
+ * @param {Array} path - The path as array of keys
200
+ * @returns {string} The formatted path
201
+ */
202
+ function formatPrologPath(path) {
203
+ const formattedElements = path.map((element) => {
204
+ if (typeof element === 'number') {
205
+ return String(element);
206
+ }
207
+ return formatPrologAtom(element);
208
+ });
209
+ return `[${formattedElements.join(',')}]`;
210
+ }
211
+
212
+ /**
213
+ * Format a path-value pair in Prolog format: [path]-value
214
+ * @param {Array} path - The path
215
+ * @param {any} value - The value (already formatted)
216
+ * @returns {string} The formatted pair
217
+ */
218
+ function formatPathValuePair(path, value) {
219
+ return `${formatPrologPath(path)}-${value}`;
220
+ }
221
+
222
+ /**
223
+ * Extract all path-value pairs from an elaborated document
224
+ * This matches TerminusDB's get_all_path_values/2 predicate
225
+ *
226
+ * @param {object} doc - The elaborated document
227
+ * @param {Array} currentPath - The current path (for recursion)
228
+ * @returns {Array} Array of [path, formattedValue] pairs
229
+ */
230
+ function extractPathValues(doc, currentPath = []) {
231
+ const pathValues = [];
232
+
233
+ if (doc === null || doc === undefined) {
234
+ return pathValues;
235
+ }
236
+
237
+ if (typeof doc !== 'object') {
238
+ // Primitive value - should not happen at top level
239
+ return pathValues;
240
+ }
241
+
242
+ // Get all keys and sort them for deterministic ordering
243
+ const keys = Object.keys(doc).filter((k) => k !== '@id').sort();
244
+
245
+ for (const key of keys) {
246
+ const value = doc[key];
247
+ const newPath = [...currentPath, key];
248
+
249
+ if (key === '@type') {
250
+ // Type is an atom value
251
+ if (typeof value === 'string') {
252
+ pathValues.push([newPath, formatPrologAtom(value)]);
253
+ }
254
+ } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
255
+ // Nested object - check @container FIRST (containers also have @type and @value)
256
+ if (value['@container'] !== undefined) {
257
+ // Container type (list, set, array)
258
+ // For containers, get_value extracts each element's value directly
259
+ // The path is just the field name (no @value, no indices)
260
+ if (value['@value'] !== undefined) {
261
+ const containerValue = value['@value'];
262
+ if (Array.isArray(containerValue)) {
263
+ if (containerValue.length === 0) {
264
+ // Empty container - emit empty list
265
+ pathValues.push([newPath, '[]']);
266
+ } else {
267
+ // Sort elements for deterministic ordering
268
+ const sortedElements = [...containerValue].sort((a, b) => {
269
+ const aStr = JSON.stringify(a);
270
+ const bStr = JSON.stringify(b);
271
+ return aStr.localeCompare(bStr);
272
+ });
273
+ // Emit each element's value with just the field path
274
+ sortedElements.forEach((element) => {
275
+ if (element['@value'] !== undefined && element['@type'] !== undefined) {
276
+ // Typed literal - emit directly
277
+ pathValues.push([newPath, formatTypedValue(element['@value'], element['@type'])]);
278
+ } else if (element['@type'] === '@id' && element['@id'] !== undefined) {
279
+ // Document link
280
+ pathValues.push([newPath, formatPrologString(element['@id'])]);
281
+ } else if (typeof element === 'object' && element !== null) {
282
+ // Subdocument - this would need recursive handling
283
+ // But for simple cases, subdocs in sets might have their own @id
284
+ const subPaths = extractPathValues(element, newPath);
285
+ pathValues.push(...subPaths);
286
+ }
287
+ });
288
+ }
289
+ }
290
+ }
291
+ } else if (value['@type'] === '@id' && value['@id'] !== undefined) {
292
+ // Document link - value is just the IRI string (double-quoted)
293
+ pathValues.push([newPath, formatPrologString(value['@id'])]);
294
+ } else if (value['@value'] !== undefined && value['@type'] !== undefined) {
295
+ // Typed literal like {"@value": "foo", "@type": "xsd:string"}
296
+ pathValues.push([newPath, formatTypedValue(value['@value'], value['@type'])]);
297
+ } else {
298
+ // Regular nested object (subdocument)
299
+ const subPaths = extractPathValues(value, newPath);
300
+ pathValues.push(...subPaths);
301
+ }
302
+ } else if (Array.isArray(value)) {
303
+ // Array/Set - sort for deterministic ordering
304
+ if (value.length === 0) {
305
+ pathValues.push([newPath, '[]']);
306
+ } else {
307
+ const sortedElements = [...value].sort((a, b) => {
308
+ const aStr = JSON.stringify(a);
309
+ const bStr = JSON.stringify(b);
310
+ return aStr.localeCompare(bStr);
311
+ });
312
+ for (let i = 0; i < sortedElements.length; i++) {
313
+ const element = sortedElements[i];
314
+ const elementPath = [...newPath, i];
315
+ if (typeof element === 'object' && element !== null) {
316
+ // Check if it's a typed literal (has @value and @type)
317
+ if (element['@value'] !== undefined && element['@type'] !== undefined) {
318
+ // Typed literal - emit directly without recursing
319
+ pathValues.push([elementPath, formatTypedValue(element['@value'], element['@type'])]);
320
+ } else {
321
+ // Complex object - recurse
322
+ const subPaths = extractPathValues(element, elementPath);
323
+ pathValues.push(...subPaths);
324
+ }
325
+ } else {
326
+ // Primitive in array
327
+ const type = inferXsdType(element);
328
+ pathValues.push([elementPath, formatTypedValue(element, type)]);
329
+ }
330
+ }
331
+ }
332
+ } else {
333
+ // Primitive value - create typed literal
334
+ const type = inferXsdType(value);
335
+ pathValues.push([newPath, formatTypedValue(value, type)]);
336
+ }
337
+ }
338
+
339
+ return pathValues;
340
+ }
341
+
342
+ /**
343
+ * Check if a string value is a document link (IRI reference)
344
+ * @param {string} value - The value to check
345
+ * @param {object} context - The context with @base
346
+ * @returns {boolean} True if value is a document link
347
+ */
348
+ function isDocumentLink(value, context) {
349
+ const base = context['@base'] || 'terminusdb:///data/';
350
+ // Check if value starts with the base IRI (it's a document reference)
351
+ return value.startsWith(base) || value.startsWith('http://') || value.startsWith('https://');
352
+ }
353
+
354
+ /**
355
+ * Elaborate a document by expanding IRIs and adding type information
356
+ * This is a simplified version that works for ValueHash computation
357
+ *
358
+ * @param {object} doc - The document to elaborate
359
+ * @param {object} context - The context with prefix mappings
360
+ * @param {object} schema - Optional schema information for type inference
361
+ * @returns {object} The elaborated document
362
+ */
363
+ function elaborateDocument(doc, context, schema = null) {
364
+ if (doc === null || doc === undefined) {
365
+ return doc;
366
+ }
367
+
368
+ if (typeof doc !== 'object') {
369
+ return doc;
370
+ }
371
+
372
+ if (Array.isArray(doc)) {
373
+ return doc.map((item) => elaborateDocument(item, context, schema));
374
+ }
375
+
376
+ const elaborated = {};
377
+
378
+ // Expand @type first
379
+ if (doc['@type']) {
380
+ elaborated['@type'] = expandIRI(doc['@type'], context);
381
+ }
382
+
383
+ // Process other fields
384
+ for (const [key, value] of Object.entries(doc)) {
385
+ if (key === '@type' || key === '@id') {
386
+ continue;
387
+ }
388
+
389
+ // Expand the key to full IRI
390
+ const expandedKey = key.startsWith('@') ? key : expandIRI(key, context);
391
+
392
+ if (value === null || value === undefined) {
393
+ continue;
394
+ }
395
+
396
+ if (typeof value === 'object' && !Array.isArray(value)) {
397
+ if (value['@type'] && value['@value'] !== undefined) {
398
+ // Already a typed value
399
+ elaborated[expandedKey] = {
400
+ '@type': expandIRI(value['@type'], context),
401
+ '@value': value['@value'],
402
+ };
403
+ } else {
404
+ // Subdocument - recursively elaborate
405
+ elaborated[expandedKey] = elaborateDocument(value, context, schema);
406
+ }
407
+ } else if (Array.isArray(value)) {
408
+ // Array of values - create container structure
409
+ const elaboratedItems = value.map((item) => {
410
+ if (typeof item === 'object' && item !== null) {
411
+ return elaborateDocument(item, context, schema);
412
+ }
413
+ // Wrap primitive in typed literal
414
+ return {
415
+ '@type': inferXsdType(item),
416
+ '@value': item,
417
+ };
418
+ });
419
+
420
+ // Infer element type from first item (or use generic)
421
+ let elementType = XSD_TYPES.string;
422
+ if (elaboratedItems.length > 0) {
423
+ const firstItem = elaboratedItems[0];
424
+ if (firstItem['@type']) {
425
+ elementType = firstItem['@type'];
426
+ }
427
+ }
428
+
429
+ // Create container structure
430
+ elaborated[expandedKey] = {
431
+ '@container': '@set', // Default to set; could be refined with schema
432
+ '@type': elementType,
433
+ '@value': elaboratedItems,
434
+ };
435
+ } else if (typeof value === 'string' && isDocumentLink(value, context)) {
436
+ // Document link - wrap as @id reference
437
+ elaborated[expandedKey] = {
438
+ '@type': '@id',
439
+ '@id': value,
440
+ };
441
+ } else {
442
+ // Primitive value - wrap in typed literal
443
+ elaborated[expandedKey] = {
444
+ '@type': inferXsdType(value),
445
+ '@value': value,
446
+ };
447
+ }
448
+ }
449
+
450
+ return elaborated;
451
+ }
452
+
453
+ /**
454
+ * Compute the ValueHash for a document
455
+ *
456
+ * @param {object} doc - The document to hash
457
+ * @param {object} [options] - Options for hash computation
458
+ * @param {object} [options.context] - The context with prefix mappings
459
+ * @param {object} [options.schema] - Optional schema information
460
+ * @returns {string} The computed ValueHash (SHA256 hex string)
461
+ */
462
+ function computeValueHash(doc, options = {}) {
463
+ const context = options.context || DEFAULT_CONTEXT;
464
+
465
+ // Elaborate the document
466
+ const elaborated = elaborateDocument(doc, context, options.schema);
467
+
468
+ // Extract path-values
469
+ const pathValues = extractPathValues(elaborated);
470
+
471
+ // Sort path-values for deterministic ordering (by path first, then value)
472
+ pathValues.sort((a, b) => {
473
+ const pathA = JSON.stringify(a[0]);
474
+ const pathB = JSON.stringify(b[0]);
475
+ if (pathA !== pathB) {
476
+ return pathA.localeCompare(pathB);
477
+ }
478
+ return String(a[1]).localeCompare(String(b[1]));
479
+ });
480
+
481
+ // Format as Prolog list of pairs
482
+ const formattedPairs = pathValues.map(([path, value]) => formatPathValuePair(path, value));
483
+ const prologList = `[${formattedPairs.join(',')}]`;
484
+
485
+ // Debug: log the Prolog list being hashed
486
+ // if (options.debug) {
487
+ // console.log('Prolog list being hashed:', prologList);
488
+ // }
489
+
490
+ // Compute SHA256 hash
491
+ const hash = crypto.createHash('sha256').update(prologList).digest('hex');
492
+
493
+ return hash;
494
+ }
495
+
496
+ /**
497
+ * Generate the full @id for a document using ValueHash
498
+ *
499
+ * @param {object} doc - The document to generate ID for
500
+ * @param {object} [options] - Options for ID generation
501
+ * @param {object} [options.context] - The context with prefix mappings
502
+ * @param {object} [options.schema] - Optional schema information
503
+ * @returns {string} The full document @id with ValueHash
504
+ */
505
+ function generateValueHashId(doc, options = {}) {
506
+ const context = options.context || DEFAULT_CONTEXT;
507
+
508
+ if (!doc['@type']) {
509
+ throw new Error('Document must have @type for ValueHash computation');
510
+ }
511
+
512
+ const expandedType = expandIRI(doc['@type'], context);
513
+ const base = getTypeBase(expandedType, context);
514
+ const hash = computeValueHash(doc, options);
515
+
516
+ return base + hash;
517
+ }
518
+
519
+ /**
520
+ * Set the @id of a document using ValueHash, recursively processing subdocuments
521
+ *
522
+ * @param {object} doc - The document to set ID for
523
+ * @param {object} [options] - Options for ID generation
524
+ * @param {object} [options.context] - The context with prefix mappings
525
+ * @param {object} [options.schema] - Optional schema information
526
+ * @returns {object} The document with @id set (and subdocuments processed)
527
+ */
528
+ function setValueHashId(doc, options = {}) {
529
+ if (doc === null || doc === undefined || typeof doc !== 'object') {
530
+ return doc;
531
+ }
532
+
533
+ if (Array.isArray(doc)) {
534
+ return doc.map((item) => setValueHashId(item, options));
535
+ }
536
+
537
+ // Create a copy to avoid mutating the original
538
+ const result = { ...doc };
539
+
540
+ // Process subdocuments first (depth-first)
541
+ for (const [key, value] of Object.entries(result)) {
542
+ if (key.startsWith('@')) {
543
+ continue;
544
+ }
545
+
546
+ if (Array.isArray(value)) {
547
+ result[key] = value.map((item) => {
548
+ if (typeof item === 'object' && item !== null && item['@type']) {
549
+ return setValueHashId(item, options);
550
+ }
551
+ return item;
552
+ });
553
+ } else if (typeof value === 'object' && value !== null && value['@type']) {
554
+ result[key] = setValueHashId(value, options);
555
+ }
556
+ }
557
+
558
+ // Now compute and set the ID for this document
559
+ if (result['@type']) {
560
+ result['@id'] = generateValueHashId(result, options);
561
+ }
562
+
563
+ return result;
564
+ }
565
+
566
+ module.exports = {
567
+ computeValueHash,
568
+ generateValueHashId,
569
+ setValueHashId,
570
+ elaborateDocument,
571
+ extractPathValues,
572
+ expandIRI,
573
+ getTypeBase,
574
+ formatPrologAtom,
575
+ formatPrologString,
576
+ formatTypedValue,
577
+ formatPrologPath,
578
+ formatPathValuePair,
579
+ XSD_TYPES,
580
+ DEFAULT_CONTEXT,
581
+ };