@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.
- package/jest.config.js +3 -0
- package/package.json +39 -0
- package/src/array.ts +142 -0
- package/src/async.ts +114 -0
- package/src/cache.ts +236 -0
- package/src/combinable.ts +40 -0
- package/src/comparator.ts +78 -0
- package/src/content.ts +138 -0
- package/src/context.ts +6 -0
- package/src/crypto.ts +11 -0
- package/src/date.ts +18 -0
- package/src/duration.ts +57 -0
- package/src/either.ts +29 -0
- package/src/entry.ts +21 -0
- package/src/equalitor.ts +12 -0
- package/src/error-event.ts +126 -0
- package/src/error.ts +16 -0
- package/src/expression/array-expression.ts +29 -0
- package/src/expression/expression-evaluator.ts +34 -0
- package/src/expression/expression.ts +188 -0
- package/src/expression/internal.ts +34 -0
- package/src/expression/numeric-expression.ts +182 -0
- package/src/expression/string-expression.ts +38 -0
- package/src/expression.ts +48 -0
- package/src/function.ts +3 -0
- package/src/glob.ts +19 -0
- package/src/global-variable.ts +40 -0
- package/src/hash.ts +28 -0
- package/src/hex-code.ts +6 -0
- package/src/index.ts +82 -0
- package/src/lazy.ts +11 -0
- package/src/logger.ts +144 -0
- package/src/math.ts +132 -0
- package/src/misc.ts +22 -0
- package/src/object.ts +236 -0
- package/src/patch.ts +128 -0
- package/src/precondition.ts +25 -0
- package/src/promise.ts +16 -0
- package/src/property.ts +29 -0
- package/src/reference.ts +68 -0
- package/src/resource.ts +32 -0
- package/src/result.ts +66 -0
- package/src/retry.ts +70 -0
- package/src/rich-text.ts +24 -0
- package/src/set.ts +46 -0
- package/src/signature.ts +20 -0
- package/src/store.ts +91 -0
- package/src/string.ts +173 -0
- package/src/tag.ts +68 -0
- package/src/types.ts +21 -0
- package/src/ulid.ts +28 -0
- package/src/unit.ts +4 -0
- package/src/uri.ts +321 -0
- package/src/url.ts +155 -0
- package/src/uuid.ts +37 -0
- package/src/zod.ts +24 -0
- package/test/comparator.test.ts +1 -0
- package/test/expression.test.ts +12 -0
- package/test/object.test.ts +104 -0
- package/test/patch.test.ts +170 -0
- package/test/set.test.ts +20 -0
- package/test/string.test.ts +22 -0
- package/test/uri.test.ts +111 -0
- package/test/url.test.ts +174 -0
- package/tsconfig.build.json +13 -0
- 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
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))
|
package/src/duration.ts
ADDED
@@ -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
|
+
}
|
package/src/equalitor.ts
ADDED
@@ -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
|
+
}
|