bare-script 2.3.2 → 3.0.1

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/lib/library.js CHANGED
@@ -2,10 +2,12 @@
2
2
  // https://github.com/craigahobbs/bare-script/blob/main/LICENSE
3
3
 
4
4
  import {
5
- addCalculatedField, aggregateData, filterData, joinData, parseCSV, parseDatetime, sortData, topData, validateAggregation, validateData
5
+ addCalculatedField, aggregateData, filterData, joinData, parseCSV, sortData, topData, validateData
6
6
  } from './data.js';
7
7
  import {validateType, validateTypeModel} from 'schema-markdown/lib/schema.js';
8
- import {jsonStringifySortKeys} from 'schema-markdown/lib/encode.js';
8
+ import {
9
+ valueBoolean, valueCompare, valueIs, valueJSON, valueParseDatetime, valueParseInteger, valueParseNumber, valueString, valueType
10
+ } from './value.js';
9
11
  import {parseSchemaMarkdown} from 'schema-markdown/lib/parser.js';
10
12
  import {typeModel} from 'schema-markdown/lib/typeModel.js';
11
13
 
@@ -27,8 +29,12 @@ export const defaultMaxStatements = 1e9;
27
29
  // $doc: Create a copy of an array
28
30
  // $arg array: The array to copy
29
31
  // $return: The array copy
