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,1015 @@
1
+ /// /@ts-check
2
+ // we can not import woqlBuilder because woqlBuilder import WOQLLibrary
3
+ const WOQLQuery = require('./woqlBuilder');
4
+
5
+ /**
6
+ * @typedef {Object} WOQLQuery - WOQL Query Builder class
7
+ * @property {function} triple - Add a triple pattern
8
+ * @property {function} and - Logical AND
9
+ * @property {function} or - Logical OR
10
+ * @property {function} select - Select variables
11
+ * @property {function} ask - Ask query
12
+ * @property {function} limit - Limit results
13
+ * @property {function} offset - Offset results
14
+ * @property {function} start - Start query
15
+ * @property {function} path - Path query
16
+ * @property {function} subquery - Subquery
17
+ * @property {function} from - Graph specifier
18
+ * @property {function} eq - Equality
19
+ * @property {function} not - Negation
20
+ * @property {function} greaterThan - Greater than
21
+ * @property {function} lessThan - Less than
22
+ * @property {function} greaterThanOrEqual - Greater than or equal
23
+ * @property {function} lessThanOrEqual - Less than or equal
24
+ * @property {function} like - String pattern matching
25
+ * @property {function} plus - Addition
26
+ * @property {function} minus - Subtraction
27
+ * @property {function} times - Multiplication
28
+ * @property {function} divide - Division
29
+ * @property {function} count - Count aggregation
30
+ * @property {function} sum - Sum aggregation
31
+ * @property {function} min - Minimum aggregation
32
+ * @property {function} max - Maximum aggregation
33
+ * @property {function} avg - Average aggregation
34
+ * @property {function} groupBy - Group by
35
+ * @property {function} orderBy - Order by
36
+ * @property {function} asc - Ascending order
37
+ * @property {function} desc - Descending order
38
+ * @property {function} json - JSON object
39
+ * @property {function} id - IRI
40
+ * @property {function} string - String literal
41
+ * @property {function} datetime - DateTime literal
42
+ * @property {function} date - Date literal
43
+ * @property {function} time - Time literal
44
+ * @property {function} boolean - Boolean literal
45
+ * @property {function} integer - Integer literal
46
+ * @property {function} decimal - Decimal literal
47
+ * @property {function} double - Double literal
48
+ * @property {function} value - Value literal
49
+ * @property {function} var - Variable
50
+ * @property {function} v - Variable shorthand
51
+ * @property {function} doc - Document
52
+ * @property {function} class - Class
53
+ * @property {function} property - Property
54
+ * @property {function} comment - Comment
55
+ * @property {function} label - Label
56
+ * @property {function} prefix - Prefix
57
+ * @property {function} using - Using
58
+ * @property {function} optional - Optional
59
+ * @property {function} union - Union
60
+ * @property {function} filter - Filter
61
+ * @property {function} bind - Bind
62
+ * @property {function} exists - Exists
63
+ * @property {function} notExists - Not exists
64
+ * @property {function} graph - Graph
65
+ * @property {function} named - Named graph
66
+ * @property {function} values - Values
67
+ * @property {function} minus - Minus
68
+ * @property {function} service - Service
69
+ * @property {function} load - Load
70
+ * @property {function} clear - Clear
71
+ * @property {function} drop - Drop
72
+ * @property {function} add - Add
73
+ * @property {function} move - Move
74
+ * @property {function} copy - Copy
75
+ * @property {function} insert - Insert
76
+ * @property {function} delete - Delete
77
+ * @property {function} modify - Modify
78
+ * @property {function} construct - Construct
79
+ * @property {function} describe - Describe
80
+ */
81
+
82
+ /**
83
+ * @license Apache Version 2
84
+ * @module WOQLLibrary
85
+ * @constructor WOQLLibrary
86
+ * @description Library Functions to manage the commits graph
87
+ * @example
88
+ * const woqlLib = WOQLLibrary()
89
+ * woqlLib.branches()
90
+ *
91
+ * //or you can call this functions using WOQL Class
92
+ * WOQL.lib().branches()
93
+ * */
94
+ class WOQLLibrary {
95
+ default_schema_resource = 'schema/main';
96
+
97
+ default_commit_resource = '_commits';
98
+
99
+ default_meta_resource = '_meta';
100
+
101
+ masterdb_resource = '_system';
102
+
103
+ empty = '';
104
+ }
105
+
106
+ /**
107
+ * General Pattern 4: Retrieves Branches, Their ID, Head Commit ID, Head Commit Time
108
+ * (if present, new branches have no commits)
109
+ */
110
+ WOQLLibrary.prototype.branches = function () { // values, variables, cresource) {
111
+ const woql = new WOQLQuery().using('_commits').triple('v:Branch', 'rdf:type', '@schema:Branch')
112
+ .triple('v:Branch', '@schema:name', 'v:Name')
113
+ .opt()
114
+ .triple('v:Branch', '@schema:head', 'v:Head')
115
+ .triple('v:Head', '@schema:identifier', 'v:commit_identifier')
116
+ .triple('v:Head', '@schema:timestamp', 'v:Timestamp');
117
+ return woql;
118
+ };
119
+
120
+ /**
121
+ * get all the commits of a specific branch
122
+ * if a timestamp is given, gets all the commits before the specified timestamp
123
+ * @param {string} [branch] - the branch name
124
+ * @param {number} [limit] - the max number of result
125
+ * @param {number} [start] - the start of the pagination
126
+ * @param {number} [timestamp] - Unix timestamp in seconds
127
+ */
128
+
129
+ WOQLLibrary.prototype.commits = function (branch = 'main', limit = 0, start = 0, timestamp = 0) {
130
+ const woql = new WOQLQuery().using('_commits');
131
+ if (limit) woql.limit(limit);
132
+ if (start) woql.start(start);
133
+ woql.select('v:Parent ID', 'v:Commit ID', 'v:Time', 'v:Author', 'v:Branch ID', 'v:Message');
134
+
135
+ const andArr = [new WOQLQuery().triple('v:Branch', 'name', new WOQLQuery().string(branch))
136
+ .triple('v:Branch', 'head', 'v:Active Commit ID')
137
+ .path('v:Active Commit ID', 'parent*', 'v:Parent', 'v:Path')
138
+ .triple('v:Parent', 'timestamp', 'v:Time')];
139
+ if (timestamp) {
140
+ andArr.push(new WOQLQuery().less('v:Time', timestamp));
141
+ }
142
+ andArr.push(new WOQLQuery().triple('v:Parent', 'identifier', 'v:Commit ID')
143
+ .triple('v:Parent', 'author', 'v:Author')
144
+ .triple('v:Parent', 'message', 'v:Message')
145
+ .opt()
146
+ .triple('v:Parent', 'parent', 'v:Parent ID'));
147
+ return woql.and(...andArr);
148
+ };
149
+
150
+ /**
151
+ *get commits older than the specified commit id
152
+ * @param {string} [commit_id] - the commit id
153
+ * @param {number} [limit] - the max number of result
154
+ */
155
+ // eslint-disable-next-line camelcase
156
+ WOQLLibrary.prototype.previousCommits = function (commit_id, limit = 10) {
157
+ return new WOQLQuery().using('_commits').limit(limit).select('v:Parent ID', 'v:Message', 'v:Commit ID', 'v:Time', 'v:Author')
158
+ .and(
159
+ new WOQLQuery().and(
160
+ new WOQLQuery().triple('v:Active Commit ID', '@schema:identifier', new WOQLQuery().string(commit_id)),
161
+ new WOQLQuery().path('v:Active Commit ID', '@schema:parent+', 'v:Parent', 'v:Path'),
162
+ new WOQLQuery().triple('v:Parent', '@schema:identifier', 'v:Commit ID'),
163
+ new WOQLQuery().triple('v:Parent', '@schema:timestamp', 'v:Time'),
164
+ new WOQLQuery().triple('v:Parent', '@schema:author', 'v:Author'),
165
+ new WOQLQuery().triple('v:Parent', '@schema:message', 'v:Message'),
166
+ new WOQLQuery().triple('v:Parent', '@schema:parent', 'v:Parent ID'),
167
+ new WOQLQuery().opt().triple('v:Parent', 'parent', 'v:Parent ID'),
168
+ ),
169
+ );
170
+ };
171
+
172
+ /**
173
+ * Finds the id of the very first commit in a database's history
174
+ *
175
+ * This is useful for finding information about when, by who and why the database was created
176
+ * The first commit is the only commit in the database that does not have a parent commit
177
+ *
178
+ */
179
+ WOQLLibrary.prototype.first_commit = function () {
180
+ const noparent = new WOQLQuery()
181
+ .using('_commits').select('v:Any Commit IRI')
182
+ .and(
183
+ new WOQLQuery().triple('v:Branch', 'name', new WOQLQuery().string('main'))
184
+ .triple('v:Branch', 'head', 'v:Active Commit ID')
185
+ .path('v:Active Commit ID', 'parent*', 'v:Any Commit IRI', 'v:Path'),
186
+
187
+ new WOQLQuery().triple(
188
+ 'v:Any Commit IRI',
189
+ '@schema:identifier',
190
+ 'v:Commit ID',
191
+ ),
192
+ new WOQLQuery().triple(
193
+ 'v:Any Commit IRI',
194
+ '@schema:author',
195
+ 'v:Author',
196
+ ),
197
+ new WOQLQuery().triple(
198
+ 'v:Any Commit IRI',
199
+ '@schema:message',
200
+ 'v:Message',
201
+ ),
202
+ new WOQLQuery()
203
+ .not()
204
+ .triple(
205
+ 'v:Any Commit IRI',
206
+ '@schema:parent',
207
+ 'v:Parent IRI',
208
+ ),
209
+
210
+ );
211
+ return noparent;
212
+ };
213
+
214
+ // ============================================================================
215
+ // RDF List Operations
216
+ // ============================================================================
217
+
218
+ /**
219
+ * Collect all rdf:List elements into a single array.
220
+ * Works in two modes:
221
+ * - Generator: If listVar is unbound, binds it to the array of all rdf:first values
222
+ * - Matching: If listVar is bound to an array, succeeds only if values match
223
+ * Uses group_by to collect all values in order (path queries return in hop-count order).
224
+ * @param {string} consSubject - Variable or IRI of the list cons cell (rdf:List head)
225
+ * @param {string} listVar - Variable to bind/match the resulting list values array
226
+ * @returns {WOQLQuery} Query that binds list as array to listVar (single binding)
227
+ * @example
228
+ * // Collect all task titles from a document's task list
229
+ * const query = WOQL.and(
230
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
231
+ * WOQL.lib().rdflist_list("v:list_head", "v:all_tasks")
232
+ * );
233
+ * // Result: { bindings: [{ "v:all_tasks": ["Task A", "Task B", "Task C"] }] }
234
+ */
235
+ WOQLLibrary.prototype.rdflist_list = function (consSubject, listVar) {
236
+ // Include ALL parameters + internal variables in localize spec
237
+ const [localized, v] = new WOQLQuery().localize({
238
+ consSubject,
239
+ listVar,
240
+ cell: null,
241
+ value: null,
242
+ });
243
+ return localized(
244
+ new WOQLQuery().group_by(
245
+ [],
246
+ [v.value],
247
+ v.listVar,
248
+ new WOQLQuery().and(
249
+ new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.cell),
250
+ new WOQLQuery().triple(v.cell, 'rdf:first', v.value),
251
+ ),
252
+ ),
253
+ );
254
+ };
255
+
256
+ /**
257
+ * Internal: Get first element without localization.
258
+ * Used by other rdflist functions to avoid nested localization.
259
+ * @param {string|Var} consSubject - Variable or IRI of the list cons cell
260
+ * @param {string|Var} valueVar - Variable to bind the first value
261
+ * @returns {WOQLQuery} Query that binds first value to valueVar
262
+ * @private
263
+ */
264
+ // eslint-disable-next-line no-underscore-dangle
265
+ WOQLLibrary.prototype._rdflist_peek_raw = function (consSubject, valueVar) {
266
+ return new WOQLQuery().and(
267
+ new WOQLQuery().triple(consSubject, 'rdf:type', 'rdf:List'),
268
+ new WOQLQuery().triple(consSubject, 'rdf:first', valueVar),
269
+ );
270
+ };
271
+
272
+ /**
273
+ * Get the first element of an rdf:List (peek operation).
274
+ * Works as generator (binds value) or matcher (checks if first element matches).
275
+ * @param {string} consSubject - Variable or IRI of the list cons cell
276
+ * @param {string} valueVar - Variable to bind the first value
277
+ * @returns {WOQLQuery} Query that binds first value to valueVar
278
+ * @example
279
+ * // Get the first task from a project's task list
280
+ * const query = WOQL.and(
281
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
282
+ * WOQL.lib().rdflist_peek("v:list_head", "v:first_task")
283
+ * );
284
+ * // Result: { bindings: [{ "v:first_task": "Task A" }] }
285
+ */
286
+ WOQLLibrary.prototype.rdflist_peek = function (consSubject, valueVar) {
287
+ const [localized, v] = new WOQLQuery().localize({ consSubject, valueVar });
288
+ // eslint-disable-next-line no-underscore-dangle
289
+ return localized(this._rdflist_peek_raw(v.consSubject, v.valueVar));
290
+ };
291
+
292
+ /**
293
+ * Get the last element of an rdf:List
294
+ * Traverses to the final cons cell (whose rdf:rest is rdf:nil) and gets its rdf:first
295
+ * @param {string} consSubject - Variable or IRI of the list cons cell
296
+ * @param {string} valueVar - Variable to bind the last value
297
+ * @returns {WOQLQuery} Query that binds last value to valueVar
298
+ */
299
+ WOQLLibrary.prototype.rdflist_last = function (consSubject, valueVar) {
300
+ // Include ALL parameters + internal variables in localize spec
301
+ const [localized, v] = new WOQLQuery().localize({
302
+ consSubject,
303
+ valueVar,
304
+ last_cell: null,
305
+ });
306
+ return localized(
307
+ new WOQLQuery().and(
308
+ new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
309
+ new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.last_cell),
310
+ new WOQLQuery().triple(v.last_cell, 'rdf:rest', 'rdf:nil'),
311
+ new WOQLQuery().triple(v.last_cell, 'rdf:first', v.valueVar),
312
+ ),
313
+ );
314
+ };
315
+
316
+ /**
317
+ * Internal: Get element at 0-indexed position without localization.
318
+ * @param {string|Var} consSubject - Variable or IRI of the list cons cell
319
+ * @param {number} index - 0-based index (must be number >= 0)
320
+ * @param {string|Var} valueVar - Variable to bind the value
321
+ * @returns {WOQLQuery} Query that binds value at index to valueVar
322
+ * @private
323
+ */
324
+ // eslint-disable-next-line no-underscore-dangle
325
+ WOQLLibrary.prototype._rdflist_nth0_raw = function (consSubject, index, valueVar) {
326
+ if (index === 0) {
327
+ // eslint-disable-next-line no-underscore-dangle
328
+ return this._rdflist_peek_raw(consSubject, valueVar);
329
+ }
330
+ const pathPattern = `rdf:rest{${index},${index}}`;
331
+ return new WOQLQuery().and(
332
+ new WOQLQuery().triple(consSubject, 'rdf:type', 'rdf:List'),
333
+ new WOQLQuery().path(consSubject, pathPattern, 'v:_nth_cell'),
334
+ new WOQLQuery().triple('v:_nth_cell', 'rdf:first', valueVar),
335
+ );
336
+ };
337
+
338
+ /**
339
+ * Get element at 0-indexed position in an rdf:List.
340
+ * Works as generator (binds value) or matcher (checks if element at index matches).
341
+ * @param {string} consSubject - Variable or IRI of the list cons cell
342
+ * @param {number|string} index - 0-based index (number or variable)
343
+ * @param {string} valueVar - Variable to bind the value at that index
344
+ * @returns {WOQLQuery} Query that binds value at index to valueVar
345
+ * @example
346
+ * // Get the second task (index 1) from a project's task list
347
+ * const query = WOQL.and(
348
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
349
+ * WOQL.lib().rdflist_nth0("v:list_head", 1, "v:second_task")
350
+ * );
351
+ * // Result: { bindings: [{ "v:second_task": "Task B" }] }
352
+ */
353
+ WOQLLibrary.prototype.rdflist_nth0 = function (consSubject, index, valueVar) {
354
+ if (typeof index === 'number') {
355
+ if (index < 0) {
356
+ throw new Error('rdflist_nth0 requires index >= 0.');
357
+ }
358
+ const [localized, v] = new WOQLQuery().localize({
359
+ consSubject,
360
+ valueVar,
361
+ });
362
+ // eslint-disable-next-line no-underscore-dangle
363
+ return localized(this._rdflist_nth0_raw(v.consSubject, index, v.valueVar));
364
+ }
365
+ // Dynamic index via variable - use path with length matching
366
+ const [localized, v] = new WOQLQuery().localize({
367
+ consSubject, index, valueVar, cell: null, path: null,
368
+ });
369
+ return localized(
370
+ new WOQLQuery().and(
371
+ new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
372
+ new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.cell, v.path),
373
+ new WOQLQuery().length(v.path, v.index),
374
+ new WOQLQuery().triple(v.cell, 'rdf:first', v.valueVar),
375
+ ),
376
+ );
377
+ };
378
+
379
+ /**
380
+ * Get element at 1-indexed position in an rdf:List.
381
+ * Works as generator (binds value) or matcher (checks if element at index matches).
382
+ * @param {string} consSubject - Variable or IRI of the list cons cell
383
+ * @param {number|string} index - 1-based index (number or variable)
384
+ * @param {string} valueVar - Variable to bind the value at that index
385
+ * @returns {WOQLQuery} Query that binds value at index to valueVar
386
+ * @example
387
+ * // Get the second task (index 2, 1-based) from a project's task list
388
+ * const query = WOQL.and(
389
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
390
+ * WOQL.lib().rdflist_nth1("v:list_head", 2, "v:second_task")
391
+ * );
392
+ * // Result: { bindings: [{ "v:second_task": "Task B" }] }
393
+ */
394
+ WOQLLibrary.prototype.rdflist_nth1 = function (consSubject, index, valueVar) {
395
+ if (typeof index === 'number') {
396
+ if (index < 1) {
397
+ throw new Error('rdflist_nth1 requires index >= 1.');
398
+ }
399
+ const [localized, v] = new WOQLQuery().localize({
400
+ consSubject,
401
+ valueVar,
402
+ });
403
+ // eslint-disable-next-line no-underscore-dangle
404
+ return localized(this._rdflist_nth0_raw(v.consSubject, index - 1, v.valueVar));
405
+ }
406
+ // Dynamic index via variable - not supported with localize, would need server-side arithmetic
407
+ throw new Error('rdflist_nth1 with variable index not yet supported with localize pattern');
408
+ };
409
+
410
+ /**
411
+ * Internal: Check if a value is a member of an rdf:List without localization.
412
+ * Used by other rdflist functions to avoid nested localization.
413
+ * @param {string|Var} value - Variable or value to find in the list
414
+ * @param {string|Var} consSubject - Variable or IRI of the list cons cell
415
+ * @param {string|Var} cellVar - Cell variable (required, must be from VarsUnique)
416
+ * @returns {WOQLQuery} Query that succeeds if value is in the list
417
+ * @private
418
+ */
419
+ // eslint-disable-next-line no-underscore-dangle
420
+ WOQLLibrary.prototype._rdflist_member_raw = function (value, consSubject, cellVar) {
421
+ return new WOQLQuery().and(
422
+ new WOQLQuery().path(consSubject, 'rdf:rest*', cellVar),
423
+ new WOQLQuery().triple(cellVar, 'rdf:first', value),
424
+ );
425
+ };
426
+
427
+ /**
428
+ * Traverse an rdf:List and yield each element as a separate binding (streaming).
429
+ * Works in two modes:
430
+ * - Generator: If value is unbound, yields one binding per list element
431
+ * - Matcher: If value is bound, succeeds only if value is a member of the list
432
+ * Ideal for large lists and streaming use cases.
433
+ * @param {string} consSubject - Variable or IRI of the list cons cell (rdf:List head)
434
+ * @param {string} value - Variable to bind each value, or value to check membership
435
+ * @returns {WOQLQuery} Query that yields/matches list elements
436
+ * @example
437
+ * // Stream all tasks from a project's task list (one binding per element)
438
+ * const query = WOQL.and(
439
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
440
+ * WOQL.lib().rdflist_member("v:list_head", "v:task")
441
+ * );
442
+ * // Result: { bindings: [{ "v:task": "Task A" }, { "v:task": "Task B" }, ...] }
443
+ */
444
+ WOQLLibrary.prototype.rdflist_member = function (consSubject, value) {
445
+ // Include ALL parameters + internal variables in localize spec
446
+ const [localized, v] = new WOQLQuery().localize({
447
+ consSubject,
448
+ value,
449
+ member_cell: null,
450
+ });
451
+ return localized(
452
+ new WOQLQuery().and(
453
+ new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
454
+ // eslint-disable-next-line no-underscore-dangle
455
+ this._rdflist_member_raw(v.value, v.consSubject, v.member_cell),
456
+ ),
457
+ );
458
+ };
459
+
460
+ /**
461
+ * Internal: Get the length of an rdf:List without localization.
462
+ * Used by other rdflist functions to avoid nested localization.
463
+ * @param {string|Var} consSubject - Variable or IRI of the list cons cell
464
+ * @param {string|Var} lengthVar - Variable to bind the length
465
+ * @param {string|Var} pathVar - Path variable (required, must be from VarsUnique)
466
+ * @returns {WOQLQuery} Query that binds list length to lengthVar
467
+ * @private
468
+ */
469
+ // eslint-disable-next-line no-underscore-dangle
470
+ WOQLLibrary.prototype._rdflist_length_raw = function (consSubject, lengthVar, pathVar) {
471
+ return new WOQLQuery().and(
472
+ new WOQLQuery().triple(consSubject, 'rdf:type', 'rdf:List'),
473
+ new WOQLQuery().path(consSubject, 'rdf:rest*', 'rdf:nil', pathVar),
474
+ new WOQLQuery().length(pathVar, lengthVar),
475
+ );
476
+ };
477
+
478
+ /**
479
+ * Get the length of an rdf:List.
480
+ * Uses path query to traverse all rest links and count them.
481
+ * Works as generator (binds length) or matcher (checks if length matches).
482
+ * @param {string} consSubject - Variable or IRI of the list cons cell
483
+ * @param {string} lengthVar - Variable to bind the length
484
+ * @returns {WOQLQuery} Query that binds list length to lengthVar
485
+ * @example
486
+ * // Get the number of tasks in a project's task list
487
+ * const query = WOQL.and(
488
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
489
+ * WOQL.lib().rdflist_length("v:list_head", "v:count")
490
+ * );
491
+ * // Result: { bindings: [{ "v:count": 3 }] }
492
+ */
493
+ WOQLLibrary.prototype.rdflist_length = function (consSubject, lengthVar) {
494
+ // Include ALL parameters + internal variables in localize spec
495
+ const [localized, v] = new WOQLQuery().localize({
496
+ consSubject,
497
+ lengthVar,
498
+ length_path: null,
499
+ });
500
+ // eslint-disable-next-line no-underscore-dangle
501
+ return localized(this._rdflist_length_raw(v.consSubject, v.lengthVar, v.length_path));
502
+ };
503
+
504
+ /**
505
+ * Pop the first element from an rdf:List in-place.
506
+ * Removes the first element and promotes the second element to head position.
507
+ * The list head IRI remains unchanged - the second cons cell is removed.
508
+ * @param {string} consSubject - Variable or IRI of the list cons cell
509
+ * @param {string} valueVar - Variable to bind the popped value
510
+ * @returns {WOQLQuery} Query that pops first element and binds it to valueVar
511
+ * @example
512
+ * // Pop the first task from a project's task list
513
+ * const query = WOQL.and(
514
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
515
+ * WOQL.lib().rdflist_pop("v:list_head", "v:first_task")
516
+ * );
517
+ * // Result: list [A, B, C] becomes [B, C], binds "A" to v:first_task
518
+ */
519
+ WOQLLibrary.prototype.rdflist_pop = function (consSubject, valueVar) {
520
+ const [localized, v] = new WOQLQuery().localize({
521
+ consSubject, valueVar, second_cons: null, new_first: null, new_rest: null,
522
+ });
523
+ return localized(
524
+ new WOQLQuery().and(
525
+ new WOQLQuery().triple(v.consSubject, 'rdf:first', v.valueVar),
526
+ new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.second_cons),
527
+ new WOQLQuery().triple(v.second_cons, 'rdf:first', v.new_first),
528
+ new WOQLQuery().triple(v.second_cons, 'rdf:rest', v.new_rest),
529
+ new WOQLQuery().delete_triple(v.second_cons, 'rdf:type', 'rdf:List'),
530
+ new WOQLQuery().delete_triple(v.second_cons, 'rdf:first', v.new_first),
531
+ new WOQLQuery().delete_triple(v.second_cons, 'rdf:rest', v.new_rest),
532
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.valueVar),
533
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.second_cons),
534
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.new_first),
535
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.new_rest),
536
+ ),
537
+ );
538
+ };
539
+
540
+ /**
541
+ * Push a new element to the front of an rdf:List in-place.
542
+ * Modifies the existing cons cell: moves old first value to a new cons cell,
543
+ * sets new value as rdf:first, and links to the new cons as rdf:rest.
544
+ * The list head IRI remains unchanged.
545
+ * @param {string|Var} consSubject - Variable or IRI of the list cons cell
546
+ * @param {string|Var} value - Value to push to the front
547
+ * @returns {WOQLQuery} Query that pushes value to front of list in-place
548
+ * @example
549
+ * // Push a new task to the front of a project's task list
550
+ * const query = WOQL.and(
551
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
552
+ * WOQL.lib().rdflist_push("v:list_head", WOQL.string("New Task"))
553
+ * );
554
+ * // Result: list [A, B, C] becomes [New Task, A, B, C], head IRI unchanged
555
+ */
556
+ WOQLLibrary.prototype.rdflist_push = function (consSubject, value) {
557
+ const [localized, v] = new WOQLQuery().localize({
558
+ consSubject, value, old_first: null, old_rest: null, new_cons: null,
559
+ });
560
+ return localized(
561
+ new WOQLQuery().and(
562
+ new WOQLQuery().triple(v.consSubject, 'rdf:first', v.old_first),
563
+ new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.old_rest),
564
+ new WOQLQuery().idgen_random('terminusdb://data/Cons/', v.new_cons),
565
+ new WOQLQuery().add_triple(v.new_cons, 'rdf:type', 'rdf:List'),
566
+ new WOQLQuery().add_triple(v.new_cons, 'rdf:first', v.old_first),
567
+ new WOQLQuery().add_triple(v.new_cons, 'rdf:rest', v.old_rest),
568
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.old_first),
569
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.old_rest),
570
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.value),
571
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.new_cons),
572
+ ),
573
+ );
574
+ };
575
+
576
+ /**
577
+ * Append an element to the end of an rdf:List.
578
+ * Traverses to the last cell and adds new cell there.
579
+ * @param {string|Var} consSubject - Variable or IRI of the list head
580
+ * @param {string|Var} value - Value to append
581
+ * @param {string} [newCellVar] - Optional variable to bind the new cell
582
+ * @returns {WOQLQuery} Query that appends value to end of list
583
+ * @example
584
+ * // Append a new task to the end of a project's task list
585
+ * const query = WOQL.and(
586
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
587
+ * WOQL.lib().rdflist_append("v:list_head", WOQL.string("New Task"))
588
+ * );
589
+ * // Result: appends "New Task" to end of list
590
+ */
591
+ WOQLLibrary.prototype.rdflist_append = function (consSubject, value, newCell = 'v:_append_new_cell') {
592
+ const [localized, v] = new WOQLQuery().localize({
593
+ consSubject, value, newCell, last_cell: null,
594
+ });
595
+ return localized(
596
+ new WOQLQuery().and(
597
+ new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
598
+ new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.last_cell),
599
+ new WOQLQuery().triple(v.last_cell, 'rdf:rest', 'rdf:nil'),
600
+ new WOQLQuery().idgen_random('terminusdb://data/Cons/', v.newCell),
601
+ new WOQLQuery().delete_triple(v.last_cell, 'rdf:rest', 'rdf:nil'),
602
+ new WOQLQuery().add_triple(v.last_cell, 'rdf:rest', v.newCell),
603
+ new WOQLQuery().add_triple(v.newCell, 'rdf:type', 'rdf:List'),
604
+ new WOQLQuery().add_triple(v.newCell, 'rdf:first', v.value),
605
+ new WOQLQuery().add_triple(v.newCell, 'rdf:rest', 'rdf:nil'),
606
+ ),
607
+ );
608
+ };
609
+
610
+ /**
611
+ * Delete all cons cells of an rdf:List and return rdf:nil as the new list value.
612
+ * Since a cons cell cannot become rdf:nil, this deletes all cells and binds
613
+ * rdf:nil to a variable so the caller can update the reference to the list.
614
+ * NOTE: This only removes the list structure. To delete subdocuments
615
+ * referenced by the list, use rdflist_member with delete_document first.
616
+ * @param {string} consSubject - Variable or IRI of the list head
617
+ * @param {string} newListVar - Variable to bind rdf:nil (the empty list)
618
+ * @returns {WOQLQuery} Query that deletes all cons cells and binds rdf:nil
619
+ * @example
620
+ * // Clear a list of literals and update the reference to point to rdf:nil:
621
+ * const clearListQuery = WOQL.and(
622
+ * WOQL.triple("doc:Project/1", "tasks", "v:old_list"),
623
+ * WOQL.lib().rdflist_clear("v:old_list", "v:empty_list"),
624
+ * WOQL.delete_triple("doc:Project/1", "tasks", "v:old_list"),
625
+ * WOQL.add_triple("doc:Project/1", "tasks", "v:empty_list")
626
+ * );
627
+ *
628
+ * // Full pattern: delete subdocuments, clear list, update reference:
629
+ * const fullClearQuery = WOQL.and(
630
+ * WOQL.triple("doc:Project/1", "tasks", "v:old_list"),
631
+ * WOQL.group_by([], "v:subdoc", "v:members",
632
+ * WOQL.and(
633
+ * WOQL.lib().rdflist_member("v:old_list", "v:subdoc"),
634
+ * WOQL.delete_document("v:subdoc")
635
+ * )
636
+ * ),
637
+ * WOQL.lib().rdflist_clear("v:old_list", "v:empty_list"),
638
+ * WOQL.delete_triple("doc:Project/1", "tasks", "v:old_list"),
639
+ * WOQL.add_triple("doc:Project/1", "tasks", "v:empty_list")
640
+ * );
641
+ */
642
+ WOQLLibrary.prototype.rdflist_clear = function (consSubject, newListVar) {
643
+ const [localized, v] = new WOQLQuery().localize({
644
+ consSubject,
645
+ newListVar,
646
+ cell: null,
647
+ cell_first: null,
648
+ cell_rest: null,
649
+ head_first: null,
650
+ head_rest: null,
651
+ });
652
+ return localized(
653
+ new WOQLQuery().and(
654
+ // Bind the new list value (rdf:nil = empty list)
655
+ new WOQLQuery().equals(v.newListVar, 'rdf:nil'),
656
+ // Delete all triples from tail cons cells (those reachable via rdf:rest+)
657
+ new WOQLQuery().optional(
658
+ new WOQLQuery().and(
659
+ new WOQLQuery().path(v.consSubject, 'rdf:rest+', v.cell),
660
+ new WOQLQuery().triple(v.cell, 'rdf:type', 'rdf:List'),
661
+ new WOQLQuery().triple(v.cell, 'rdf:first', v.cell_first),
662
+ new WOQLQuery().triple(v.cell, 'rdf:rest', v.cell_rest),
663
+ new WOQLQuery().delete_triple(v.cell, 'rdf:type', 'rdf:List'),
664
+ new WOQLQuery().delete_triple(v.cell, 'rdf:first', v.cell_first),
665
+ new WOQLQuery().delete_triple(v.cell, 'rdf:rest', v.cell_rest),
666
+ ),
667
+ ),
668
+ // Delete the head cons cell completely
669
+ new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
670
+ new WOQLQuery().triple(v.consSubject, 'rdf:first', v.head_first),
671
+ new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.head_rest),
672
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:type', 'rdf:List'),
673
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.head_first),
674
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.head_rest),
675
+ ),
676
+ );
677
+ };
678
+
679
+ /**
680
+ * Insert a value at a specific position in an rdf:List.
681
+ * @param {string} consSubject - Variable or IRI of the list head
682
+ * @param {number} position - Position to insert at (0-indexed)
683
+ * @param {string|Var} value - Value to insert
684
+ * @param {string|Var} [newNodeVar] - Optional variable to bind the new node
685
+ * @returns {WOQLQuery} Query that inserts value at position
686
+ * @example
687
+ * // Insert a task at position 1 in a project's task list
688
+ * const query = WOQL.and(
689
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
690
+ * WOQL.lib().rdflist_insert("v:list_head", 1, WOQL.string("Inserted Task"))
691
+ * );
692
+ * // Result: [A, Inserted Task, B, C]
693
+ */
694
+ WOQLLibrary.prototype.rdflist_insert = function (consSubject, position, value, newNodeVar = 'v:_insert_new_node') {
695
+ if (position < 0) {
696
+ throw new Error('rdflist_insert requires position >= 0.');
697
+ }
698
+
699
+ if (position === 0) {
700
+ const [localized, v] = new WOQLQuery().localize({
701
+ consSubject, value, newNodeVar, old_first: null, old_rest: null,
702
+ });
703
+ return localized(
704
+ new WOQLQuery().and(
705
+ new WOQLQuery().triple(v.consSubject, 'rdf:first', v.old_first),
706
+ new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.old_rest),
707
+ new WOQLQuery().idgen('list_node', [v.old_first, v.consSubject], v.newNodeVar),
708
+ new WOQLQuery().add_triple(v.newNodeVar, 'rdf:type', 'rdf:List'),
709
+ new WOQLQuery().add_triple(v.newNodeVar, 'rdf:first', v.old_first),
710
+ new WOQLQuery().add_triple(v.newNodeVar, 'rdf:rest', v.old_rest),
711
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.old_first),
712
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.old_rest),
713
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.value),
714
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.newNodeVar),
715
+ ),
716
+ );
717
+ }
718
+
719
+ const [localized, v] = new WOQLQuery().localize({
720
+ consSubject, value, newNodeVar, pred_node: null, old_rest: null,
721
+ });
722
+ const restCount = position - 1;
723
+ const pathPattern = restCount === 0 ? '' : `rdf:rest{${restCount},${restCount}}`;
724
+
725
+ const findPredecessor = restCount === 0
726
+ ? new WOQLQuery().eq(v.pred_node, v.consSubject)
727
+ : new WOQLQuery().path(v.consSubject, pathPattern, v.pred_node);
728
+
729
+ return localized(
730
+ new WOQLQuery().and(
731
+ findPredecessor,
732
+ new WOQLQuery().triple(v.pred_node, 'rdf:rest', v.old_rest),
733
+ new WOQLQuery().idgen('list_node', [v.value, v.pred_node], v.newNodeVar),
734
+ new WOQLQuery().delete_triple(v.pred_node, 'rdf:rest', v.old_rest),
735
+ new WOQLQuery().add_triple(v.pred_node, 'rdf:rest', v.newNodeVar),
736
+ new WOQLQuery().add_triple(v.newNodeVar, 'rdf:type', 'rdf:List'),
737
+ new WOQLQuery().add_triple(v.newNodeVar, 'rdf:first', v.value),
738
+ new WOQLQuery().add_triple(v.newNodeVar, 'rdf:rest', v.old_rest),
739
+ ),
740
+ );
741
+ };
742
+
743
+ /**
744
+ * Drop/remove a single element from an rdf:List at a specific position.
745
+ * @param {string} consSubject - Variable or IRI of the list head
746
+ * @param {number} position - Position of element to remove (0-indexed)
747
+ * @returns {WOQLQuery} Query that removes the element at position
748
+ * @example
749
+ * // Remove the task at position 1 from a project's task list
750
+ * const query = WOQL.and(
751
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
752
+ * WOQL.lib().rdflist_drop("v:list_head", 1)
753
+ * );
754
+ * // Result: [A, C] (B removed)
755
+ */
756
+ WOQLLibrary.prototype.rdflist_drop = function (consSubject, position) {
757
+ if (position < 0) {
758
+ throw new Error('rdflist_drop requires position >= 0.');
759
+ }
760
+
761
+ if (position === 0) {
762
+ const [localized, v] = new WOQLQuery().localize({
763
+ consSubject, old_first: null, rest_node: null, next_first: null, next_rest: null,
764
+ });
765
+ return localized(
766
+ new WOQLQuery().and(
767
+ new WOQLQuery().triple(v.consSubject, 'rdf:first', v.old_first),
768
+ new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.rest_node),
769
+ new WOQLQuery().triple(v.rest_node, 'rdf:first', v.next_first),
770
+ new WOQLQuery().triple(v.rest_node, 'rdf:rest', v.next_rest),
771
+ new WOQLQuery().delete_triple(v.rest_node, 'rdf:type', 'rdf:List'),
772
+ new WOQLQuery().delete_triple(v.rest_node, 'rdf:first', v.next_first),
773
+ new WOQLQuery().delete_triple(v.rest_node, 'rdf:rest', v.next_rest),
774
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.old_first),
775
+ new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.rest_node),
776
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.next_first),
777
+ new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.next_rest),
778
+ ),
779
+ );
780
+ }
781
+
782
+ const [localized, v] = new WOQLQuery().localize({
783
+ consSubject, pred_node: null, drop_node: null, drop_first: null, drop_rest: null,
784
+ });
785
+ const restCount = position - 1;
786
+ const pathPattern = restCount === 0 ? '' : `rdf:rest{${restCount},${restCount}}`;
787
+
788
+ const findPredecessor = restCount === 0
789
+ ? new WOQLQuery().eq(v.pred_node, v.consSubject)
790
+ : new WOQLQuery().path(v.consSubject, pathPattern, v.pred_node);
791
+
792
+ return localized(
793
+ new WOQLQuery().and(
794
+ findPredecessor,
795
+ new WOQLQuery().triple(v.pred_node, 'rdf:rest', v.drop_node),
796
+ new WOQLQuery().triple(v.drop_node, 'rdf:first', v.drop_first),
797
+ new WOQLQuery().triple(v.drop_node, 'rdf:rest', v.drop_rest),
798
+ new WOQLQuery().delete_triple(v.drop_node, 'rdf:type', 'rdf:List'),
799
+ new WOQLQuery().delete_triple(v.drop_node, 'rdf:first', v.drop_first),
800
+ new WOQLQuery().delete_triple(v.drop_node, 'rdf:rest', v.drop_rest),
801
+ new WOQLQuery().delete_triple(v.pred_node, 'rdf:rest', v.drop_node),
802
+ new WOQLQuery().add_triple(v.pred_node, 'rdf:rest', v.drop_rest),
803
+ ),
804
+ );
805
+ };
806
+
807
+ /**
808
+ * Swap elements at two positions in an rdf:List by exchanging their rdf:first values.
809
+ * This is a simplified implementation that swaps values in-place without moving cons cells.
810
+ * @param {string} consSubject - Variable or IRI of the list head
811
+ * @param {number} posA - First position (0-indexed)
812
+ * @param {number} posB - Second position (0-indexed)
813
+ * @returns {WOQLQuery} Query that swaps elements at the two positions
814
+ * @example
815
+ * // Swap the first and third tasks in a project's task list
816
+ * const query = WOQL.and(
817
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
818
+ * WOQL.lib().rdflist_swap("v:list_head", 0, 2)
819
+ * );
820
+ * // Result: [C, B, A] (A and C swapped)
821
+ */
822
+ WOQLLibrary.prototype.rdflist_swap = function (consSubject, posA, posB) {
823
+ if (posA < 0 || posB < 0) {
824
+ throw new Error('rdflist_swap requires positions >= 0.');
825
+ }
826
+
827
+ if (posA === posB) {
828
+ const [localized, v] = new WOQLQuery().localize({ consSubject });
829
+ return localized(new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'));
830
+ }
831
+
832
+ const [localized, v] = new WOQLQuery().localize({
833
+ consSubject, node_a: null, node_b: null, value_a: null, value_b: null,
834
+ });
835
+
836
+ // Find node at posA
837
+ const findNodeA = posA === 0
838
+ ? new WOQLQuery().eq(v.node_a, v.consSubject)
839
+ : new WOQLQuery().path(v.consSubject, `rdf:rest{${posA},${posA}}`, v.node_a);
840
+
841
+ // Find node at posB
842
+ const findNodeB = posB === 0
843
+ ? new WOQLQuery().eq(v.node_b, v.consSubject)
844
+ : new WOQLQuery().path(v.consSubject, `rdf:rest{${posB},${posB}}`, v.node_b);
845
+
846
+ return localized(
847
+ new WOQLQuery().and(
848
+ findNodeA,
849
+ findNodeB,
850
+ new WOQLQuery().triple(v.node_a, 'rdf:first', v.value_a),
851
+ new WOQLQuery().triple(v.node_b, 'rdf:first', v.value_b),
852
+ new WOQLQuery().delete_triple(v.node_a, 'rdf:first', v.value_a),
853
+ new WOQLQuery().delete_triple(v.node_b, 'rdf:first', v.value_b),
854
+ new WOQLQuery().add_triple(v.node_a, 'rdf:first', v.value_b),
855
+ new WOQLQuery().add_triple(v.node_b, 'rdf:first', v.value_a),
856
+ ),
857
+ );
858
+ };
859
+
860
+ /**
861
+ * Create an empty rdf:List (just rdf:nil).
862
+ * @param {string} listVar - Variable to bind to rdf:nil
863
+ * @returns {WOQLQuery} Query that creates empty list
864
+ * @example
865
+ * // Create an empty list reference
866
+ * const query = WOQL.lib().rdflist_empty("v:empty_list");
867
+ * // Result: { bindings: [{ "v:empty_list": "rdf:nil" }] }
868
+ */
869
+ WOQLLibrary.prototype.rdflist_empty = function (listVar) {
870
+ return new WOQLQuery().eq(listVar, 'rdf:nil');
871
+ };
872
+
873
+ /**
874
+ * Check if an rdf:List is empty (equals rdf:nil).
875
+ * @param {string} consSubject - Variable or IRI to check
876
+ * @returns {WOQLQuery} Query that succeeds if list is empty
877
+ * @example
878
+ * // Check if a project's task list is empty
879
+ * const query = WOQL.and(
880
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
881
+ * WOQL.lib().rdflist_is_empty("v:list_head")
882
+ * );
883
+ * // Succeeds only if tasks list is rdf:nil
884
+ */
885
+ WOQLLibrary.prototype.rdflist_is_empty = function (consSubject) {
886
+ return new WOQLQuery().eq(consSubject, 'rdf:nil');
887
+ };
888
+
889
+ /**
890
+ * Extract a slice of an rdf:List (range of elements) as an array.
891
+ * Works in two modes:
892
+ * - Generator: If resultVar is unbound, binds it to the array of sliced values
893
+ * - Matcher: If resultVar is bound to an array, succeeds only if values match
894
+ * Path queries return results in hop-count order, preserving list element order.
895
+ * @param {string} consSubject - Variable or IRI of the list head
896
+ * @param {number|string} start - Start index (inclusive) or variable
897
+ * @param {number|string} end - End index (exclusive) or variable
898
+ * @param {string} resultVar - Variable to bind/match the array of sliced values
899
+ * @returns {WOQLQuery} Query that extracts/matches list slice values
900
+ * @example
901
+ * // Get the first two tasks from a project's task list
902
+ * const query = WOQL.and(
903
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
904
+ * WOQL.lib().rdflist_slice("v:list_head", 0, 2, "v:first_two")
905
+ * );
906
+ * // Result: { bindings: [{ "v:first_two": ["Task A", "Task B"] }] }
907
+ */
908
+ WOQLLibrary.prototype.rdflist_slice = function (consSubject, start, end, resultVar) {
909
+ if (start < 0 || end < 0) {
910
+ throw new Error('rdflist_slice: negative indices not supported');
911
+ }
912
+
913
+ if (start >= end) {
914
+ const [localized, v] = new WOQLQuery().localize({
915
+ consSubject,
916
+ resultVar,
917
+ });
918
+ return localized(new WOQLQuery().eq(v.resultVar, []));
919
+ }
920
+
921
+ const [localized, v] = new WOQLQuery().localize({
922
+ consSubject, resultVar, node: null, value: null,
923
+ });
924
+
925
+ let findNodes;
926
+ if (start === 0 && end === 1) {
927
+ findNodes = new WOQLQuery().eq(v.node, v.consSubject);
928
+ } else if (start === 0) {
929
+ findNodes = new WOQLQuery().path(v.consSubject, `rdf:rest{0,${end - 1}}`, v.node);
930
+ } else {
931
+ findNodes = new WOQLQuery().path(v.consSubject, `rdf:rest{${start},${end - 1}}`, v.node);
932
+ }
933
+
934
+ return localized(
935
+ new WOQLQuery().group_by(
936
+ [],
937
+ [v.value],
938
+ v.resultVar,
939
+ new WOQLQuery().and(
940
+ findNodes,
941
+ new WOQLQuery().triple(v.node, 'rdf:first', v.value),
942
+ ),
943
+ ),
944
+ );
945
+ };
946
+
947
+ /**
948
+ * Reverse an rdf:List in-place by swapping rdf:first values across nodes.
949
+ * Optimized: collects all values once via group_by, then uses slice+member
950
+ * to index into the collected list instead of double path traversal.
951
+ * @param {string} consSubject - Variable or IRI of the list to reverse
952
+ * @returns {WOQLQuery} Query that reverses the list in place
953
+ * @example
954
+ * // Reverse a project's task list in place
955
+ * const query = WOQL.and(
956
+ * WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
957
+ * WOQL.lib().rdflist_reverse("v:list_head")
958
+ * );
959
+ * // Result: [A, B, C] becomes [C, B, A]
960
+ */
961
+ WOQLLibrary.prototype.rdflist_reverse = function (consSubject) {
962
+ const [localized, v] = new WOQLQuery().localize({
963
+ consSubject,
964
+ node: null,
965
+ path: null,
966
+ old_value: null,
967
+ new_value: null,
968
+ pos: null,
969
+ len: null,
970
+ len_path: null,
971
+ all_values: null,
972
+ rev_pos: null,
973
+ rev_pos_plus1: null,
974
+ });
975
+
976
+ // Generate unique local variable name for slice result (using timestamp for uniqueness)
977
+ const sliceResultVar = `v:_slice_result_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
978
+
979
+ return localized(
980
+ new WOQLQuery().and(
981
+ // Get list length
982
+ // eslint-disable-next-line no-underscore-dangle
983
+ this._rdflist_length_raw(v.consSubject, v.len, v.len_path),
984
+ // Collect all values into a list (single path traversal)
985
+ new WOQLQuery().group_by(
986
+ [],
987
+ '_rev_collect_value',
988
+ v.all_values,
989
+ new WOQLQuery().and(
990
+ new WOQLQuery().path(v.consSubject, 'rdf:rest*', 'v:_rev_collect_node'),
991
+ new WOQLQuery().triple('v:_rev_collect_node', 'rdf:first', 'v:_rev_collect_value'),
992
+ ),
993
+ ),
994
+ // For each node, get its position and current value
995
+ new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.node, v.path),
996
+ new WOQLQuery().length(v.path, v.pos),
997
+ new WOQLQuery().triple(v.node, 'rdf:first', v.old_value),
998
+ // Calculate reversed position: revPos = (len - 1) - pos
999
+ new WOQLQuery().eval(
1000
+ new WOQLQuery().minus(new WOQLQuery().minus(v.len, 1), v.pos),
1001
+ v.rev_pos,
1002
+ ),
1003
+ // Get value at reversed position: slice to single-element array, then extract with member
1004
+ new WOQLQuery().eval(new WOQLQuery().plus(v.rev_pos, 1), v.rev_pos_plus1),
1005
+ new WOQLQuery().slice(v.all_values, sliceResultVar, v.rev_pos, v.rev_pos_plus1),
1006
+ // Use member() on WOQL array (slice returns JavaScript array, not RDF list)
1007
+ new WOQLQuery().member(v.new_value, sliceResultVar),
1008
+ // Rewrite: delete old rdf:first, add new rdf:first
1009
+ new WOQLQuery().delete_triple(v.node, 'rdf:first', v.old_value),
1010
+ new WOQLQuery().add_triple(v.node, 'rdf:first', v.new_value),
1011
+ ),
1012
+ );
1013
+ };
1014
+
1015
+ module.exports = WOQLLibrary;