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.
- package/Contributing.md +36 -0
- package/LICENSE +201 -0
- package/README.md +175 -0
- package/RELEASE_NOTES.md +462 -0
- package/dist/index.html +22 -0
- package/dist/terminusdb-client.min.js +3 -0
- package/dist/terminusdb-client.min.js.LICENSE.txt +188 -0
- package/dist/terminusdb-client.min.js.map +1 -0
- package/dist/typescript/index.d.ts +14 -0
- package/dist/typescript/lib/accessControl.d.ts +554 -0
- package/dist/typescript/lib/axiosInstance.d.ts +2 -0
- package/dist/typescript/lib/connectionConfig.d.ts +381 -0
- package/dist/typescript/lib/const.d.ts +54 -0
- package/dist/typescript/lib/dispatchRequest.d.ts +17 -0
- package/dist/typescript/lib/errorMessage.d.ts +25 -0
- package/dist/typescript/lib/query/woqlBuilder.d.ts +75 -0
- package/dist/typescript/lib/query/woqlCore.d.ts +341 -0
- package/dist/typescript/lib/query/woqlDoc.d.ts +63 -0
- package/dist/typescript/lib/query/woqlLibrary.d.ts +718 -0
- package/dist/typescript/lib/query/woqlPrinter.d.ts +71 -0
- package/dist/typescript/lib/query/woqlQuery.d.ts +833 -0
- package/dist/typescript/lib/typedef.d.ts +624 -0
- package/dist/typescript/lib/utils.d.ts +199 -0
- package/dist/typescript/lib/valueHash.d.ts +146 -0
- package/dist/typescript/lib/viewer/chartConfig.d.ts +62 -0
- package/dist/typescript/lib/viewer/chooserConfig.d.ts +38 -0
- package/dist/typescript/lib/viewer/documentFrame.d.ts +44 -0
- package/dist/typescript/lib/viewer/frameConfig.d.ts +74 -0
- package/dist/typescript/lib/viewer/frameRule.d.ts +145 -0
- package/dist/typescript/lib/viewer/graphConfig.d.ts +73 -0
- package/dist/typescript/lib/viewer/objectFrame.d.ts +212 -0
- package/dist/typescript/lib/viewer/streamConfig.d.ts +23 -0
- package/dist/typescript/lib/viewer/tableConfig.d.ts +66 -0
- package/dist/typescript/lib/viewer/terminusRule.d.ts +75 -0
- package/dist/typescript/lib/viewer/viewConfig.d.ts +47 -0
- package/dist/typescript/lib/viewer/woqlChart.d.ts +1 -0
- package/dist/typescript/lib/viewer/woqlChooser.d.ts +56 -0
- package/dist/typescript/lib/viewer/woqlGraph.d.ts +26 -0
- package/dist/typescript/lib/viewer/woqlPaging.d.ts +1 -0
- package/dist/typescript/lib/viewer/woqlResult.d.ts +128 -0
- package/dist/typescript/lib/viewer/woqlRule.d.ts +96 -0
- package/dist/typescript/lib/viewer/woqlStream.d.ts +31 -0
- package/dist/typescript/lib/viewer/woqlTable.d.ts +102 -0
- package/dist/typescript/lib/viewer/woqlView.d.ts +49 -0
- package/dist/typescript/lib/woql.d.ts +1267 -0
- package/dist/typescript/lib/woqlClient.d.ts +1216 -0
- package/index.js +28 -0
- package/lib/.eslintrc +1 -0
- package/lib/accessControl.js +988 -0
- package/lib/axiosInstance.js +5 -0
- package/lib/connectionConfig.js +765 -0
- package/lib/const.js +59 -0
- package/lib/dispatchRequest.js +236 -0
- package/lib/errorMessage.js +110 -0
- package/lib/query/woqlBuilder.js +234 -0
- package/lib/query/woqlCore.js +934 -0
- package/lib/query/woqlDoc.js +177 -0
- package/lib/query/woqlLibrary.js +1015 -0
- package/lib/query/woqlPrinter.js +476 -0
- package/lib/query/woqlQuery.js +1865 -0
- package/lib/typedef.js +248 -0
- package/lib/utils.js +817 -0
- package/lib/valueHash.js_old +581 -0
- package/lib/viewer/chartConfig.js +411 -0
- package/lib/viewer/chooserConfig.js +234 -0
- package/lib/viewer/documentFrame.js +206 -0
- package/lib/viewer/frameConfig.js +469 -0
- package/lib/viewer/frameRule.js +519 -0
- package/lib/viewer/graphConfig.js +345 -0
- package/lib/viewer/objectFrame.js +1550 -0
- package/lib/viewer/streamConfig.js +82 -0
- package/lib/viewer/tableConfig.js +310 -0
- package/lib/viewer/terminusRule.js +196 -0
- package/lib/viewer/viewConfig.js +219 -0
- package/lib/viewer/woqlChart.js +17 -0
- package/lib/viewer/woqlChooser.js +171 -0
- package/lib/viewer/woqlGraph.js +295 -0
- package/lib/viewer/woqlPaging.js +148 -0
- package/lib/viewer/woqlResult.js +258 -0
- package/lib/viewer/woqlRule.js +312 -0
- package/lib/viewer/woqlStream.js +27 -0
- package/lib/viewer/woqlTable.js +332 -0
- package/lib/viewer/woqlView.js +107 -0
- package/lib/woql.js +1693 -0
- package/lib/woqlClient.js +2091 -0
- 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
|
+
};
|