30
- function arrayCopy([array]) {
31
- return Array.isArray(array) ? [...array] : [];
32
+ function arrayCopy([array = null]) {
33
+ if (valueType(array) !== 'array') {
34
+ return null;
35
+ }
36
+
37
+ return [...array];
32
38
  }
33
39
 
34
40
 
@@ -38,10 +44,12 @@ function arrayCopy([array]) {
38
44
  // $arg array: The array to extend
39
45
  // $arg array2: The array to extend with
40
46
  // $return: The extended array
41
- function arrayExtend([array, array2]) {
42
- if (Array.isArray(array) && Array.isArray(array2)) {
43
- array.push(...array2);
47
+ function arrayExtend([array = null, array2 = null]) {
48
+ if (valueType(array) !== 'array' || valueType(array2) !== 'array') {
49
+ return null;
44
50
  }
51
+
52
+ array.push(...array2);
45
53
  return array;
46
54
  }
47
55
 
@@ -52,8 +60,13 @@ function arrayExtend([array, array2]) {
52
60
  // $arg array: The array
53
61
  // $arg index: The array element's index
54
62
  // $return: The array element
55
- function arrayGet([array, index]) {
56
- return Array.isArray(array) ? array[index] ?? null : null;
63
+ function arrayGet([array = null, index = null]) {
64
+ if (valueType(array) !== 'array' ||
65
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
66
+ return null;
67
+ }
68
+
69
+ return array[index];
57
70
  }
58
71
 
59
72
 
@@ -61,11 +74,24 @@ function arrayGet([array, index]) {
61
74
  // $group: Array
62
75
  // $doc: Find the index of a value in an array
63
76
  // $arg array: The array
64
- // $arg value: The value to find in the array
77
+ // $arg value: The value to find in the array, or a match function, f(value) -> bool
65
78
  // $arg index: Optional (default is 0). The index at which to start the search.
66
79
  // $return: The first index of the value in the array; -1 if not found.
67
- function arrayIndexOf([array, value, index = 0]) {
68
- return Array.isArray(array) ? array.indexOf(value, index) : -1;
80
+ function arrayIndexOf([array = null, value = null, index = 0], options) {
81
+ if (valueType(array) !== 'array' ||
82
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
83
+ return -1;
84
+ }
85
+
86
+ if (valueType(value) === 'function') {
87
+ for (let ix = index; ix < array.length; ix += 1) {
88
+ if (valueBoolean(value([array[ix]], options))) {
89
+ return ix;
90
+ }
91
+ }
92
+ }
93
+
94
+ return array.indexOf(value, index);
69
95
  }
70
96
 
71
97
 
@@ -75,8 +101,12 @@ function arrayIndexOf([array, value, index = 0]) {
75
101
  // $arg array: The array
76
102
  // $arg separator: The separator string
77
103
  // $return: The joined string
78
- function arrayJoin([array, separator]) {
79
- return Array.isArray(array) ? array.join(separator) : '';
104
+ function arrayJoin([array = null, separator = null]) {
105
+ if (valueType(array) !== 'array' || valueType(separator) !== 'string') {
106
+ return null;
107
+ }
108
+
109
+ return array.map((value) => valueString(value)).join(separator);
80
110
  }
81
111
 
82
112
 
@@ -84,11 +114,28 @@ function arrayJoin([array, separator]) {
84
114
  // $group: Array
85
115
  // $doc: Find the last index of a value in an array
86
116
  // $arg array: The array
87
- // $arg value: The value to find in the array
117
+ // $arg value: The value to find in the array, or a match function, f(value) -> bool
88
118
  // $arg index: Optional (default is the end of the array). The index at which to start the search.
89
119
  // $return: The last index of the value in the array; -1 if not found.
90
- function arrayLastIndexOf([array, value, index = null]) {
91
- return Array.isArray(array) ? (index === null ? array.lastIndexOf(value) : array.lastIndexOf(value, index)) : -1;
120
+ function arrayLastIndexOf([array = null, value = null, indexArg = null], options) {
121
+ let index = indexArg;
122
+ if (valueType(array) === 'array' && index === null) {
123
+ index = array.length - 1;
124
+ }
125
+ if (valueType(array) !== 'array' ||
126
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
127
+ return -1;
128
+ }
129
+
130
+ if (valueType(value) === 'function') {
131
+ for (let ix = index; ix >= 0; ix -= 1) {
132
+ if (valueBoolean(value([array[ix]], options))) {
133
+ return ix;
134
+ }
135
+ }
136
+ }
137
+
138
+ return array.lastIndexOf(value, index);
92
139
  }
93
140
 
94
141
 
@@ -96,9 +143,13 @@ function arrayLastIndexOf([array, value, index = null]) {
96
143
  // $group: Array
97
144
  // $doc: Get the length of an array
98
145
  // $arg array: The array
99
- // $return: The array's length; null if not an array
100
- function arrayLength([array]) {
101
- return Array.isArray(array) ? array.length : null;
146
+ // $return: The array's length; zero if not an array
147
+ function arrayLength([array = null]) {
148
+ if (valueType(array) !== 'array') {
149
+ return 0;
150
+ }
151
+
152
+ return array.length;
102
153
  }
103
154
 
104
155
 
@@ -119,6 +170,10 @@ function arrayNew(values) {
119
170
  // $arg value: Optional (default is 0). The value with which to fill the new array.
120
171
  // $return: The new array
121
172
  function arrayNewSize([size = 0, value = 0]) {
173
+ if (valueType(size) !== 'number' || Math.floor(size) !== size || size < 0) {
174
+ return null;
175
+ }
176
+
122
177
  return new Array(size).fill(value);
123
178
  }
124
179
 
@@ -128,8 +183,12 @@ function arrayNewSize([size = 0, value = 0]) {
128
183
  // $doc: Remove the last element of the array and return it
129
184
  // $arg array: The array
130
185
  // $return: The last element of the array; null if the array is empty.
131
- function arrayPop([array]) {
132
- return Array.isArray(array) ? array.pop() ?? null : null;
186
+ function arrayPop([array = null]) {
187
+ if (valueType(array) !== 'array' || array.length === 0) {
188
+ return null;
189
+ }
190
+
191
+ return array.pop();
133
192
  }
134
193
 
135
194
 
@@ -139,10 +198,12 @@ function arrayPop([array]) {
139
198
  // $arg array: The array
140
199
  // $arg values...: The values to add to the end of the array
141
200
  // $return: The array
142
- function arrayPush([array, ...values]) {
143
- if (Array.isArray(array)) {
144
- array.push(...values);
201
+ function arrayPush([array = null, ...values]) {
202
+ if (valueType(array) !== 'array') {
203
+ return null;
145
204
  }
205
+
206
+ array.push(...values);
146
207
  return array;
147
208
  }
148
209
 
@@ -154,10 +215,13 @@ function arrayPush([array, ...values]) {
154
215
  // $arg index: The index of the element to set
155
216
  // $arg value: The value to set
156
217
  // $return: The value
157
- function arraySet([array, index, value]) {
158
- if (Array.isArray(array)) {
159
- array[index] = value;
218
+ function arraySet([array = null, index = null, value = null]) {
219
+ if (valueType(array) !== 'array' ||
220
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= array.length) {
221
+ return null;
160
222
  }
223
+
224
+ array[index] = value;
161
225
  return value;
162
226
  }
163
227
 
@@ -167,8 +231,12 @@ function arraySet([array, index, value]) {
167
231
  // $doc: Remove the first element of the array and return it
168
232
  // $arg array: The array
169
233
  // $return: The first element of the array; null if the array is empty.
170
- function arrayShift([array]) {
171
- return Array.isArray(array) ? array.shift() ?? null : null;
234
+ function arrayShift([array = null]) {
235
+ if (valueType(array) !== 'array' || array.length === 0) {
236
+ return null;
237
+ }
238
+
239
+ return array.shift();
172
240
  }
173
241
 
174
242
 
@@ -179,8 +247,18 @@ function arrayShift([array]) {
179
247
  // $arg start: Optional (default is 0). The start index of the slice.
180
248
  // $arg end: Optional (default is the end of the array). The end index of the slice.
181
249
  // $return: The new array slice
182
- function arraySlice([array, start, end]) {
183
- return Array.isArray(array) ? array.slice(start, end) : null;
250
+ function arraySlice([array = null, start = 0, endArg = null]) {
251
+ let end = endArg;
252
+ if (valueType(array) === 'array' && end === null) {
253
+ end = array.length;
254
+ }
255
+ if (valueType(array) !== 'array' ||
256
+ valueType(start) !== 'number' || Math.floor(start) !== start || start < 0 || start > array.length ||
257
+ valueType(end) !== 'number' || Math.floor(end) !== end || end < 0 || end > array.length) {
258
+ return null;
259
+ }
260
+
261
+ return array.slice(start, end);
184
262
  }
185
263
 
186
264
 
@@ -190,8 +268,15 @@ function arraySlice([array, start, end]) {
190
268
  // $arg array: The array
191
269
  // $arg compareFn: Optional (default is null). The comparison function.
192
270
  // $return: The sorted array
193
- function arraySort([array, compareFn = null], options) {
194
- return Array.isArray(array) ? (compareFn === null ? array.sort() : array.sort((...args) => compareFn(args, options))) : null;
271
+ function arraySort([array = null, compareFn = null], options) {
272
+ if (valueType(array) !== 'array' || (compareFn !== null && valueType(compareFn) !== 'function')) {
273
+ return null;
274
+ }
275
+
276
+ if (compareFn === null) {
277
+ return array.sort(valueCompare);
278
+ }
279
+ return array.sort((...args) => compareFn(args, options));
195
280
  }
196
281
 
197
282
 
@@ -204,10 +289,14 @@ function arraySort([array, compareFn = null], options) {
204
289
  // $group: Data
205
290
  // $doc: Aggregate a data array
206
291
  // $arg data: The data array
207
- // $arg aggregation: The [aggregation model](https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation')
292
+ // $arg aggregation: The [aggregation model](model.html#var.vName='Aggregation')
208
293
  // $return: The aggregated data array
209
- function dataAggregate([data, aggregation]) {
210
- return aggregateData(data, validateAggregation(aggregation));
294
+ function dataAggregate([data = null, aggregation = null]) {
295
+ if (valueType(data) !== 'array' || (aggregation !== null && valueType(aggregation) !== 'object')) {
296
+ return null;
297
+ }
298
+
299
+ return aggregateData(data, aggregation);
211
300
  }
212
301
 
213
302
 
@@ -219,7 +308,12 @@ function dataAggregate([data, aggregation]) {
219
308
  // $arg expr: The calculated field expression
220
309
  // $arg variables: Optional (default is null). A variables object the expression evaluation.
221
310
  // $return: The updated data array
222
- function dataCalculatedField([data, fieldName, expr, variables = null], options) {
311
+ function dataCalculatedField([data = null, fieldName = null, expr = null, variables = null], options) {
312
+ if (valueType(data) !== 'array' || valueType(fieldName) !== 'string' || valueType(expr) !== 'string' ||
313
+ (variables !== null && valueType(variables) !== 'object')) {
314
+ return null;
315
+ }
316
+
223
317
  return addCalculatedField(data, fieldName, expr, variables, options);
224
318
  }
225
319
 
@@ -230,7 +324,11 @@ function dataCalculatedField([data, fieldName, expr, variables = null], options)
230
324
  // $arg expr: The filter expression
231
325
  // $arg variables: Optional (default is null). A variables object the expression evaluation.
232
326
  // $return: The filtered data array
233
- function dataFilter([data, expr, variables = null], options) {
327
+ function dataFilter([data = null, expr = null, variables = null], options) {
328
+ if (valueType(data) !== 'array' || valueType(expr) !== 'string' || (variables !== null && valueType(variables) !== 'object')) {
329
+ return null;
330
+ }
331
+
234
332
  return filterData(data, expr, variables, options);
235
333
  }
236
334
 
@@ -246,7 +344,12 @@ function dataFilter([data, expr, variables = null], options) {
246
344
  // $arg isLeftJoin: Optional (default is false). If true, perform a left join (always include left row).
247
345
  // $arg variables: Optional (default is null). A variables object for join expression evaluation.
248
346
  // $return: The joined data array
249
- function dataJoin([leftData, rightData, joinExpr, rightExpr = null, isLeftJoin = false, variables = null], options) {
347
+ function dataJoin([leftData = null, rightData = null, joinExpr = null, rightExpr = null, isLeftJoin = false, variables = null], options) {
348
+ if (valueType(leftData) !== 'array' || valueType(rightData) !== 'array' || valueType(joinExpr) !== 'string' ||
349
+ (rightExpr !== null && valueType(rightExpr) !== 'string') || (variables !== null && valueType(variables) !== 'object')) {
350
+ return null;
351
+ }
352
+
250
353
  return joinData(leftData, rightData, joinExpr, rightExpr, isLeftJoin, variables, options);
251
354
  }
252
355
 
@@ -256,8 +359,20 @@ function dataJoin([leftData, rightData, joinExpr, rightExpr = null, isLeftJoin =
256
359
  // $doc: Parse CSV text to a data array
257
360
  // $arg text...: The CSV text
258
361
  // $return: The data array
259
- function dataParseCSV(text) {
260
- const data = parseCSV(text);
362
+ function dataParseCSV(args) {
363
+ // Split the input CSV parts into lines
364
+ const lines = [];
365
+ for (const arg of args) {
366
+ if (arg === null) {
367
+ continue;
368
+ }
369
+ if (valueType(arg) !== 'string') {
370
+ return null;
371
+ }
372
+ lines.push(arg);
373
+ }
374
+
375
+ const data = parseCSV(lines);
261
376
  validateData(data, true);
262
377
  return data;
263
378
  }
@@ -269,7 +384,11 @@ function dataParseCSV(text) {
269
384
  // $arg data: The data array
270
385
  // $arg sorts: The sort field-name/descending-sort tuples
271
386
  // $return: The sorted data array
272
- function dataSort([data, sorts]) {
387
+ function dataSort([data = null, sorts = null]) {
388
+ if (valueType(data) !== 'array' || valueType(sorts) !== 'array') {
389
+ return null;
390
+ }
391
+
273
392
  return sortData(data, sorts);
274
393
  }
275
394
 
@@ -278,10 +397,16 @@ function dataSort([data, sorts]) {
278
397
  // $group: Data
279
398
  // $doc: Keep the top rows for each category
280
399
  // $arg data: The data array
281
- // $arg count: The number of rows to keep
400
+ // $arg count: The number of rows to keep (default is 1)
282
401
  // $arg categoryFields: Optional (default is null). The category fields.
283
402
  // $return: The top data array
284
- function dataTop([data, count, categoryFields = null]) {
403
+ function dataTop([data = null, count = null, categoryFields = null]) {
404
+ if (valueType(data) !== 'array' ||
405
+ valueType(count) !== 'number' || Math.floor(count) !== count || count < 1 ||
406
+ (categoryFields !== null && valueType(categoryFields) !== 'array')) {
407
+ return null;
408
+ }
409
+
285
410
  return topData(data, count, categoryFields);
286
411
  }
287
412
 
@@ -291,7 +416,11 @@ function dataTop([data, count, categoryFields = null]) {
291
416
  // $doc: Validate a data array
292
417
  // $arg data: The data array
293
418
  // $return: The validated data array
294
- function dataValidate([data]) {
419
+ function dataValidate([data = null]) {
420
+ if (valueType(data) !== 'array') {
421
+ return null;
422
+ }
423
+
295
424
  validateData(data);
296
425
  return data;
297
426
  }
@@ -306,10 +435,13 @@ function dataValidate([data]) {
306
435
  // $group: Datetime
307
436
  // $doc: Get the day of the month of a datetime
308
437
  // $arg datetime: The datetime
309
- // $arg utc: Optional (default is false). If true, return the UTC day of the month.
310
438
  // $return: The day of the month
311
- function datetimeDay([datetime, utc = false]) {
312
- return datetime instanceof Date ? (utc ? datetime.getUTCDate() : datetime.getDate()) : null;
439
+ function datetimeDay([datetime = null]) {
440
+ if (valueType(datetime) !== 'datetime') {
441
+ return null;
442
+ }
443
+
444
+ return datetime.getDate();
313
445
  }
314
446
 
315
447
 
@@ -317,10 +449,13 @@ function datetimeDay([datetime, utc = false]) {
317
449
  // $group: Datetime
318
450
  // $doc: Get the hour of a datetime
319
451
  // $arg datetime: The datetime
320
- // $arg utc: Optional (default is false). If true, return the UTC hour.
321
452
  // $return: The hour
322
- function datetimeHour([datetime, utc = false]) {
323
- return datetime instanceof Date ? (utc ? datetime.getUTCHours() : datetime.getHours()) : null;
453
+ function datetimeHour([datetime = null]) {
454
+ if (valueType(datetime) !== 'datetime') {
455
+ return null;
456
+ }
457
+
458
+ return datetime.getHours();
324
459
  }
325
460
 
326
461
 
@@ -330,51 +465,75 @@ function datetimeHour([datetime, utc = false]) {
330
465
  // $arg datetime: The datetime
331
466
  // $arg isDate: If true, format the datetime as an ISO date
332
467
  // $return: The formatted datetime string
333
- function datetimeISOFormat([datetime, isDate = false]) {
334
- let result = null;
335
- if (datetime instanceof Date) {
336
- if (isDate) {
337
- const year = datetime.getFullYear();
338
- const month = datetime.getMonth() + 1;
339
- const day = datetime.getDate();
340
- result = `${year}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
341
- } else {
342
- result = datetime.toISOString();
343
- }
468
+ function datetimeISOFormat([datetime = null, isDate = false]) {
469
+ if (valueType(datetime) !== 'datetime') {
470
+ return null;
344
471
  }
345
- return result;
472
+
473
+ if (valueBoolean(isDate)) {
474
+ const year = datetime.getFullYear();
475
+ const month = datetime.getMonth() + 1;
476
+ const day = datetime.getDate();
477
+ return `${year}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
478
+ }
479
+
480
+ return valueString(datetime);
346
481
  }
347
482
 
348
483
 
349
484
  // $function: datetimeISOParse
350
485
  // $group: Datetime
351
486
  // $doc: Parse an ISO date/time string
352
- // $arg str: The ISO date/time string
487
+ // $arg string: The ISO date/time string
353
488
  // $return: The datetime, or null if parsing fails
354
- function datetimeISOParse([str]) {
355
- return parseDatetime(str);
489
+ function datetimeISOParse([string]) {
490
+ if (valueType(string) !== 'string') {
491
+ return null;
492
+ }
493
+
494
+ return valueParseDatetime(string);
495
+ }
496
+
497
+
498
+ // $function: datetimeMillisecond
499
+ // $group: Datetime
500
+ // $doc: Get the millisecond of a datetime
501
+ // $arg datetime: The datetime
502
+ // $return: The millisecond
503
+ function datetimeMillisecond([datetime = null]) {
504
+ if (valueType(datetime) !== 'datetime') {
505
+ return null;
506
+ }
507
+
508
+ return datetime.getMilliseconds();
356
509
  }
357
510
 
358
511
 
359
512
  // $function: datetimeMinute
360
513
  // $group: Datetime
361
- // $doc: Get the number of minutes of a datetime
514
+ // $doc: Get the minute of a datetime
362
515
  // $arg datetime: The datetime
363
- // $arg utc: Optional (default is false). If true, return the UTC minutes.
364
- // $return: The number of minutes
365
- function datetimeMinute([datetime, utc = false]) {
366
- return datetime instanceof Date ? (utc ? datetime.getUTCMinutes() : datetime.getMinutes()) : null;
516
+ // $return: The minute
517
+ function datetimeMinute([datetime = null]) {
518
+ if (valueType(datetime) !== 'datetime') {
519
+ return null;
520
+ }
521
+
522
+ return datetime.getMinutes();
367
523
  }
368
524
 
369
525
 
370
526
  // $function: datetimeMonth
371
527
  // $group: Datetime
372
- // $doc: Get the number of the month (1-12) of a datetime
528
+ // $doc: Get the month (1-12) of a datetime
373
529
  // $arg datetime: The datetime
374
- // $arg utc: Optional (default is false). If true, return the UTC month.
375
- // $return: The number of the month
376
- function datetimeMonth([datetime, utc = false]) {
377
- return datetime instanceof Date ? (utc ? datetime.getUTCMonth() + 1 : datetime.getMonth() + 1) : null;
530
+ // $return: The month
531
+ function datetimeMonth([datetime = null]) {
532
+ if (valueType(datetime) !== 'datetime') {
533
+ return null;
534
+ }
535
+
536
+ return datetime.getMonth() + 1;
378
537
  }
379
538
 
380
539
 
@@ -384,29 +543,23 @@ function datetimeMonth([datetime, utc = false]) {
384
543
  // $arg year: The full year
385
544
  // $arg month: The month (1-12)
386
545
  // $arg day: The day of the month
387
- // $arg hours: Optional (default is 0). The hour (0-23)
388
- // $arg minutes: Optional (default is 0). The number of minutes.
389
- // $arg seconds: Optional (default is 0). The number of seconds.
390
- // $arg milliseconds: Optional (default is 0). The number of milliseconds.
546
+ // $arg hour: Optional (default is 0). The hour (0-23).
547
+ // $arg minute: Optional (default is 0). The minute.
548
+ // $arg second: Optional (default is 0). The second.
549
+ // $arg millisecond: Optional (default is 0). The millisecond.
391
550
  // $return: The new datetime
392
- function datetimeNew([year, month, day, hours = 0, minutes = 0, seconds = 0, milliseconds = 0]) {
393
- return new Date(year, month - 1, day, hours, minutes, seconds, milliseconds);
394
- }
395
-
551
+ function datetimeNew([year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0]) {
552
+ if (valueType(year) !== 'number' || Math.floor(year) !== year ||
553
+ valueType(month) !== 'number' || Math.floor(month) !== month ||
554
+ valueType(day) !== 'number' || Math.floor(day) !== day || day < -10000 || day > 10000 ||
555
+ valueType(hour) !== 'number' || Math.floor(hour) !== hour ||
556
+ valueType(minute) !== 'number' || Math.floor(minute) !== minute ||
557
+ valueType(second) !== 'number' || Math.floor(second) !== second ||
558
+ valueType(millisecond) !== 'number' || Math.floor(millisecond) !== millisecond) {
559
+ return null;
560
+ }
396
561
 
397
- // $function: datetimeNewUTC
398
- // $group: Datetime
399
- // $doc: Create a new UTC datetime
400
- // $arg year: The full year
401
- // $arg month: The month (1-12)
402
- // $arg day: The day of the month
403
- // $arg hours: Optional (default is 0). The hour (0-23)
404
- // $arg minutes: Optional (default is 0). The number of minutes.
405
- // $arg seconds: Optional (default is 0). The number of seconds.
406
- // $arg milliseconds: Optional (default is 0). The number of milliseconds.
407
- // $return: The new UTC datetime
408
- function datetimeNewUTC([year, month, day, hours = 0, minutes = 0, seconds = 0, milliseconds = 0]) {
409
- return new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds, milliseconds));
562
+ return new Date(year, month - 1, day, hour, minute, second, millisecond);
410
563
  }
411
564
 
412
565
 
@@ -421,12 +574,15 @@ function datetimeNow() {
421
574
 
422
575
  // $function: datetimeSecond
423
576
  // $group: Datetime
424
- // $doc: Get the number of seconds of a datetime
577
+ // $doc: Get the second of a datetime
425
578
  // $arg datetime: The datetime
426
- // $arg utc: Optional (default is false). If true, return the UTC seconds.
427
- // $return: The number of seconds
428
- function datetimeSecond([datetime, utc = false]) {
429
- return datetime instanceof Date ? (utc ? datetime.getUTCSeconds() : datetime.getSeconds()) : null;
579
+ // $return: The second
580
+ function datetimeSecond([datetime = null]) {
581
+ if (valueType(datetime) !== 'datetime') {
582
+ return null;
583
+ }
584
+
585
+ return datetime.getSeconds();
430
586
  }
431
587
 
432
588
 
@@ -444,10 +600,13 @@ function datetimeToday() {
444
600
  // $group: Datetime
445
601
  // $doc: Get the full year of a datetime
446
602
  // $arg datetime: The datetime
447
- // $arg utc: Optional (default is false). If true, return the UTC year.
448
603
  // $return: The full year
449
- function datetimeYear([datetime, utc = false]) {
450
- return datetime instanceof Date ? (utc ? datetime.getUTCFullYear() : datetime.getFullYear()) : null;
604
+ function datetimeYear([datetime = null]) {
605
+ if (valueType(datetime) !== 'datetime') {
606
+ return null;
607
+ }
608
+
609
+ return datetime.getFullYear();
451
610
  }
452
611
 
453
612
 
@@ -455,12 +614,17 @@ function datetimeYear([datetime, utc = false]) {
455
614
  // JSON functions
456
615
  //
457
616
 
617
+
458
618
  // $function: jsonParse
459
619
  // $group: JSON
460
620
  // $doc: Convert a JSON string to an object
461
621
  // $arg string: The JSON string
462
622
  // $return: The object
463
- function jsonParse([string]) {
623
+ function jsonParse([string = null]) {
624
+ if (valueType(string) !== 'string') {
625
+ return null;
626
+ }
627
+
464
628
  return JSON.parse(string);
465
629
  }
466
630
 
@@ -469,10 +633,14 @@ function jsonParse([string]) {
469
633
  // $group: JSON
470
634
  // $doc: Convert an object to a JSON string
471
635
  // $arg value: The object
472
- // $arg space: Optional (default is null). The indentation string or number.
636
+ // $arg indent: Optional (default is null). The indentation number.
473
637
  // $return: The JSON string
474
- function jsonStringify([value, space]) {
475
- return jsonStringifySortKeys(value, space);
638
+ function jsonStringify([value = null, indent = null]) {
639
+ if (indent !== null && (valueType(indent) !== 'number' || Math.floor(indent) !== indent || indent < 1)) {
640
+ return null;
641
+ }
642
+
643
+ return valueJSON(value, indent);
476
644
  }
477
645
 
478
646
 
@@ -485,7 +653,11 @@ function jsonStringify([value, space]) {
485
653
  // $doc: Compute the absolute value of a number
486
654
  // $arg x: The number
487
655
  // $return: The absolute value of the number
488
- function mathAbs([x]) {
656
+ function mathAbs([x = null]) {
657
+ if (valueType(x) !== 'number') {
658
+ return null;
659
+ }
660
+
489
661
  return Math.abs(x);
490
662
  }
491
663
 
@@ -495,7 +667,11 @@ function mathAbs([x]) {
495
667
  // $doc: Compute the arccosine, in radians, of a number
496
668
  // $arg x: The number
497
669
  // $return: The arccosine, in radians, of the number
498
- function mathAcos([x]) {
670
+ function mathAcos([x = null]) {
671
+ if (valueType(x) !== 'number') {
672
+ return null;
673
+ }
674
+
499
675
  return Math.acos(x);
500
676
  }
501
677
 
@@ -505,7 +681,11 @@ function mathAcos([x]) {
505
681
  // $doc: Compute the arcsine, in radians, of a number
506
682
  // $arg x: The number
507
683
  // $return: The arcsine, in radians, of the number
508
- function mathAsin([x]) {
684
+ function mathAsin([x = null]) {
685
+ if (valueType(x) !== 'number') {
686
+ return null;
687
+ }
688
+
509
689
  return Math.asin(x);
510
690
  }
511
691
 
@@ -515,7 +695,11 @@ function mathAsin([x]) {
515
695
  // $doc: Compute the arctangent, in radians, of a number
516
696
  // $arg x: The number
517
697
  // $return: The arctangent, in radians, of the number
518
- function mathAtan([x]) {
698
+ function mathAtan([x = null]) {
699
+ if (valueType(x) !== 'number') {
700
+ return null;
701
+ }
702
+
519
703
  return Math.atan(x);
520
704
  }
521
705
 
@@ -526,7 +710,11 @@ function mathAtan([x]) {
526
710
  // $arg y: The Y-coordinate of the point
527
711
  // $arg x: The X-coordinate of the point
528
712
  // $return: The angle, in radians
529
- function mathAtan2([y, x]) {
713
+ function mathAtan2([y = null, x = null]) {
714
+ if (valueType(y) !== 'number' || valueType(x) !== 'number') {
715
+ return null;
716
+ }
717
+
530
718
  return Math.atan2(y, x);
531
719
  }
532
720
 
@@ -536,7 +724,11 @@ function mathAtan2([y, x]) {
536
724
  // $doc: Compute the ceiling of a number (round up to the next highest integer)
537
725
  // $arg x: The number
538
726
  // $return: The ceiling of the number
539
- function mathCeil([x]) {
727
+ function mathCeil([x = null]) {
728
+ if (valueType(x) !== 'number') {
729
+ return null;
730
+ }
731
+
540
732
  return Math.ceil(x);
541
733
  }
542
734
 
@@ -546,7 +738,11 @@ function mathCeil([x]) {
546
738
  // $doc: Compute the cosine of an angle, in radians
547
739
  // $arg x: The angle, in radians
548
740
  // $return: The cosine of the angle
549
- function mathCos([x]) {
741
+ function mathCos([x = null]) {
742
+ if (valueType(x) !== 'number') {
743
+ return null;
744
+ }
745
+
550
746
  return Math.cos(x);
551
747
  }
552
748
 
@@ -556,7 +752,11 @@ function mathCos([x]) {
556
752
  // $doc: Compute the floor of a number (round down to the next lowest integer)
557
753
  // $arg x: The number
558
754
  // $return: The floor of the number
559
- function mathFloor([x]) {
755
+ function mathFloor([x = null]) {
756
+ if (valueType(x) !== 'number') {
757
+ return null;
758
+ }
759
+
560
760
  return Math.floor(x);
561
761
  }
562
762
 
@@ -566,7 +766,11 @@ function mathFloor([x]) {
566
766
  // $doc: Compute the natural logarithm (base e) of a number
567
767
  // $arg x: The number
568
768
  // $return: The natural logarithm of the number
569
- function mathLn([x]) {
769
+ function mathLn([x = null]) {
770
+ if (valueType(x) !== 'number' || x <= 0) {
771
+ return null;
772
+ }
773
+
570
774
  return Math.log(x);
571
775
  }
572
776
 
@@ -577,7 +781,11 @@ function mathLn([x]) {
577
781
  // $arg x: The number
578
782
  // $arg base: Optional (default is 10). The logarithm base.
579
783
  // $return: The logarithm of the number
580
- function mathLog([x, base = 10]) {
784
+ function mathLog([x = null, base = 10]) {
785
+ if (valueType(x) !== 'number' || x <= 0 || valueType(base) !== 'number' || base <= 0 || base === 1) {
786
+ return null;
787
+ }
788
+
581
789
  return Math.log(x) / Math.log(base);
582
790
  }
583
791
 
@@ -588,6 +796,10 @@ function mathLog([x, base = 10]) {
588
796
  // $arg values...: The values
589
797
  // $return: The maximum value
590
798
  function mathMax(values) {
799
+ if (values.some((value) => valueType(value) !== 'number')) {
800
+ return null;
801
+ }
802
+
591
803
  return Math.max(...values);
592
804
  }
593
805
 
@@ -598,6 +810,10 @@ function mathMax(values) {
598
810
  // $arg values...: The values
599
811
  // $return: The minimum value
600
812
  function mathMin(values) {
813
+ if (values.some((value) => valueType(value) !== 'number')) {
814
+ return null;
815
+ }
816
+
601
817
  return Math.min(...values);
602
818
  }
603
819
 
@@ -626,7 +842,11 @@ function mathRandom() {
626
842
  // $arg x: The number
627
843
  // $arg digits: Optional (default is 0). The number of decimal digits to round to.
628
844
  // $return: The rounded number
629
- function mathRound([x, digits = 0]) {
845
+ function mathRound([x = null, digits = 0]) {
846
+ if (valueType(x) !== 'number' || valueType(digits) !== 'number' || Math.floor(digits) !== digits || digits < 0) {
847
+ return null;
848
+ }
849
+
630
850
  const multiplier = 10 ** digits;
631
851
  return Math.round(x * multiplier) / multiplier;
632
852
  }
@@ -637,7 +857,11 @@ function mathRound([x, digits = 0]) {
637
857
  // $doc: Compute the sign of a number
638
858
  // $arg x: The number
639
859
  // $return: -1 for a negative number, 1 for a positive number, and 0 for zero
640
- function mathSign([x]) {
860
+ function mathSign([x = null]) {
861
+ if (valueType(x) !== 'number') {
862
+ return null;
863
+ }
864
+
641
865
  return Math.sign(x);
642
866
  }
643
867
 
@@ -647,7 +871,11 @@ function mathSign([x]) {
647
871
  // $doc: Compute the sine of an angle, in radians
648
872
  // $arg x: The angle, in radians
649
873
  // $return: The sine of the angle
650
- function mathSin([x]) {
874
+ function mathSin([x = null]) {
875
+ if (valueType(x) !== 'number') {
876
+ return null;
877
+ }
878
+
651
879
  return Math.sin(x);
652
880
  }
653
881
 
@@ -657,7 +885,11 @@ function mathSin([x]) {
657
885
  // $doc: Compute the square root of a number
658
886
  // $arg x: The number
659
887
  // $return: The square root of the number
660
- function mathSqrt([x]) {
888
+ function mathSqrt([x = null]) {
889
+ if (valueType(x) !== 'number' || x < 0) {
890
+ return null;
891
+ }
892
+
661
893
  return Math.sqrt(x);
662
894
  }
663
895
 
@@ -667,7 +899,11 @@ function mathSqrt([x]) {
667
899
  // $doc: Compute the tangent of an angle, in radians
668
900
  // $arg x: The angle, in radians
669
901
  // $return: The tangent of the angle
670
- function mathTan([x]) {
902
+ function mathTan([x = null]) {
903
+ if (valueType(x) !== 'number') {
904
+ return null;
905
+ }
906
+
671
907
  return Math.tan(x);
672
908
  }
673
909
 
@@ -676,13 +912,18 @@ function mathTan([x]) {
676
912
  // Number functions
677
913
  //
678
914
 
915
+
679
916
  // $function: numberParseFloat
680
917
  // $group: Number
681
918
  // $doc: Parse a string as a floating point number
682
919
  // $arg string: The string
683
920
  // $return: The number
684
- function numberParseFloat([string]) {
685
- return Number.parseFloat(string);
921
+ function numberParseFloat([string = null]) {
922
+ if (valueType(string) !== 'string') {
923
+ return null;
924
+ }
925
+
926
+ return valueParseNumber(string);
686
927
  }
687
928
 
688
929
 
@@ -692,8 +933,12 @@ function numberParseFloat([string]) {
692
933
  // $arg string: The string
693
934
  // $arg radix: Optional (default is 10). The number base.
694
935
  // $return: The integer
695
- function numberParseInt([string, radix = 10]) {
696
- return Number.parseInt(string, radix);
936
+ function numberParseInt([string = null, radix = 10]) {
937
+ if (valueType(string) !== 'string' || valueType(radix) !== 'number' || Math.floor(radix) !== radix || radix < 2 || radix > 36) {
938
+ return null;
939
+ }
940
+
941
+ return valueParseInteger(string, radix);
697
942
  }
698
943
 
699
944
 
@@ -704,13 +949,14 @@ function numberParseInt([string, radix = 10]) {
704
949
  // $arg digits: Optional (default is 2). The number of digits to appear after the decimal point.
705
950
  // $arg trim: Optional (default is false). If true, trim trailing zeroes and decimal point.
706
951
  // $return: The fixed-point notation string
707
- function numberToFixed([x, digits = 2, trim = false]) {
708
- let result = null;
709
- if (typeof x === 'number') {
710
- result = x.toFixed(digits);
711
- if (trim) {
712
- result = result.replace(rNumberCleanup, '');
713
- }
952
+ function numberToFixed([x = null, digits = 2, trim = false]) {
953
+ if (valueType(x) !== 'number' || valueType(digits) !== 'number' || Math.floor(digits) !== digits || digits < 0) {
954
+ return null;
955
+ }
956
+
957
+ let result = x.toFixed(digits);
958
+ if (valueBoolean(trim)) {
959
+ result = result.replace(rNumberCleanup, '');
714
960
  }
715
961
  return result;
716
962
  }
@@ -722,17 +968,19 @@ const rNumberCleanup = /\.0*$/;
722
968
  // Object functions
723
969
  //
724
970
 
971
+
725
972
  // $function: objectAssign
726
973
  // $group: Object
727
974
  // $doc: Assign the keys/values of one object to another
728
975
  // $arg object: The object to assign to
729
976
  // $arg object2: The object to assign
730
977
  // $return: The updated object
731
- function objectAssign([object, object2]) {
732
- if (object !== null && typeof object === 'object' && !Array.isArray(object) &&
733
- object2 !== null && typeof object2 === 'object' && !Array.isArray(object2)) {
734
- Object.assign(object, object2);
978
+ function objectAssign([object = null, object2 = null]) {
979
+ if (valueType(object) !== 'object' || valueType(object2) !== 'object') {
980
+ return null;
735
981
  }
982
+
983
+ Object.assign(object, object2);
736
984
  return object;
737
985
  }
738
986
 
@@ -742,8 +990,12 @@ function objectAssign([object, object2]) {
742
990
  // $doc: Create a copy of an object
743
991
  // $arg object: The object to copy
744
992
  // $return: The object copy
745
- function objectCopy([object]) {
746
- return (object !== null && typeof object === 'object' && !Array.isArray(object) ? {...object} : {});
993
+ function objectCopy([object = null]) {
994
+ if (valueType(object) !== 'object') {
995
+ return null;
996
+ }
997
+
998
+ return {...object};
747
999
  }
748
1000
 
749
1001
 
@@ -752,10 +1004,13 @@ function objectCopy([object]) {
752
1004
  // $doc: Delete an object key
753
1005
  // $arg object: The object
754
1006
  // $arg key: The key to delete
755
- function objectDelete([object, key]) {
756
- if (object !== null && typeof object === 'object' && !Array.isArray(object)) {
757
- delete object[key];
1007
+ function objectDelete([object = null, key = null]) {
1008
+ if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1009
+ return null;
758
1010
  }
1011
+
1012
+ delete object[key];
1013
+ return null;
759
1014
  }
760
1015
 
761
1016
 
@@ -766,8 +1021,12 @@ function objectDelete([object, key]) {
766
1021
  // $arg key: The key
767
1022
  // $arg defaultValue: The default value (optional)
768
1023
  // $return: The value or null if the key does not exist
769
- function objectGet([object, key, defaultValue = null]) {
770
- return object !== null && typeof object === 'object' ? (Object.hasOwn(object, key) ? object[key] : defaultValue) : defaultValue;
1024
+ function objectGet([object = null, key = null, defaultValue = null]) {
1025
+ if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1026
+ return defaultValue;
1027
+ }
1028
+
1029
+ return object[key] ?? defaultValue;
771
1030
  }
772
1031
 
773
1032
 
@@ -777,8 +1036,12 @@ function objectGet([object, key, defaultValue = null]) {
777
1036
  // $arg object: The object
778
1037
  // $arg key: The key
779
1038
  // $return: true if the object contains the key, false otherwise
780
- function objectHas([object, key]) {
781
- return object !== null && typeof object === 'object' && Object.hasOwn(object, key);
1039
+ function objectHas([object = null, key = null]) {
1040
+ if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1041
+ return false;
1042
+ }
1043
+
1044
+ return key in object;
782
1045
  }
783
1046
 
784
1047
 
@@ -786,9 +1049,13 @@ function objectHas([object, key]) {
786
1049
  // $group: Object
787
1050
  // $doc: Get an object's keys
788
1051
  // $arg object: The object
789
- // $return: The array of keys; null if not an object
790
- function objectKeys([object]) {
791
- return object !== null && typeof object === 'object' && !Array.isArray(object) ? Object.keys(object) : null;
1052
+ // $return: The array of keys
1053
+ function objectKeys([object = null]) {
1054
+ if (valueType(object) !== 'object') {
1055
+ return null;
1056
+ }
1057
+
1058
+ return Object.keys(object);
792
1059
  }
793
1060
 
794
1061
 
@@ -797,10 +1064,15 @@ function objectKeys([object]) {
797
1064
  // $doc: Create a new object
798
1065
  // $arg keyValues...: The object's initial key and value pairs
799
1066
  // $return: The new object
800
- function objectNew(keyValues) {
1067
+ function objectNew(keyValues = null) {
801
1068
  const object = {};
802
1069
  for (let ix = 0; ix < keyValues.length; ix += 2) {
803
- object[keyValues[ix]] = (ix + 1 < keyValues.length ? keyValues[ix + 1] : null);
1070
+ const key = keyValues[ix];
1071
+ const value = ix + 1 < keyValues.length ? keyValues[ix + 1] : null;
1072
+ if (valueType(key) !== 'string') {
1073
+ return null;
1074
+ }
1075
+ object[key] = value;
804
1076
  }
805
1077
  return object;
806
1078
  }
@@ -813,10 +1085,12 @@ function objectNew(keyValues) {
813
1085
  // $arg key: The key
814
1086
  // $arg value: The value to set
815
1087
  // $return: The value to set
816
- function objectSet([object, key, value]) {
817
- if (object !== null && typeof object === 'object' && !Array.isArray(object)) {
818
- object[key] = value;
1088
+ function objectSet([object = null, key = null, value = null]) {
1089
+ if (valueType(object) !== 'object' || valueType(key) !== 'string') {
1090
+ return null;
819
1091
  }
1092
+
1093
+ object[key] = value;
820
1094
  return value;
821
1095
  }
822
1096
 
@@ -831,8 +1105,12 @@ function objectSet([object, key, value]) {
831
1105
  // $doc: Escape a string for use in a regular expression
832
1106
  // $arg string: The string to escape
833
1107
  // $return: The escaped string
834
- function regexEscape([string]) {
835
- return typeof string === 'string' ? string.replace(rRegexEscape, '\\$&') : null;
1108
+ function regexEscape([string = null]) {
1109
+ if (valueType(string) !== 'string') {
1110
+ return null;
1111
+ }
1112
+
1113
+ return string.replace(rRegexEscape, '\\$&');
836
1114
  }
837
1115
 
838
1116
  const rRegexEscape = /[.*+?^${}()|[\]\\]/g;
@@ -843,11 +1121,19 @@ const rRegexEscape = /[.*+?^${}()|[\]\\]/g;
843
1121
  // $doc: Find the first match of a regular expression in a string
844
1122
  // $arg regex: The regular expression
845
1123
  // $arg string: The string
846
- // $return: The [match object
847
- // $return: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value)
848
- // $return: or null if no matches are found
849
- function regexMatch([regex, string]) {
850
- return typeof string === 'string' ? string.match(regex) : null;
1124
+ // $return: The [match object](model.html#var.vName='RegexMatch'), or null if no matches are found
1125
+ function regexMatch([regex = null, string = null]) {
1126
+ if (valueType(regex) !== 'regex' || valueType(string) !== 'string') {
1127
+ return null;
1128
+ }
1129
+
1130
+ // Match?
1131
+ const match = string.match(regex);
1132
+ if (match === null) {
1133
+ return null;
1134
+ }
1135
+
1136
+ return regexMatchGroups(match);
851
1137
  }
852
1138
 
853
1139
 
@@ -856,23 +1142,82 @@ function regexMatch([regex, string]) {
856
1142
  // $doc: Find all matches of regular expression in a string
857
1143
  // $arg regex: The regular expression
858
1144
  // $arg string: The string
859
- // $return: The [match object
860
- // $return: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value)
861
- // $return: array or null if no matches are found
862
- function regexMatchAll([regex, string]) {
863
- return typeof string === 'string' ? Array.from(string.matchAll(regex)) : null;
1145
+ // $return: The array of [match objects](model.html#var.vName='RegexMatch')
1146
+ function regexMatchAll([regex = null, string = null]) {
1147
+ if (valueType(regex) !== 'regex' || valueType(string) !== 'string') {
1148
+ return null;
1149
+ }
1150
+
1151
+ // Re-compile the regex with the "g" flag, if necessary
1152
+ const regexGlobal = (regex.flags.indexOf('g') !== -1 ? regex : new RegExp(regex.source, `${regex.flags}g`));
1153
+
1154
+ return Array.from(string.matchAll(regexGlobal)).map((match) => regexMatchGroups(match));
864
1155
  }
865
1156
 
866
1157
 
1158
+ // Helper function to create a match model from a metch object
1159
+ function regexMatchGroups(match) {
1160
+ const groups = {};
1161
+ for (let ixMatch = 0; ixMatch < match.length; ixMatch++) {
1162
+ groups[`${ixMatch}`] = match[ixMatch];
1163
+ }
1164
+ if (match.groups) {
1165
+ for (const groupName of Object.keys(match.groups)) {
1166
+ groups[groupName] = match.groups[groupName];
1167
+ }
1168
+ }
1169
+ return {
1170
+ 'index': match.index,
1171
+ 'input': match.input,
1172
+ 'groups': groups
1173
+ };
1174
+ }
1175
+
1176
+
1177
+ // The regex match model
1178
+ export const regexMatchTypes = parseSchemaMarkdown(`\
1179
+ group "RegexMatch"
1180
+
1181
+
1182
+ # A regex match model
1183
+ struct RegexMatch
1184
+
1185
+ # The zero-based index of the match in the input string
1186
+ int(>= 0) index
1187
+
1188
+ # The input string
1189
+ string input
1190
+
1191
+ # The matched groups. The "0" key is the full match text. Ordered (non-named) groups use keys "1", "2", and so on.
1192
+ string{} groups
1193
+ `);
1194
+
1195
+
867
1196
  // $function: regexNew
868
1197
  // $group: Regex
869
1198
  // $doc: Create a regular expression
870
- // $arg pattern: The regular expression pattern string
871
- // $arg flags: The [regular expression flags
872
- // $arg flags: ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
1199
+ // eslint-disable-next-line max-len
1200
+ // $arg pattern: The [regular expression pattern string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#writing_a_regular_expression_pattern)
1201
+ // $arg flags: The regular expression flags. The string may contain the following characters:
1202
+ // $arg flags: - **i** - case-insensitive search
1203
+ // $arg flags: - **m** - multi-line search - "^" and "$" matches next to newline characters
1204
+ // $arg flags: - **s** - "." matches newline characters
873
1205
  // $return: The regular expression or null if the pattern is invalid
874
- function regexNew([pattern, flags]) {
875
- return new RegExp(pattern, flags);
1206
+ function regexNew([pattern = null, flags = null]) {
1207
+ if (valueType(pattern) !== 'string' || (flags !== null && valueType(flags) !== 'string')) {
1208
+ return null;
1209
+ }
1210
+
1211
+ // Valid flags mask?
1212
+ if (flags !== null) {
1213
+ for (const flag of flags) {
1214
+ if (flag !== 'i' && flag !== 'm' && flag !== 's') {
1215
+ return null;
1216
+ }
1217
+ }
1218
+ }
1219
+
1220
+ return flags !== null ? new RegExp(pattern, flags) : new RegExp(pattern);
876
1221
  }
877
1222
 
878
1223
 
@@ -883,8 +1228,8 @@ function regexNew([pattern, flags]) {
883
1228
  // $arg string: The string
884
1229
  // $arg substr: The replacement string
885
1230
  // $return: The updated string
886
- function regexReplace([regex, string, substr]) {
887
- if (!(regex instanceof RegExp) || typeof string !== 'string' || typeof substr !== 'string') {
1231
+ function regexReplace([regex = null, string = null, substr = null]) {
1232
+ if (valueType(regex) !== 'regex' || valueType(string) !== 'string' || valueType(substr) !== 'string') {
888
1233
  return null;
889
1234
  }
890
1235
 
@@ -901,29 +1246,20 @@ function regexReplace([regex, string, substr]) {
901
1246
  // $arg regex: The regular expression
902
1247
  // $arg string: The string
903
1248
  // $return: The array of split parts
904
- function regexSplit([regex, string]) {
905
- if (!(regex instanceof RegExp) || typeof string !== 'string') {
1249
+ function regexSplit([regex = null, string = null]) {
1250
+ if (valueType(regex) !== 'regex' || valueType(string) !== 'string') {
906
1251
  return null;
907
1252
  }
908
1253
 
909
1254
  return string.split(regex);
910
1255
  }
911
1256
 
912
- // $function: regexTest
913
- // $group: Regex
914
- // $doc: Test if a regular expression matches a string
915
- // $arg regex: The regular expression
916
- // $arg string: The string
917
- // $return: true if the regular expression matches, false otherwise
918
- function regexTest([regex, string]) {
919
- return regex instanceof RegExp ? regex.test(string) : null;
920
- }
921
-
922
1257
 
923
1258
  //
924
1259
  // Schema functions
925
1260
  //
926
1261
 
1262
+
927
1263
  // $function: schemaParse
928
1264
  // $group: Schema
929
1265
  // $doc: Parse the [Schema Markdown](https://craigahobbs.github.io/schema-markdown-js/language/) text
@@ -943,7 +1279,12 @@ function schemaParse(lines) {
943
1279
  // $arg types: Optional. The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types').
944
1280
  // $arg filename: Optional (default is ""). The file name.
945
1281
  // $return: The schema's [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
946
- function schemaParseEx([lines, types = {}, filename = '']) {
1282
+ function schemaParseEx([lines = null, types = {}, filename = '']) {
1283
+ if (!(valueType(lines) === 'array' || valueType(lines) === 'string') ||
1284
+ valueType(types) !== 'object' || valueType(filename) !== 'string') {
1285
+ return null;
1286
+ }
1287
+
947
1288
  return parseSchemaMarkdown(lines, {types, filename});
948
1289
  }
949
1290
 
@@ -964,7 +1305,12 @@ function schemaTypeModel() {
964
1305
  // $arg typeName: The type name
965
1306
  // $arg value: The object to validate
966
1307
  // $return: The validated object or null if validation fails
967
- function schemaValidate([types, typeName, value]) {
1308
+ function schemaValidate([types = null, typeName = null, value = null]) {
1309
+ if (valueType(types) !== 'object' || valueType(typeName) !== 'string') {
1310
+ return null;
1311
+ }
1312
+
1313
+ validateTypeModel(types);
968
1314
  return validateType(types, typeName, value);
969
1315
  }
970
1316
 
@@ -974,7 +1320,11 @@ function schemaValidate([types, typeName, value]) {
974
1320
  // $doc: Validate a [Schema Markdown Type Model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
975
1321
  // $arg types: The [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types') to validate
976
1322
  // $return: The validated [type model](https://craigahobbs.github.io/schema-markdown-doc/doc/#var.vName='Types')
977
- function schemaValidateTypeModel([types]) {
1323
+ function schemaValidateTypeModel([types = null]) {
1324
+ if (valueType(types) !== 'object') {
1325
+ return null;
1326
+ }
1327
+
978
1328
  return validateTypeModel(types);
979
1329
  }
980
1330
 
@@ -990,8 +1340,13 @@ function schemaValidateTypeModel([types]) {
990
1340
  // $arg string: The string
991
1341
  // $arg index: The character index
992
1342
  // $return: The character code
993
- function stringCharCodeAt([string, index]) {
994
- return typeof string === 'string' ? string.charCodeAt(index) : null;
1343
+ function stringCharCodeAt([string = null, index = null]) {
1344
+ if (valueType(string) !== 'string' ||
1345
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= string.length) {
1346
+ return null;
1347
+ }
1348
+
1349
+ return string.charCodeAt(index);
995
1350
  }
996
1351
 
997
1352
 
@@ -999,10 +1354,14 @@ function stringCharCodeAt([string, index]) {
999
1354
  // $group: String
1000
1355
  // $doc: Determine if a string ends with a search string
1001
1356
  // $arg string: The string
1002
- // $arg searchString: The search string
1357
+ // $arg search: The search string
1003
1358
  // $return: true if the string ends with the search string, false otherwise
1004
- function stringEndsWith([string, searchString]) {
1005
- return typeof string === 'string' ? string.endsWith(searchString) : null;
1359
+ function stringEndsWith([string = null, search = null]) {
1360
+ if (valueType(string) !== 'string' || valueType(search) !== 'string') {
1361
+ return null;
1362
+ }
1363
+
1364
+ return string.endsWith(search);
1006
1365
  }
1007
1366
 
1008
1367
 
@@ -1011,7 +1370,11 @@ function stringEndsWith([string, searchString]) {
1011
1370
  // $doc: Create a string of characters from character codes
1012
1371
  // $arg charCodes...: The character codes
1013
1372
  // $return: The string of characters
1014
- function stringFromCharCode(charCodes) {
1373
+ function stringFromCharCode(charCodes = null) {
1374
+ if (charCodes.some((code) => valueType(code) !== 'number' || Math.floor(code) !== code || code < 0)) {
1375
+ return null;
1376
+ }
1377
+
1015
1378
  return String.fromCharCode(...charCodes);
1016
1379
  }
1017
1380
 
@@ -1020,11 +1383,16 @@ function stringFromCharCode(charCodes) {
1020
1383
  // $group: String
1021
1384
  // $doc: Find the first index of a search string in a string
1022
1385
  // $arg string: The string
1023
- // $arg searchString: The search string
1386
+ // $arg search: The search string
1024
1387
  // $arg index: Optional (default is 0). The index at which to start the search.
1025
1388
  // $return: The first index of the search string; -1 if not found.
1026
- function stringIndexOf([string, searchString, index]) {
1027
- return typeof string === 'string' ? string.indexOf(searchString, index) : -1;
1389
+ function stringIndexOf([string = null, search = null, index = 0]) {
1390
+ if (valueType(string) !== 'string' || valueType(search) !== 'string' ||
1391
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= string.length) {
1392
+ return -1;
1393
+ }
1394
+
1395
+ return string.indexOf(search, index);
1028
1396
  }
1029
1397
 
1030
1398
 
@@ -1032,11 +1400,20 @@ function stringIndexOf([string, searchString, index]) {
1032
1400
  // $group: String
1033
1401
  // $doc: Find the last index of a search string in a string
1034
1402
  // $arg string: The string
1035
- // $arg searchString: The search string
1403
+ // $arg search: The search string
1036
1404
  // $arg index: Optional (default is the end of the string). The index at which to start the search.
1037
1405
  // $return: The last index of the search string; -1 if not found.
1038
- function stringLastIndexOf([string, searchString, index]) {
1039
- return typeof string === 'string' ? string.lastIndexOf(searchString, index) : -1;
1406
+ function stringLastIndexOf([string = null, search = null, indexArg = null]) {
1407
+ let index = indexArg;
1408
+ if (index === null && valueType(string) === 'string') {
1409
+ index = string.length - 1;
1410
+ }
1411
+ if (valueType(string) !== 'string' || valueType(search) !== 'string' ||
1412
+ valueType(index) !== 'number' || Math.floor(index) !== index || index < 0 || index >= string.length) {
1413
+ return -1;
1414
+ }
1415
+
1416
+ return string.lastIndexOf(search, index);
1040
1417
  }
1041
1418
 
1042
1419
 
@@ -1044,9 +1421,13 @@ function stringLastIndexOf([string, searchString, index]) {
1044
1421
  // $group: String
1045
1422
  // $doc: Get the length of a string
1046
1423
  // $arg string: The string
1047
- // $return: The string's length; null if not a string
1048
- function stringLength([string]) {
1049
- return typeof string === 'string' ? string.length : null;
1424
+ // $return: The string's length; zero if not a string
1425
+ function stringLength([string = null]) {
1426
+ if (valueType(string) !== 'string') {
1427
+ return 0;
1428
+ }
1429
+
1430
+ return string.length;
1050
1431
  }
1051
1432
 
1052
1433
 
@@ -1055,8 +1436,12 @@ function stringLength([string]) {
1055
1436
  // $doc: Convert a string to lower-case
1056
1437
  // $arg string: The string
1057
1438
  // $return: The lower-case string
1058
- function stringLower([string]) {
1059
- return typeof string === 'string' ? string.toLowerCase() : null;
1439
+ function stringLower([string = null]) {
1440
+ if (valueType(string) !== 'string') {
1441
+ return null;
1442
+ }
1443
+
1444
+ return string.toLowerCase();
1060
1445
  }
1061
1446
 
1062
1447
 
@@ -1065,8 +1450,8 @@ function stringLower([string]) {
1065
1450
  // $doc: Create a new string from a value
1066
1451
  // $arg value: The value
1067
1452
  // $return: The new string
1068
- function stringNew([value]) {
1069
- return `${value}`;
1453
+ function stringNew([value = null]) {
1454
+ return valueString(value);
1070
1455
  }
1071
1456
 
1072
1457
 
@@ -1076,8 +1461,12 @@ function stringNew([value]) {
1076
1461
  // $arg string: The string to repeat
1077
1462
  // $arg count: The number of times to repeat the string
1078
1463
  // $return: The repeated string
1079
- function stringRepeat([string, count]) {
1080
- return typeof string === 'string' ? string.repeat(count) : null;
1464
+ function stringRepeat([string = null, count = null]) {
1465
+ if (valueType(string) !== 'string' || valueType(count) !== 'number' || Math.floor(count) !== count || count < 0) {
1466
+ return null;
1467
+ }
1468
+
1469
+ return string.repeat(count);
1081
1470
  }
1082
1471
 
1083
1472
 
@@ -1088,14 +1477,11 @@ function stringRepeat([string, count]) {
1088
1477
  // $arg substr: The string to replace
1089
1478
  // $arg newSubstr: The replacement string
1090
1479
  // $return: The updated string
1091
- function stringReplace([string, substr, newSubstr], options) {
1092
- if (typeof string !== 'string') {
1480
+ function stringReplace([string = null, substr = null, newSubstr = null]) {
1481
+ if (valueType(string) !== 'string' || valueType(substr) !== 'string' || valueType(newSubstr) !== 'string') {
1093
1482
  return null;
1094
1483
  }
1095
- if (typeof newSubstr === 'function') {
1096
- const replacerFunction = (...args) => newSubstr(args, options);
1097
- return string.replaceAll(substr, replacerFunction);
1098
- }
1484
+
1099
1485
  return string.replaceAll(substr, newSubstr);
1100
1486
  }
1101
1487
 
@@ -1104,11 +1490,21 @@ function stringReplace([string, substr, newSubstr], options) {
1104
1490
  // $group: String
1105
1491
  // $doc: Copy a portion of a string
1106
1492
  // $arg string: The string
1107
- // $arg start: Optional (default is 0). The start index of the slice.
1493
+ // $arg start: The start index of the slice
1108
1494
  // $arg end: Optional (default is the end of the string). The end index of the slice.
1109
1495
  // $return: The new string slice
1110
- function stringSlice([string, beginIndex, endIndex]) {
1111
- return typeof string === 'string' ? string.slice(beginIndex, endIndex) : null;
1496
+ function stringSlice([string = null, begin = null, endArg = null]) {
1497
+ let end = endArg;
1498
+ if (end === null && valueType(string) === 'string') {
1499
+ end = string.length;
1500
+ }
1501
+ if (valueType(string) !== 'string' ||
1502
+ valueType(begin) !== 'number' || Math.floor(begin) !== begin || begin < 0 || begin > string.length ||
1503
+ valueType(end) !== 'number' || Math.floor(end) !== end || end < 0 || end > string.length) {
1504
+ return null;
1505
+ }
1506
+
1507
+ return string.slice(begin, end);
1112
1508
  }
1113
1509
 
1114
1510
 
@@ -1116,11 +1512,14 @@ function stringSlice([string, beginIndex, endIndex]) {
1116
1512
  // $group: String
1117
1513
  // $doc: Split a string
1118
1514
  // $arg string: The string to split
1119
- // $arg separator: The separator string or regular expression
1120
- // $arg limit: The maximum number of strings to split into
1515
+ // $arg separator: The separator string
1121
1516
  // $return: The array of split-out strings
1122
- function stringSplit([string, separator, limit]) {
1123
- return typeof string === 'string' ? string.split(separator, limit) : null;
1517
+ function stringSplit([string = null, separator = null]) {
1518
+ if (valueType(string) !== 'string' || valueType(separator) !== 'string') {
1519
+ return null;
1520
+ }
1521
+
1522
+ return string.split(separator);
1124
1523
  }
1125
1524
 
1126
1525
 
@@ -1128,10 +1527,14 @@ function stringSplit([string, separator, limit]) {
1128
1527
  // $group: String
1129
1528
  // $doc: Determine if a string starts with a search string
1130
1529
  // $arg string: The string
1131
- // $arg searchString: The search string
1530
+ // $arg search: The search string
1132
1531
  // $return: true if the string starts with the search string, false otherwise
1133
- function stringStartsWith([string, searchString]) {
1134
- return typeof string === 'string' ? string.startsWith(searchString) : null;
1532
+ function stringStartsWith([string = null, search = null]) {
1533
+ if (valueType(string) !== 'string' || valueType(search) !== 'string') {
1534
+ return null;
1535
+ }
1536
+
1537
+ return string.startsWith(search);
1135
1538
  }
1136
1539
 
1137
1540
 
@@ -1140,8 +1543,12 @@ function stringStartsWith([string, searchString]) {
1140
1543
  // $doc: Trim the whitespace from the beginning and end of a string
1141
1544
  // $arg string: The string
1142
1545
  // $return: The trimmed string
1143
- function stringTrim([string]) {
1144
- return typeof string === 'string' ? string.trim() : null;
1546
+ function stringTrim([string = null]) {
1547
+ if (valueType(string) !== 'string') {
1548
+ return null;
1549
+ }
1550
+
1551
+ return string.trim();
1145
1552
  }
1146
1553
 
1147
1554
 
@@ -1150,8 +1557,12 @@ function stringTrim([string]) {
1150
1557
  // $doc: Convert a string to upper-case
1151
1558
  // $arg string: The string
1152
1559
  // $return: The upper-case string
1153
- function stringUpper([string]) {
1154
- return typeof string === 'string' ? string.toUpperCase() : null;
1560
+ function stringUpper([string = null]) {
1561
+ if (valueType(string) !== 'string') {
1562
+ return null;
1563
+ }
1564
+
1565
+ return string.toUpperCase();
1155
1566
  }
1156
1567
 
1157
1568
 
@@ -1160,51 +1571,128 @@ function stringUpper([string]) {
1160
1571
  //
1161
1572
 
1162
1573
 
1574
+ // $function: systemBoolean
1575
+ // $group: System
1576
+ // $doc: Interpret a value as a boolean
1577
+ // $arg value: The value
1578
+ // $return: true or false
1579
+ function systemBoolean([value = null]) {
1580
+ return valueBoolean(value);
1581
+ }
1582
+
1583
+
1584
+ // $function: systemCompare
1585
+ // $group: System
1586
+ // $doc: Compare two values
1587
+ // $arg left: The left value
1588
+ // $arg right: The right value
1589
+ // $return: -1 if the left value is less than the right value, 0 if equal, and 1 if greater than
1590
+ function systemCompare([left = null, right = null]) {
1591
+ return valueCompare(left, right);
1592
+ }
1593
+
1594
+
1163
1595
  // $function: systemFetch
1164
1596
  // $group: System
1165
- // $doc: Retrieve a remote JSON or text resource
1166
- // $arg url: The resource URL or array of URLs
1167
- // $arg options: Optional (default is null). The [fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters).
1168
- // $arg isText: Optional (default is false). If true, retrieve the resource as text.
1169
- // $return: The resource object/string or array of objects/strings; null if an error occurred.
1170
- async function systemFetch([url, fetchOptions = null, isText = false], options) {
1171
- const isArray = Array.isArray(url);
1172
- const urls = (isArray ? url : [url]).map((mURL) => (options !== null && 'urlFn' in options ? options.urlFn(mURL) : mURL));
1173
- const responses = await Promise.all(urls.map(async (fURL) => {
1597
+ // $doc: Retrieve a URL resource
1598
+ // $arg url: The resource URL, [request model](model.html#var.vName='SystemFetchRequest'), or array of URL and
1599
+ // $arg url: [request model](model.html#var.vName='SystemFetchRequest')
1600
+ // $return: The response string or array of strings; null if an error occurred
1601
+ async function systemFetch([url = null], options) {
1602
+ // Options
1603
+ const fetchFn = options !== null ? (options.fetchFn ?? null) : null;
1604
+ const logFn = options !== null && options.debug ? (options.logFn ?? null) : null;
1605
+ const urlFn = options !== null ? (options.urlFn ?? null) : null;
1606
+
1607
+ // Validate the URL argument
1608
+ const requests = [];
1609
+ let isResponseArray = false;
1610
+ if (valueType(url) === 'string') {
1611
+ requests.push({'url': url});
1612
+ } else if (valueType(url) === 'object') {
1613
+ requests.push(validateType(systemFetchTypes, 'SystemFetchRequest', url));
1614
+ } else if (valueType(url) === 'array') {
1615
+ isResponseArray = true;
1616
+ for (const urlItem of url) {
1617
+ if (valueType(urlItem) === 'string') {
1618
+ requests.push({'url': urlItem});
1619
+ } else {
1620
+ requests.push(validateType(systemFetchTypes, 'SystemFetchRequest', urlItem));
1621
+ }
1622
+ }
1623
+ } else {
1624
+ return null;
1625
+ }
1626
+
1627
+ // Fetch in parallel
1628
+ const fetchResponses = await Promise.all(requests.map((request) => {
1174
1629
  try {
1175
- return 'fetchFn' in options ? await (fetchOptions ? options.fetchFn(fURL, fetchOptions) : options.fetchFn(fURL)) : null;
1630
+ const fetchURL = urlFn !== null ? urlFn(request.url) : request.url;
1631
+ const fetchOptions = {};
1632
+ if ((request.body ?? null) !== null) {
1633
+ fetchOptions.body = request.body;
1634
+ }
1635
+ if ((request.headers ?? null) !== null) {
1636
+ fetchOptions.headers = request.headers;
1637
+ }
1638
+ return fetchFn !== null ? fetchFn(fetchURL, fetchOptions) : null;
1176
1639
  } catch {
1177
1640
  return null;
1178
1641
  }
1179
1642
  }));
1180
- const values = await Promise.all(responses.map(async (response) => {
1643
+ const responses = await Promise.all(fetchResponses.map(async (fetchResponse, ixResponse) => {
1644
+ let response;
1181
1645
  try {
1182
- return response !== null && response.ok ? await (isText ? response.text() : response.json()) : null;
1646
+ response = fetchResponse !== null && fetchResponse.ok ? await fetchResponse.text() : null;
1183
1647
  } catch {
1184
- return null;
1648
+ response = null;
1185
1649
  }
1186
- }));
1187
1650
 
1188
- // Log failures
1189
- for (const [ixValue, value] of values.entries()) {
1190
- if (value === null && options !== null && 'logFn' in options && options.debug) {
1191
- const errorURL = urls[ixValue];
1192
- options.logFn(`BareScript: Function "systemFetch" failed for ${isText ? 'text' : 'JSON'} resource "${errorURL}"`);
1651
+ // Log failure
1652
+ if (response === null && logFn !== null) {
1653
+ const errorURL = requests[ixResponse].url;
1654
+ logFn(`BareScript: Function "systemFetch" failed for resource "${errorURL}"`);
1193
1655
  }
1194
- }
1195
1656
 
1196
- return isArray ? values : values[0];
1657
+ return response;
1658
+ }));
1659
+
1660
+ return isResponseArray ? responses : responses[0];
1197
1661
  }
1198
1662
 
1199
1663
 
1664
+ // The aggregation model
1665
+ export const systemFetchTypes = parseSchemaMarkdown(`\
1666
+ group "SystemFetch"
1667
+
1668
+
1669
+ # A fetch request model
1670
+ struct SystemFetchRequest
1671
+
1672
+ # The resource URL
1673
+ string url
1674
+
1675
+ # The request body
1676
+ optional string body
1677
+
1678
+ # The request headers
1679
+ optional string{} headers
1680
+ `);
1681
+
1682
+
1200
1683
  // $function: systemGlobalGet
1201
1684
  // $group: System
1202
1685
  // $doc: Get a global variable value
1203
1686
  // $arg name: The global variable name
1687
+ // $arg defaultValue: The default value (optional)
1204
1688
  // $return: The global variable's value or null if it does not exist
1205
- function systemGlobalGet([name], options) {
1689
+ function systemGlobalGet([name = null, defaultValue = null], options) {
1690
+ if (valueType(name) !== 'string') {
1691
+ return defaultValue;
1692
+ }
1693
+
1206
1694
  const globals = (options !== null ? (options.globals ?? null) : null);
1207
- return (globals !== null ? (globals[name] ?? null) : null);
1695
+ return globals !== null ? (globals[name] ?? defaultValue) : defaultValue;
1208
1696
  }
1209
1697
 
1210
1698
 
@@ -1214,24 +1702,37 @@ function systemGlobalGet([name], options) {
1214
1702
  // $arg name: The global variable name
1215
1703
  // $arg value: The global variable's value
1216
1704
  // $return: The global variable's value
1217
- function systemGlobalSet([name, value], options) {
1218
- if (options !== null) {
1219
- const globals = options.globals ?? null;
1220
- if (globals !== null) {
1221
- globals[name] = value;
1222
- }
1705
+ function systemGlobalSet([name = null, value = null], options) {
1706
+ if (valueType(name) !== 'string') {
1707
+ return null;
1708
+ }
1709
+
1710
+ const globals = (options !== null ? (options.globals ?? null) : null);
1711
+ if (globals !== null) {
1712
+ globals[name] = value;
1223
1713
  }
1224
1714
  return value;
1225
1715
  }
1226
1716
 
1227
1717
 
1718
+ // $function: systemIs
1719
+ // $group: System
1720
+ // $doc: Test if one value is the same object as another
1721
+ // $arg value1: The first value
1722
+ // $arg value2: The second value
1723
+ // $return: true if values are the same object, false otherwise
1724
+ function systemIs([value1 = null, value2 = null]) {
1725
+ return valueIs(value1, value2);
1726
+ }
1727
+
1728
+
1228
1729
  // $function: systemLog
1229
1730
  // $group: System
1230
1731
  // $doc: Log a message to the console
1231
- // $arg string: The message
1232
- function systemLog([string], options) {
1732
+ // $arg message: The log message
1733
+ function systemLog([message = null], options) {
1233
1734
  if (options !== null && 'logFn' in options) {
1234
- options.logFn(string);
1735
+ options.logFn(valueString(message));
1235
1736
  }
1236
1737
  }
1237
1738
 
@@ -1239,10 +1740,10 @@ function systemLog([string], options) {
1239
1740
  // $function: systemLogDebug
1240
1741
  // $group: System
1241
1742
  // $doc: Log a message to the console, if in debug mode
1242
- // $arg string: The message
1243
- function systemLogDebug([string], options) {
1743
+ // $arg message: The log message
1744
+ function systemLogDebug([message = null], options) {
1244
1745
  if (options !== null && 'logFn' in options && options.debug) {
1245
- options.logFn(string);
1746
+ options.logFn(valueString(message));
1246
1747
  }
1247
1748
  }
1248
1749
 
@@ -1254,7 +1755,11 @@ function systemLogDebug([string], options) {
1254
1755
  // $arg func: The function
1255
1756
  // $arg args...: The function arguments
1256
1757
  // $return: The new function called with "args"
1257
- function systemPartial([func, ...args]) {
1758
+ function systemPartial([func = null, ...args]) {
1759
+ if (valueType(func) !== 'function' || args.length < 1) {
1760
+ return null;
1761
+ }
1762
+
1258
1763
  return (argsExtra, options) => func([...args, ...argsExtra], options);
1259
1764
  }
1260
1765
 
@@ -1265,20 +1770,8 @@ function systemPartial([func, ...args]) {
1265
1770
  // $arg value: The value
1266
1771
  // $return: The type string of the value.
1267
1772
  // $return: Valid values are: 'array', 'boolean', 'datetime', 'function', 'null', 'number', 'object', 'regex', 'string'.
1268
- function systemType([value]) {
1269
- const type = typeof value;
1270
- if (type === 'object') {
1271
- if (value === null) {
1272
- return 'null';
1273
- } else if (Array.isArray(value)) {
1274
- return 'array';
1275
- } else if (value instanceof Date) {
1276
- return 'datetime';
1277
- } else if (value instanceof RegExp) {
1278
- return 'regex';
1279
- }
1280
- }
1281
- return type;
1773
+ function systemType([value = null]) {
1774
+ return valueType(value);
1282
1775
  }
1283
1776
 
1284
1777
 
@@ -1293,9 +1786,13 @@ function systemType([value]) {
1293
1786
  // $arg url: The URL string
1294
1787
  // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1295
1788
  // $return: The encoded URL string
1296
- function urlEncode([url, extra = true]) {
1789
+ function urlEncode([url = null, extra = true]) {
1790
+ if (valueType(url) !== 'string') {
1791
+ return null;
1792
+ }
1793
+
1297
1794
  let urlEncoded = encodeURI(url);
1298
- if (extra) {
1795
+ if (valueBoolean(extra)) {
1299
1796
  // Replace ')' with '%29' for Markdown links
1300
1797
  urlEncoded = urlEncoded.replaceAll(')', '%29');
1301
1798
  }
@@ -1309,13 +1806,17 @@ function urlEncode([url, extra = true]) {
1309
1806
  // $arg url: The URL component string
1310
1807
  // $arg extra: Optional (default is true). If true, encode extra characters for wider compatibility.
1311
1808
  // $return: The encoded URL component string
1312
- function urlEncodeComponent([urlComponent, extra = true]) {
1313
- let urlComponentEncoded = encodeURIComponent(urlComponent);
1314
- if (extra) {
1809
+ function urlEncodeComponent([url = null, extra = true]) {
1810
+ if (valueType(url) !== 'string') {
1811
+ return null;
1812
+ }
1813
+
1814
+ let urlEncoded = encodeURIComponent(url);
1815
+ if (valueBoolean(extra)) {
1315
1816
  // Replace ')' with '%29' for Markdown links
1316
- urlComponentEncoded = urlComponentEncoded.replaceAll(')', '%29');
1817
+ urlEncoded = urlEncoded.replaceAll(')', '%29');
1317
1818
  }
1318
- return urlComponentEncoded;
1819
+ return urlEncoded;
1319
1820
  }
1320
1821
 
1321
1822
 
@@ -1348,10 +1849,10 @@ export const scriptFunctions = {
1348
1849
  datetimeHour,
1349
1850
  datetimeISOFormat,
1350
1851
  datetimeISOParse,
1852
+ datetimeMillisecond,
1351
1853
  datetimeMinute,
1352
1854
  datetimeMonth,
1353
1855
  datetimeNew,
1354
- datetimeNewUTC,
1355
1856
  datetimeNow,
1356
1857
  datetimeSecond,
1357
1858
  datetimeToday,
@@ -1394,7 +1895,6 @@ export const scriptFunctions = {
1394
1895
  regexNew,
1395
1896
  regexReplace,
1396
1897
  regexSplit,
1397
- regexTest,
1398
1898
  schemaParse,
1399
1899
  schemaParseEx,
1400
1900
  schemaTypeModel,
@@ -1415,9 +1915,12 @@ export const scriptFunctions = {
1415
1915
  stringStartsWith,
1416
1916
  stringTrim,
1417
1917
  stringUpper,
1918
+ systemBoolean,
1919
+ systemCompare,
1418
1920
  systemFetch,
1419
1921
  systemGlobalGet,
1420
1922
  systemGlobalSet,
1923
+ systemIs,
1421
1924
  systemLog,
1422
1925
  systemLogDebug,
1423
1926
  systemPartial,