@nxtedition/lib 26.8.7 → 27.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,600 +0,0 @@
1
- import moment from 'moment-timezone'
2
- import * as rxjs from 'rxjs'
3
- import JSON5 from 'json5'
4
- import fp from 'lodash/fp.js'
5
- import split from 'split-string'
6
- import { makeWeakCache } from '../../weakCache.js'
7
-
8
- const RETURN = {}
9
-
10
- function asFilter(transform, predicate, obj) {
11
- return fp.mapValues(
12
- (factory) =>
13
- (...args) => {
14
- const filter = factory(...args)
15
- return (value, options) => {
16
- try {
17
- value = transform ? transform(value, options) : value
18
- return !predicate || predicate(value, options) ? filter(value, options) : null
19
- } catch {
20
- return null
21
- }
22
- }
23
- },
24
- obj,
25
- )
26
- }
27
-
28
- export default function ({ ds, logger, platform: { hasha } } = {}) {
29
- const FILTERS = {
30
- // any
31
- ...asFilter(null, null, {
32
- boolean: () => (value) => Boolean(value),
33
- toJSON: (indent) => (value) => JSON.stringify(value, null, indent),
34
- toJSON5: () => (value) => JSON5.stringify(value),
35
- json: (indent) => (value) => JSON.stringify(value, null, indent),
36
- isEmpty: () => (value) => fp.isEmpty(value),
37
- json5: () => (value) => JSON5.stringify(value),
38
- string: () => (value) => {
39
- if (moment.isMoment(value) || moment.isDate(value)) {
40
- return value.toISOString()
41
- }
42
- if (typeof value?.toString() === 'function') {
43
- return value.toString()
44
- }
45
- return String(value)
46
- },
47
- number: () => (value) => {
48
- if (moment.isMoment(value) || moment.isDate(value)) {
49
- return value.valueOf()
50
- }
51
- return Number(value)
52
- },
53
- date: (tz) => (value) => (tz ? moment.tz(value, tz) : moment(value)),
54
- array: () => (value) => [value],
55
- value: (value) => () => value,
56
- int:
57
- (fallback = null, radix) =>
58
- (value) => {
59
- // TODO (fix): Validate arguments...
60
-
61
- const int = parseInt(value, radix)
62
- return Number.isFinite(int) ? int : fallback
63
- },
64
- float:
65
- (fallback = null) =>
66
- (value) => {
67
- // TODO (fix): Validate arguments...
68
-
69
- const float = parseFloat(value)
70
- return Number.isFinite(float) ? float : fallback
71
- },
72
- default: (defaultValue, notJustNully) => (value) =>
73
- notJustNully ? (!value ? defaultValue : value) : value == null ? defaultValue : value,
74
- else: (x) => (value) => value || x,
75
- eq: (x) => (value) => value === x,
76
- ne: (x) => (value) => value !== x,
77
- not: () => (value) => !value,
78
- and: (x) => (value) => x && value,
79
- or: (x) => (value) => x || value,
80
- isDate: () => (value) => moment.isMoment(value),
81
- isArray: () => (value) => Array.isArray(value),
82
- isEqual: (x) => (value) => fp.isEqual(value, x),
83
- isNil: () => (value) => value == null,
84
- isNumber: () => (value) => Number.isFinite(value),
85
- isString: () => (value) => fp.isString(value),
86
- ternary: (a, b) => (value) => (value ? a : b),
87
- cond: (a, b) => (value) => (value ? a : b),
88
- hasha: (options) => (value) => hasha(JSON.stringify(value), options || {}),
89
- hashaint: (options) => (value) =>
90
- parseInt(hasha(JSON.stringify(value), options || {}).slice(-13), 16),
91
- return: () => (value) => value || RETURN,
92
- add:
93
- (...args) =>
94
- (value) => {
95
- if (args.length === 2) {
96
- value = moment(value)
97
- return value.isValid() ? value.add(...args) : null
98
- } else {
99
- value = Number(value)
100
- return Number.isFinite(value) ? value + args[0] : null
101
- }
102
- },
103
- sub:
104
- (...args) =>
105
- (value) => {
106
- if (args.length === 2) {
107
- value = moment(value)
108
- return value.isValid() ? value.subtract(...args) : null
109
- } else {
110
- value = Number(value)
111
- return Number.isFinite(value) ? value - args[0] : null
112
- }
113
- },
114
- }),
115
- // number
116
- ...asFilter(
117
- (value) => Number(value),
118
- (value) => Number.isFinite(value),
119
- {
120
- le: (x) => (value) => value <= x,
121
- lt: (x) => (value) => value < x,
122
- ge: (x) => (value) => value >= x,
123
- gt: (x) => (value) => value > x,
124
- mul: (x) => (value) => value * x,
125
- div: (x) => (value) => value / x,
126
- mod: (x) => (value) => value % x,
127
- abs: () => (value) => Math.abs(value),
128
- round: () => (value) => Math.round(value),
129
- floor: () => (value) => Math.floor(value),
130
- ceil: () => (value) => Math.ceil(value),
131
- clamp: (min, max) => (value) => Math.max(min, Math.min(max, value)),
132
- },
133
- ),
134
- ...asFilter(
135
- (value) =>
136
- (Array.isArray(value) ? value : [value])
137
- .map((x) => Number(x))
138
- .filter((x) => Number.isFinite(x)),
139
- (value) => value.every((x) => Number.isFinite(x)),
140
- {
141
- min:
142
- (...args) =>
143
- (value) =>
144
- Math.min(...value, ...args),
145
- max:
146
- (...args) =>
147
- (value) =>
148
- Math.max(...value, ...args),
149
- },
150
- ),
151
- // date
152
- ...asFilter(
153
- (value) => moment(value),
154
- (value) => value.isValid(),
155
- {
156
- moment: (format, tz) => {
157
- // TODO (fix): Validate arguments...
158
- return (value) => {
159
- value = moment(value)
160
- if (tz) {
161
- value = value.tz(tz)
162
- }
163
- return format ? value.format(format) : value.toISOString()
164
- }
165
- },
166
- startOf: (startOf) => {
167
- // TODO (fix): Validate arguments...
168
- return (value) => value.startOf(startOf)
169
- },
170
- endOf: (endOf) => {
171
- // TODO (fix): Validate arguments...
172
- return (value) => value.endOf(endOf)
173
- },
174
- },
175
- ),
176
- // ds
177
- ...asFilter(null, (value) => value && (typeof value === 'string' || Array.isArray(value)), {
178
- ds: (postfix, path, state) => {
179
- // TODO (fix): Validate arguments...
180
- if (!ds) {
181
- throw new Error('invalid argument')
182
- }
183
-
184
- if (typeof state === 'string') {
185
- state = ds.record.STATE[state.toUpperCase()]
186
- }
187
-
188
- function observe(id, options) {
189
- if (id == null) {
190
- return rxjs.of(path ? undefined : {})
191
- }
192
-
193
- const name = (id || '') + (postfix || '')
194
- if (options?.observe) {
195
- return options.observe(name, path, state)
196
- } else {
197
- state ??=
198
- name.startsWith('{') || name.endsWith('?') ? ds.record.PROVIDER : ds.record.SERVER
199
-
200
- return ds.record.observe(name, path, state)
201
- }
202
- }
203
-
204
- return (value, options) => {
205
- if (value && fp.isString(value)) {
206
- return observe(value, options)
207
- }
208
-
209
- if (Array.isArray(value)) {
210
- value = value.filter((x) => x && fp.isString(x))
211
- return value.length
212
- ? rxjs.combineLatest(value.map((id) => observe(id, options)))
213
- : rxjs.of([])
214
- }
215
-
216
- return null
217
- }
218
- },
219
- }),
220
- // string
221
- ...asFilter(
222
- (value) =>
223
- value == null || fp.isPlainObject(value) || Array.isArray(value) ? '' : String(value),
224
- (value) => typeof value === 'string',
225
- {
226
- fromJSON: () => (value) => (value ? JSON.parse(value) : null),
227
- fromJSON5: () => (value) => (value ? JSON5.parse(value) : null),
228
- toSlate: () => (value) => ({
229
- object: 'value',
230
- document: {
231
- object: 'document',
232
- data: {},
233
- nodes: value.split('\n').map((line) => ({
234
- object: 'block',
235
- type: 'paragraph',
236
- data: {},
237
- nodes: [
238
- {
239
- object: 'text',
240
- leaves: [
241
- {
242
- object: 'leaf',
243
- text: line,
244
- marks: [],
245
- },
246
- ],
247
- },
248
- ],
249
- })),
250
- },
251
- }),
252
- append: (post) => (value) => value + post,
253
- prepend: (pre) => (value) => pre + value,
254
- asset: (type, _return = true) => {
255
- // TODO (fix): Validate arguments...
256
- if (!ds) {
257
- throw new Error('invalid argument')
258
- }
259
-
260
- return (value) =>
261
- value
262
- ? ds.record.observe2(`${value}:asset.rawTypes?`).pipe(
263
- rxjs.map(({ state, data }) => ({
264
- state,
265
- data: fp.includes(type, data.value),
266
- })),
267
- rxjs.filter(({ state, data }) => data || state >= ds.record.PROVIDER),
268
- rxjs.pluck('data'),
269
- rxjs.distinctUntilChanged(),
270
- rxjs.map((isType) => (isType ? value : _return ? RETURN : null)),
271
- )
272
- : null
273
- },
274
- lower: () => (value) => value.toLowerCase(),
275
- upper: () => (value) => value.toUpperCase(),
276
- match: (...args) => {
277
- // TODO (fix): Validate argument count...
278
-
279
- if (args.some((val) => typeof val !== 'string')) {
280
- throw new Error('invalid argument')
281
- }
282
-
283
- return (value) => value.match(new RegExp(...args))
284
- },
285
- capitalize: () => (value) => fp.capitalize(value),
286
- split: (delimiter) => (value) => value.split(delimiter),
287
- title: () => (value) => fp.startCase(value),
288
- replace: (...args) => {
289
- // TODO (fix): Validate argument count...
290
-
291
- if (args.some((val) => typeof val !== 'string')) {
292
- throw new Error('invalid argument')
293
- }
294
-
295
- if (args.length === 3) {
296
- const [pattern, flags, str] = args
297
- const expr = new RegExp(pattern, flags)
298
- return (value) => value.replace(expr, str)
299
- } else if (args.length === 2) {
300
- const [a, b] = args
301
- return (value) => value.replace(a, b)
302
- } else {
303
- throw new Error('invalid argument')
304
- }
305
- },
306
- padStart:
307
- (...args) =>
308
- (value) =>
309
- value.padStart(...args),
310
- padEnd:
311
- (...args) =>
312
- (value) =>
313
- value.padEnd(...args),
314
- charAt:
315
- (...args) =>
316
- (value) =>
317
- value.charAt(...args),
318
- startsWith:
319
- (...args) =>
320
- (value) =>
321
- value.startsWith(...args),
322
- endsWith:
323
- (...args) =>
324
- (value) =>
325
- value.endsWith(...args),
326
- substring:
327
- (...args) =>
328
- (value) =>
329
- value.substring(...args),
330
- slice:
331
- (...args) =>
332
- (value) =>
333
- value.slice(...args),
334
- trim: () => (value) => value.trim(),
335
- trimStart: () => (value) => value.trimStart(),
336
- trimEnd: () => (value) => value.trimEnd(),
337
- trimLeft: () => (value) => value.trimLeft(),
338
- trimRight: () => (value) => value.trimRight(),
339
- truncate:
340
- (length = 255, killwords = false, end = '...', leeway = 0) =>
341
- (value) => {
342
- // TODO (fix): Validate arguments...
343
-
344
- const s = String(value)
345
-
346
- if (length < end.length) {
347
- length = end.length
348
- }
349
-
350
- if (leeway < 0) {
351
- leeway = 0
352
- }
353
-
354
- if (s.length <= length + leeway) {
355
- return s
356
- }
357
-
358
- if (killwords) {
359
- return s.slice(0, length - end.length) + end
360
- }
361
-
362
- return (
363
- s
364
- .slice(0, length - end.length)
365
- .trimRight()
366
- .split(' ')
367
- .slice(0, -1)
368
- .join(' ') + end
369
- )
370
- },
371
- wordcount: () => (value) => fp.words(value).length,
372
- words: () => (value) => fp.words(value),
373
- },
374
- ),
375
- ...asFilter(
376
- (value) => (Array.isArray(value) || typeof value === 'string' ? value : []),
377
- (value) => value.includes,
378
- {
379
- includes:
380
- (...args) =>
381
- (value) =>
382
- value.includes(...args),
383
- },
384
- ),
385
- // array
386
- ...asFilter(
387
- (value) => (Array.isArray(value) ? value : []),
388
- (value) => Array.isArray(value),
389
- {
390
- fromEntries: () => fp.fromPairs,
391
- fromPairs: () => fp.fromPairs,
392
- mergeAll: () => fp.mergeAll,
393
- map: (path) => (value) =>
394
- fp.isArray(path) ? fp.map(fp.pick(path), value) : fp.map(fp.get(path), value),
395
- every: (predicate) => (value) => fp.every(predicate, value),
396
- some: (predicate) => (value) => fp.some(predicate, value),
397
- filter: (predicate) => (value) => fp.filter(predicate, value),
398
- reject: (predicate) => (value) => fp.reject(predicate, value),
399
- findIndex: (predicate) => (value) => fp.findIndex(predicate, value),
400
- findLastIndex: (predicate) => (value) => fp.findLastIndex(predicate, value),
401
- find: (predicate) => (value) => fp.find(predicate, value),
402
- findLast: (predicate) => (value) => fp.findLast(predicate, value),
403
- slice: (start, end) => (value) => value.slice(start, end),
404
- reverse: () => (value) => fp.reverse(value),
405
- join: (delimiter) => (value) => fp.join(delimiter, value),
406
- at: (index) => (value) => value[index],
407
- first: () => (value) => fp.head(value),
408
- head: () => (value) => fp.head(value),
409
- last: () => (value) => fp.last(value),
410
- tail: () => (value) => fp.last(value),
411
- length: () => (value) => fp.size(value),
412
- size: () => (value) => fp.size(value),
413
- sort: () => (value) => [...value].sort(),
414
- sortBy: (iteratees) => (value) => fp.sortBy(iteratees, value),
415
- sum: () => (value) => fp.sum(value),
416
- unique: () => (value) => fp.uniq(value),
417
- uniq: () => (value) => fp.uniq(value),
418
- flatten:
419
- (depth = 1) =>
420
- (value) =>
421
- fp.flattenDepth(depth, value),
422
- flattenDeep: () => (value) => fp.flattenDeep(value),
423
- union:
424
- (...args) =>
425
- (value) =>
426
- fp.union(args[0], value),
427
- intersection:
428
- (...args) =>
429
- (value) =>
430
- fp.intersection(args[0], value),
431
- concat:
432
- (...args) =>
433
- (value) =>
434
- fp.concat(value, args[0]),
435
- difference:
436
- (...args) =>
437
- (value) =>
438
- fp.difference(value, args[0]),
439
- initial: () => (value) => fp.initial(value),
440
- compact: () => (value) => fp.compact(value),
441
- pull:
442
- (...args) =>
443
- (value) =>
444
- fp.pull(args[0], value),
445
- },
446
- ),
447
- // object
448
- ...asFilter(
449
- (value) => (fp.isPlainObject(value) ? value : {}),
450
- (value) => fp.isPlainObject(value),
451
- {
452
- merge: (value) => fp.merge(value),
453
- set: (path, value) => fp.set(path, value),
454
- },
455
- ),
456
- // collection
457
- ...asFilter(
458
- (value) => (Array.isArray(value) || fp.isPlainObject(value) ? value : []),
459
- (value) => Array.isArray(value) || fp.isPlainObject(value),
460
- {
461
- pluck: (path) => (value) => fp.get(path, value),
462
- pick: (paths) => (value) => fp.pick(paths, value),
463
- omit: (paths) => (value) => fp.omit(paths, value),
464
- get: (path) => (value) => fp.get(path, value),
465
- values: () => (value) => fp.values(value),
466
- keys: () => (value) => fp.keys(value),
467
- size: () => (value) => fp.size(value),
468
- entries: () => (value) => fp.entries(value),
469
- },
470
- ),
471
- // misc
472
- ...asFilter(null, null, {
473
- timer: (period) => (dueTime) => {
474
- if (moment.isMoment(dueTime)) {
475
- if (!dueTime.isValid()) {
476
- return null
477
- }
478
- dueTime = dueTime.toDate()
479
- } else if (dueTime instanceof Date) {
480
- if (isNaN(dueTime)) {
481
- return null
482
- }
483
- } else if (!Number.isFinite(dueTime)) {
484
- return null
485
- }
486
-
487
- if (period !== '' && period != null && !Number.isFinite(period)) {
488
- return null
489
- }
490
-
491
- return rxjs.timer(dueTime, period).pipe(
492
- rxjs.map(() => moment()),
493
- rxjs.startWith(null),
494
- )
495
- },
496
- }),
497
- }
498
-
499
- const getFilter = makeWeakCache((filterStr) => {
500
- const [, filterName, argsStr] =
501
- filterStr
502
- .replace(/\n/g, '\\n')
503
- .replace(/\r/g, '\\r')
504
- .match(/([^(]+)\((.*)\)/) || []
505
-
506
- const tokens = split(argsStr, {
507
- separator: ',',
508
- brackets: true,
509
- quotes: ['"'],
510
- })
511
-
512
- const args = tokens.map((x) => {
513
- try {
514
- x = x.trim()
515
- if (x === 'undefined' || x === '') {
516
- return undefined
517
- }
518
- if (x === 'null') {
519
- return null
520
- }
521
- return x ? JSON5.parse(x) : x
522
- } catch (err) {
523
- throw new Error(`failed to parse token ${x}`, { cuase: err })
524
- }
525
- })
526
-
527
- const factory = FILTERS[filterName]
528
-
529
- if (!factory) {
530
- throw new Error(`unknown filter: ${filterName}`)
531
- }
532
-
533
- return factory(...args)
534
- })
535
-
536
- const getValue = (value, [path, ...rest]) => {
537
- if (!path || !value || typeof value !== 'object') {
538
- return rxjs.isObservable(value) ? value : rxjs.of(value)
539
- } else {
540
- return rxjs.isObservable(value)
541
- ? value.pipe(
542
- rxjs.switchMap((value) => getValue(value[path], rest)),
543
- rxjs.distinctUntilChanged(),
544
- )
545
- : getValue(value[path], rest)
546
- }
547
- }
548
-
549
- const reduceValue = (value, index, filters, options) => {
550
- if (value === RETURN) {
551
- return rxjs.of(null)
552
- }
553
-
554
- while (index < filters.length) {
555
- value = filters[index++](value, options)
556
-
557
- if (value === RETURN) {
558
- return rxjs.of(null)
559
- }
560
-
561
- if (rxjs.isObservable(value)) {
562
- return value.pipe(
563
- rxjs.switchMap((value) => reduceValue(value, index, filters, options)),
564
- rxjs.distinctUntilChanged(),
565
- // TODO (fix): better error handling...
566
- rxjs.catchError(() => rxjs.of(null)),
567
- )
568
- }
569
- }
570
-
571
- return rxjs.of(value)
572
- }
573
-
574
- return makeWeakCache((expression) => {
575
- try {
576
- const [basePathStr, ...tokens] = split(expression, {
577
- separator: '|',
578
- quotes: ['"'],
579
- }).map((str) => str.trim())
580
-
581
- const filters = tokens.map(getFilter)
582
- const basePath = fp.toPath(basePathStr)
583
-
584
- return (context, options) =>
585
- getValue(context, basePath).pipe(
586
- rxjs.switchMap((value) => reduceValue(value, 0, filters, options)),
587
- rxjs.distinctUntilChanged(),
588
- rxjs.catchError((err) => {
589
- ;(options?.logger ?? logger)?.error(
590
- { err, data: { expression, context: JSON.stringify(context) } },
591
- 'expression failed',
592
- )
593
- return rxjs.of(null)
594
- }),
595
- )
596
- } catch (err) {
597
- throw new Error(`failed to parse expression ${expression}`, { cause: err })
598
- }
599
- })
600
- }