@bessemer/cornerstone 0.5.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.
Files changed (66) hide show
  1. package/jest.config.js +3 -0
  2. package/package.json +39 -0
  3. package/src/array.ts +142 -0
  4. package/src/async.ts +114 -0
  5. package/src/cache.ts +236 -0
  6. package/src/combinable.ts +40 -0
  7. package/src/comparator.ts +78 -0
  8. package/src/content.ts +138 -0
  9. package/src/context.ts +6 -0
  10. package/src/crypto.ts +11 -0
  11. package/src/date.ts +18 -0
  12. package/src/duration.ts +57 -0
  13. package/src/either.ts +29 -0
  14. package/src/entry.ts +21 -0
  15. package/src/equalitor.ts +12 -0
  16. package/src/error-event.ts +126 -0
  17. package/src/error.ts +16 -0
  18. package/src/expression/array-expression.ts +29 -0
  19. package/src/expression/expression-evaluator.ts +34 -0
  20. package/src/expression/expression.ts +188 -0
  21. package/src/expression/internal.ts +34 -0
  22. package/src/expression/numeric-expression.ts +182 -0
  23. package/src/expression/string-expression.ts +38 -0
  24. package/src/expression.ts +48 -0
  25. package/src/function.ts +3 -0
  26. package/src/glob.ts +19 -0
  27. package/src/global-variable.ts +40 -0
  28. package/src/hash.ts +28 -0
  29. package/src/hex-code.ts +6 -0
  30. package/src/index.ts +82 -0
  31. package/src/lazy.ts +11 -0
  32. package/src/logger.ts +144 -0
  33. package/src/math.ts +132 -0
  34. package/src/misc.ts +22 -0
  35. package/src/object.ts +236 -0
  36. package/src/patch.ts +128 -0
  37. package/src/precondition.ts +25 -0
  38. package/src/promise.ts +16 -0
  39. package/src/property.ts +29 -0
  40. package/src/reference.ts +68 -0
  41. package/src/resource.ts +32 -0
  42. package/src/result.ts +66 -0
  43. package/src/retry.ts +70 -0
  44. package/src/rich-text.ts +24 -0
  45. package/src/set.ts +46 -0
  46. package/src/signature.ts +20 -0
  47. package/src/store.ts +91 -0
  48. package/src/string.ts +173 -0
  49. package/src/tag.ts +68 -0
  50. package/src/types.ts +21 -0
  51. package/src/ulid.ts +28 -0
  52. package/src/unit.ts +4 -0
  53. package/src/uri.ts +321 -0
  54. package/src/url.ts +155 -0
  55. package/src/uuid.ts +37 -0
  56. package/src/zod.ts +24 -0
  57. package/test/comparator.test.ts +1 -0
  58. package/test/expression.test.ts +12 -0
  59. package/test/object.test.ts +104 -0
  60. package/test/patch.test.ts +170 -0
  61. package/test/set.test.ts +20 -0
  62. package/test/string.test.ts +22 -0
  63. package/test/uri.test.ts +111 -0
  64. package/test/url.test.ts +174 -0
  65. package/tsconfig.build.json +13 -0
  66. package/tsup.config.ts +4 -0
package/src/content.ts ADDED
@@ -0,0 +1,138 @@
1
+ import { Referencable, Reference, ReferenceType } from '@bessemer/cornerstone/reference'
2
+ import { NominalType } from '@bessemer/cornerstone/types'
3
+ import { AbstractApplicationContext } from '@bessemer/cornerstone/context'
4
+ import { Arrays, Objects, References, Tags, Ulids, Zod } from '@bessemer/cornerstone'
5
+ import { RichTextJson } from '@bessemer/cornerstone/rich-text'
6
+ import { Tag } from '@bessemer/cornerstone/tag'
7
+ import { ZodType } from 'zod'
8
+
9
+ export type ContentSector = NominalType<string, 'ContentSector'>
10
+ export const ContentSectorSchema: ZodType<ContentSector> = Zod.string()
11
+
12
+ export type ContentKey = NominalType<string, 'ContentKey'>
13
+ export const ContentKeySchema: ZodType<ContentKey> = Zod.string()
14
+
15
+ export type ContentType<Data = unknown> = NominalType<string, ['ContentType', Data]>
16
+ export const ContentTypeSchema: ZodType<ContentType> = Zod.string()
17
+
18
+ export type ContentReference = Reference<'Content'>
19
+
20
+ type ContentDataType<Type> = Type extends ContentType<infer Data> ? Data : never
21
+
22
+ export const ContentDataSchema = Zod.object({
23
+ key: ContentKeySchema,
24
+ type: ContentTypeSchema,
25
+ data: Zod.unknown(),
26
+ sector: ContentSectorSchema.nullable(),
27
+ })
28
+
29
+ export type ContentData<Type extends ContentType = ContentType, Data = ContentDataType<Type>> = Referencable<ContentReference> & {
30
+ key: ContentKey
31
+ type: Type
32
+ data: Data
33
+ sector: ContentSector | null
34
+ }
35
+
36
+ export type ContentTypeConstructor<Content extends ContentData> = Content['type']
37
+
38
+ export type ContentDisplayType = NominalType<string, 'ContentDisplayType'>
39
+
40
+ export namespace ContentDisplayTypes {
41
+ export const Default: ContentDisplayType = 'Default'
42
+ export const Label: ContentDisplayType = 'Label'
43
+ export const Desktop: ContentDisplayType = 'Desktop'
44
+ export const Mobile: ContentDisplayType = 'Mobile'
45
+ export const Modal: ContentDisplayType = 'Modal'
46
+ export const Tooltip: ContentDisplayType = 'Tooltip'
47
+ }
48
+
49
+ export const TextContentType: ContentType<RichTextJson> = 'Text'
50
+ export type TextContent = ContentData<typeof TextContentType>
51
+
52
+ export interface ContentProvider<ContextType extends AbstractApplicationContext = AbstractApplicationContext> {
53
+ fetchContentByIds: (references: Array<ReferenceType<ContentReference>>, context: ContextType) => Promise<Array<ContentData>>
54
+
55
+ fetchContentByKeys: (keys: Array<ContentKey>, tags: Array<Tag>, context: ContextType) => Promise<Array<ContentData>>
56
+
57
+ fetchContentBySectors: (sectors: Array<ContentSector>, tags: Array<Tag>, context: ContextType) => Promise<Array<ContentData>>
58
+ }
59
+
60
+ export type ContentNormalizer<
61
+ ApplicationContext extends AbstractApplicationContext = AbstractApplicationContext,
62
+ Type extends ContentData = ContentData
63
+ > = {
64
+ type: Type['type']
65
+ normalize: (data: Array<ContentData>, context: ApplicationContext) => Promise<Array<Type>>
66
+ }
67
+
68
+ // TODO might be more efficient to put the normalizers in a map at some point
69
+ export const normalizeContent = async <ApplicationContext extends AbstractApplicationContext>(
70
+ content: Array<ContentData>,
71
+ normalizers: Array<ContentNormalizer<ApplicationContext>>,
72
+ context: ApplicationContext
73
+ ): Promise<Array<ContentData>> => {
74
+ const groupedContent = Arrays.groupBy(content, (it) => it.type)
75
+ const normalizedGroupedContent = Object.entries(groupedContent).map(async ([type, values]) => {
76
+ const normalizer = normalizers.find((it) => it.type === type)
77
+ if (Objects.isNil(normalizer)) {
78
+ return values
79
+ }
80
+
81
+ return await normalizer.normalize(values, context)
82
+ })
83
+
84
+ const normalizedContent = (await Promise.all(normalizedGroupedContent)).flatMap((it) => it)
85
+ return normalizedContent
86
+ }
87
+
88
+ export type StaticContentData<Type extends ContentType = ContentType, Data = ContentDataType<Type>> = ContentData<Type, Data> & {
89
+ tags: Array<Tag>
90
+ }
91
+
92
+ export const staticData = <Type extends ContentType = ContentType, Data = ContentDataType<Type>>(
93
+ key: ContentKey,
94
+ type: Type,
95
+ data: Data,
96
+ options?: { tags?: Array<Tag>; sector?: ContentSector }
97
+ ): StaticContentData<Type, Data> => {
98
+ return {
99
+ reference: References.reference(Ulids.generateString(), 'Content'),
100
+ key,
101
+ type,
102
+ data,
103
+ tags: options?.tags ?? [],
104
+ sector: options?.sector ?? null,
105
+ }
106
+ }
107
+
108
+ export const staticProvider = <ApplicationContext extends AbstractApplicationContext>(
109
+ content: Array<StaticContentData>,
110
+ normalizers?: Array<ContentNormalizer<ApplicationContext>>
111
+ ): ContentProvider<ApplicationContext> => {
112
+ return {
113
+ async fetchContentByIds(references: Array<ReferenceType<ContentReference>>, context: ApplicationContext): Promise<Array<ContentData>> {
114
+ const matchingContent = content.filter((it) => Arrays.contains(references, it.reference))
115
+ return normalizeContent(matchingContent, normalizers ?? [], context)
116
+ },
117
+ async fetchContentByKeys(keys: Array<ContentKey>, tags: Array<Tag>, context: ApplicationContext): Promise<Array<ContentData>> {
118
+ const matchingContent = content.filter((it) => Arrays.contains(keys, it.key))
119
+
120
+ const resolvedContent = Object.values(Arrays.groupBy(matchingContent, (it) => it.key)).map((it) => {
121
+ const resolvedContent = Tags.resolveBy(it, (it) => it.tags ?? [], tags)
122
+ return Arrays.first(resolvedContent)!
123
+ })
124
+
125
+ return normalizeContent(resolvedContent, normalizers ?? [], context)
126
+ },
127
+ async fetchContentBySectors(sectors: Array<ContentSector>, tags: Array<Tag>, context: ApplicationContext): Promise<Array<ContentData>> {
128
+ const matchingContent = content.filter((it) => Objects.isPresent(it.sector)).filter((it) => Arrays.contains(sectors, it.sector!))
129
+
130
+ const resolvedContent = Object.values(Arrays.groupBy(matchingContent, (it) => it.key)).map((it) => {
131
+ const resolvedContent = Tags.resolveBy(it, (it) => it.tags ?? [], tags)
132
+ return Arrays.first(resolvedContent)!
133
+ })
134
+
135
+ return normalizeContent(resolvedContent, normalizers ?? [], context)
136
+ },
137
+ }
138
+ }
package/src/context.ts ADDED
@@ -0,0 +1,6 @@
1
+ export type AbstractApplicationContext = {
2
+ global: {}
3
+ client: {
4
+ runtime: {}
5
+ }
6
+ }
package/src/crypto.ts ADDED
@@ -0,0 +1,11 @@
1
+ export const getRandomBytes = (length: number): Uint8Array => {
2
+ return crypto.getRandomValues(new Uint8Array(length))
3
+ }
4
+
5
+ export const getRandomHex = (length: number): string => {
6
+ const bytes = getRandomBytes(length)
7
+ // JOHN should Uint8Array to Hex String be a utility?
8
+ const hashArray = Array.from(bytes)
9
+ const hexString = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
10
+ return hexString
11
+ }
package/src/date.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { addHours as _addHours, addMilliseconds as _addMilliseconds, isAfter as _isAfter, isBefore as _isBefore, parseISO } from 'date-fns'
2
+ import { Duration } from '@bessemer/cornerstone/duration'
3
+ import { Durations } from '@bessemer/cornerstone/index'
4
+
5
+ export const now = (): Date => {
6
+ return new Date()
7
+ }
8
+
9
+ export const of = (dateString: string): Date => {
10
+ return parseISO(dateString)
11
+ }
12
+
13
+ export const addMilliseconds = _addMilliseconds
14
+ export const addHours = _addHours
15
+ export const isBefore = _isBefore
16
+ export const isAfter = _isAfter
17
+
18
+ export const addDuration = (date: Date, duration: Duration): Date => addMilliseconds(date, Durations.inMilliseconds(duration))
@@ -0,0 +1,57 @@
1
+ import { NominalType } from '@bessemer/cornerstone/types'
2
+
3
+ export type Millisecond = NominalType<number, 'Millisecond'>
4
+ export type Second = NominalType<number, 'Second'>
5
+ export type Minute = NominalType<number, 'Minute'>
6
+ export type Hour = NominalType<number, 'Hour'>
7
+ export type Day = NominalType<number, 'Day'>
8
+
9
+ export type Duration = {
10
+ value: Millisecond
11
+ }
12
+
13
+ export const ofMilliseconds = (value: Millisecond) => {
14
+ return {
15
+ value,
16
+ }
17
+ }
18
+
19
+ export const inMilliseconds = (duration: Duration) => {
20
+ return duration.value
21
+ }
22
+
23
+ export const ofSeconds = (value: Second) => {
24
+ return ofMilliseconds(value * 1000)
25
+ }
26
+
27
+ export const inSeconds = (duration: Duration) => {
28
+ return inMilliseconds(duration) / 1000
29
+ }
30
+
31
+ export const ofMinutes = (value: Minute) => {
32
+ return ofSeconds(value * 60)
33
+ }
34
+
35
+ export const inMinutes = (duration: Duration) => {
36
+ return inSeconds(duration) / 60
37
+ }
38
+
39
+ export const ofHours = (value: Hour) => {
40
+ return ofMinutes(value * 60)
41
+ }
42
+
43
+ export const inHours = (duration: Duration) => {
44
+ return inMinutes(duration) / 60
45
+ }
46
+
47
+ export const ofDays = (value: Day) => {
48
+ return ofHours(value * 24)
49
+ }
50
+
51
+ export const inDays = (duration: Duration) => {
52
+ return inHours(duration) / 24
53
+ }
54
+
55
+ export const Zero = ofMilliseconds(0)
56
+ export const OneDay = ofDays(1)
57
+ export const OneHour = ofHours(1)
package/src/either.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { Eithers } from '@bessemer/cornerstone/index'
2
+
3
+ export enum EitherType {
4
+ Left = 'Left',
5
+ Right = 'Right',
6
+ }
7
+
8
+ export type Left<T> = {
9
+ type: EitherType.Left
10
+ value: T
11
+ }
12
+ export type Right<N> = {
13
+ type: EitherType.Right
14
+ value: N
15
+ }
16
+
17
+ export type Either<T, N> = Left<T> | Right<N>
18
+
19
+ export const left = <T>(value: T): Left<T> => ({ type: EitherType.Left, value })
20
+ export const right = <N>(value: N): Right<N> => ({ type: EitherType.Right, value })
21
+
22
+ export const isLeft = <T, N>(either: Either<T, N>): either is Left<T> => either.type === EitherType.Left
23
+ export const isRight = <T, N>(either: Either<T, N>): either is Right<N> => either.type === EitherType.Right
24
+
25
+ export const split = <L, R>(array: Array<Either<L, R>>): [Array<L>, Array<R>] => {
26
+ const lefts = array.filter(Eithers.isLeft).map((it) => it.value)
27
+ const rights = array.filter(Eithers.isRight).map((it) => it.value)
28
+ return [lefts, rights]
29
+ }
package/src/entry.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type Entry<Value, Key = string> = [Key, Value]
2
+
3
+ export const of = <Value, Key = string>(key: Key, value: Value): Entry<Value, Key> => {
4
+ return [key, value]
5
+ }
6
+
7
+ export const keys = <Key>(entries: Array<Entry<unknown, Key>>): Array<Key> => {
8
+ return entries.map((it) => it[0])
9
+ }
10
+
11
+ export const values = <T>(entries: Array<Entry<T>>): Array<T> => {
12
+ return entries.map((it) => it[1])
13
+ }
14
+
15
+ export const mapKeys = <Value, Key, NewKey>(entries: Array<Entry<Value, Key>>, mapper: (key: Key) => NewKey): Array<Entry<Value, NewKey>> => {
16
+ return entries.map(([key, value]) => of(mapper(key), value))
17
+ }
18
+
19
+ export const mapValues = <Value, Key, NewValue>(entries: Array<Entry<Value, Key>>, mapper: (key: Value) => NewValue): Array<Entry<NewValue, Key>> => {
20
+ return entries.map(([key, value]) => of(key, mapper(value)))
21
+ }
@@ -0,0 +1,12 @@
1
+ import { Comparator } from '@bessemer/cornerstone/comparator'
2
+
3
+ export type Equalitor<T> = (first: T, second: T) => boolean
4
+
5
+ export const fromComparator = <T>(comparator: Comparator<T>): Equalitor<T> => {
6
+ return (first, second) => comparator(first, second) === 0
7
+ }
8
+
9
+ export const reference =
10
+ <T>(): Equalitor<T> =>
11
+ (first, second) =>
12
+ first === second
@@ -0,0 +1,126 @@
1
+ import { Arrays, Errors, Objects } from '@bessemer/cornerstone'
2
+ import { NominalType, Throwable } from '@bessemer/cornerstone/types'
3
+ import { RecordAttribute } from '@bessemer/cornerstone/object'
4
+
5
+ /*
6
+ Represents a structured error event. The code can be mapped to a unique type of error while the
7
+ message and attributes can provide contextual information about the error. Finally,
8
+ an ErrorEvent could have multiple causes which get aggregated into a single parent error.
9
+ */
10
+ export type ErrorCode = NominalType<string, 'ErrorCode'>
11
+ export type ErrorAttribute<Type = unknown> = RecordAttribute<Type, 'ErrorAttribute'>
12
+
13
+ export type ErrorEvent = {
14
+ code: ErrorCode
15
+ message: string
16
+ attributes: Record<ErrorAttribute, unknown>
17
+ causes: Array<ErrorEvent>
18
+ }
19
+
20
+ // Builder object that allows for 'partial' representation of ErrorEvents
21
+ export type ErrorEventBuilder = {
22
+ code: ErrorCode
23
+ message?: string | null
24
+ attributes?: Record<ErrorAttribute, unknown>
25
+ causes?: Array<ErrorEvent>
26
+ }
27
+
28
+ // An exception type that contains an ErrorEvent
29
+ export class ErrorEventException extends Error {
30
+ readonly errorEvent: ErrorEvent
31
+
32
+ constructor(errorEvent: ErrorEvent, cause?: unknown) {
33
+ super(errorEvent.message ?? '', { cause })
34
+ this.name = this.constructor.name
35
+ this.errorEvent = errorEvent
36
+ }
37
+ }
38
+
39
+ export const isErrorEvent = (throwable: Throwable): throwable is ErrorEvent => {
40
+ if (!Objects.isObject(throwable)) {
41
+ return false
42
+ }
43
+
44
+ return 'code' in throwable && 'message' in throwable && 'attributes' in throwable && 'causes' in throwable
45
+ }
46
+
47
+ export const isErrorEventException = (throwable: Throwable): throwable is ErrorEventException => {
48
+ return throwable instanceof ErrorEventException
49
+ }
50
+
51
+ export const of = (builder: ErrorEventBuilder): ErrorEvent => {
52
+ const code = builder.code
53
+
54
+ return {
55
+ code,
56
+ message: builder.message ?? code,
57
+ attributes: builder.attributes ?? {},
58
+ causes: builder.causes ?? [],
59
+ }
60
+ }
61
+
62
+ export const from = (throwable: Throwable): ErrorEvent => {
63
+ if (isErrorEvent(throwable)) {
64
+ return throwable
65
+ }
66
+
67
+ if (!Errors.isError(throwable)) {
68
+ return unhandled()
69
+ }
70
+
71
+ const errorEventException = Errors.findInCausalChain(throwable, isErrorEventException) as ErrorEventException | undefined
72
+ if (Objects.isNil(errorEventException)) {
73
+ return unhandled()
74
+ }
75
+
76
+ return errorEventException.errorEvent
77
+ }
78
+
79
+ export const findByCodeInCausalChain = (error: ErrorEvent, code: string): ErrorEvent | undefined => {
80
+ return findInCausalChain(error, (it) => it.code === code)
81
+ }
82
+
83
+ /*
84
+ Traverses the causal chain of the ErrorEvent, searching for a predicate that matches (including matching on the parent error event)
85
+ This is useful if you want to find whether or not a given error was caused by a specific failure. The search executes depth-first and
86
+ will return te first matching instance that satisfies the predicate, or undefined otherwise
87
+ */
88
+ export const findInCausalChain = (error: ErrorEvent, predicate: (error: ErrorEvent) => boolean): ErrorEvent | undefined => {
89
+ if (predicate(error)) {
90
+ return error
91
+ }
92
+
93
+ return Arrays.first(error.causes.map((it) => findInCausalChain(it, predicate)).filter(Objects.isPresent))
94
+ }
95
+
96
+ export const aggregate = (builder: ErrorEventBuilder, causes: Array<ErrorEvent>): ErrorEvent | undefined => {
97
+ if (causes.length === 0) {
98
+ return undefined
99
+ }
100
+
101
+ return of({ ...builder, causes })
102
+ }
103
+
104
+ export const UnhandledErrorCode: ErrorCode = 'error-event.unhandled'
105
+ export const NotFoundErrorCode: ErrorCode = 'error-event.not-found'
106
+
107
+ export const RequestCorrelationIdAttribute: ErrorAttribute<string> = 'requestCorrelationId'
108
+ export const HttpStatusCodeAttribute: ErrorAttribute<number> = 'httpStatusCode'
109
+
110
+ export const unhandled = (builder?: ErrorEventBuilder) =>
111
+ of(
112
+ Objects.merge(builder, {
113
+ code: UnhandledErrorCode,
114
+ message: 'An Unhandled Error has occurred.',
115
+ attributes: { [HttpStatusCodeAttribute]: 500 },
116
+ })
117
+ )
118
+
119
+ export const notFound = (builder?: ErrorEventBuilder) =>
120
+ of(
121
+ Objects.merge(builder, {
122
+ code: NotFoundErrorCode,
123
+ message: 'The requested Resource could not be found.',
124
+ attributes: { [HttpStatusCodeAttribute]: 404 },
125
+ })
126
+ )
package/src/error.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Objects } from '@bessemer/cornerstone'
2
+ import { isError as _isError } from 'lodash-es'
3
+
4
+ export const isError = _isError
5
+
6
+ export const findInCausalChain = (error: Error, predicate: (error: Error) => boolean): Error | undefined => {
7
+ if (predicate(error)) {
8
+ return error
9
+ }
10
+
11
+ if (Objects.isPresent(error.cause) && error.cause instanceof Error) {
12
+ return findInCausalChain(error.cause, predicate)
13
+ }
14
+
15
+ return undefined
16
+ }
@@ -0,0 +1,29 @@
1
+ import { defineExpression } from '@bessemer/cornerstone/expression/internal'
2
+ import { Expression } from '@bessemer/cornerstone/expression'
3
+ import { Arrays } from '@bessemer/cornerstone'
4
+
5
+ export const ConcatenateExpression = defineExpression({
6
+ expressionKey: 'Array.Concatenate',
7
+ builder: (operands: Array<Expression<Array<Expression<unknown>>>>) => {
8
+ return { operands }
9
+ },
10
+ resolver: ({ operands }, evaluate) => {
11
+ const values = evaluate(operands).map((it) => evaluate(it))
12
+ return Arrays.concatenate(values[0], ...Arrays.rest(values))
13
+ },
14
+ })
15
+
16
+ export const concatenate = ConcatenateExpression.builder
17
+
18
+ export const FirstExpression = defineExpression({
19
+ expressionKey: 'Array.First',
20
+ builder: (operands: Array<Expression<Array<Expression<unknown>>>>) => {
21
+ return { operands }
22
+ },
23
+ resolver: ({ operands }, evaluate) => {
24
+ const values = evaluate(operands).map((it) => evaluate(it))
25
+ return Arrays.first(values)
26
+ },
27
+ })
28
+
29
+ export const first = FirstExpression.builder
@@ -0,0 +1,34 @@
1
+ import { Expression, ExpressionContext, ExpressionDefinition, ExpressionReference, IExpression } from '@bessemer/cornerstone/expression'
2
+ import { Objects, Preconditions } from '@bessemer/cornerstone'
3
+
4
+ export class ExpressionEvaluator {
5
+ constructor(private readonly expressionDefinitions: Array<ExpressionDefinition<unknown, Array<any>, Expression<any>>>) {}
6
+
7
+ evaluate<T>(expression: Expression<T>, context: ExpressionContext): T {
8
+ if (isValue(expression)) {
9
+ return expression
10
+ }
11
+
12
+ const matchingExpressionDefinition = this.expressionDefinitions.find((it) => it.expressionKey === expression.expressionKey)
13
+ Preconditions.isPresent(matchingExpressionDefinition)
14
+ return matchingExpressionDefinition.resolver(expression, (expression) => this.evaluate(expression, context), context) as T
15
+ }
16
+
17
+ dereference<ReturnType, ArgumentType extends Array<unknown>>(
18
+ reference: ExpressionReference<ReturnType, ArgumentType>,
19
+ ...args: ArgumentType
20
+ ): Expression<ReturnType> {
21
+ const matchingExpressionDefinition = this.expressionDefinitions.find((it) => it.expressionKey === reference.expressionKey)
22
+ Preconditions.isPresent(matchingExpressionDefinition, () => `Unable to find Expression Definition for type: ${reference.expressionKey}`)
23
+ return matchingExpressionDefinition.builder(...args)
24
+ }
25
+ }
26
+
27
+ const isValue = <T>(expression: Expression<T>): expression is T => {
28
+ if (!Objects.isObject(expression)) {
29
+ return true
30
+ }
31
+
32
+ const result = (expression as IExpression<T>).expressionKey === undefined
33
+ return result
34
+ }