effect-app 0.152.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/.eslintrc.cjs +11 -0
- package/.prettierignore +6 -0
- package/CHANGELOG.md +4106 -0
- package/_cjs/Config/SecretURL.cjs +58 -0
- package/_cjs/Config/SecretURL.cjs.map +1 -0
- package/_cjs/Config/internal/configSecretURL.cjs +88 -0
- package/_cjs/Config/internal/configSecretURL.cjs.map +1 -0
- package/_cjs/Inputify.type.cjs +6 -0
- package/_cjs/Inputify.type.cjs.map +1 -0
- package/_cjs/Operations.cjs +76 -0
- package/_cjs/Operations.cjs.map +1 -0
- package/_cjs/Pure.cjs +201 -0
- package/_cjs/Pure.cjs.map +1 -0
- package/_cjs/Request.cjs +76 -0
- package/_cjs/Request.cjs.map +1 -0
- package/_cjs/Widen.type.cjs +6 -0
- package/_cjs/Widen.type.cjs.map +1 -0
- package/_cjs/_ext/date.cjs +64 -0
- package/_cjs/_ext/date.cjs.map +1 -0
- package/_cjs/_ext/misc.cjs +121 -0
- package/_cjs/_ext/misc.cjs.map +1 -0
- package/_cjs/_global.cjs +24 -0
- package/_cjs/_global.cjs.map +1 -0
- package/_cjs/_global.ext.cjs +5 -0
- package/_cjs/_global.ext.cjs.map +1 -0
- package/_cjs/_global.schema.cjs +4 -0
- package/_cjs/_global.schema.cjs.map +1 -0
- package/_cjs/client/QueryResult.cjs +116 -0
- package/_cjs/client/QueryResult.cjs.map +1 -0
- package/_cjs/client/clientFor.cjs +159 -0
- package/_cjs/client/clientFor.cjs.map +1 -0
- package/_cjs/client/config.cjs +21 -0
- package/_cjs/client/config.cjs.map +1 -0
- package/_cjs/client/errors.cjs +116 -0
- package/_cjs/client/errors.cjs.map +1 -0
- package/_cjs/client/fetch.cjs +178 -0
- package/_cjs/client/fetch.cjs.map +1 -0
- package/_cjs/client.cjs +61 -0
- package/_cjs/client.cjs.map +1 -0
- package/_cjs/faker.cjs +31 -0
- package/_cjs/faker.cjs.map +1 -0
- package/_cjs/ids.cjs +24 -0
- package/_cjs/ids.cjs.map +1 -0
- package/_cjs/index.cjs +27 -0
- package/_cjs/index.cjs.map +1 -0
- package/_cjs/refinements.cjs +97 -0
- package/_cjs/refinements.cjs.map +1 -0
- package/_cjs/schema.cjs +50 -0
- package/_cjs/schema.cjs.map +1 -0
- package/_cjs/schema.test.cjs +9 -0
- package/_cjs/schema.test.cjs.map +1 -0
- package/_cjs/service.cjs +97 -0
- package/_cjs/service.cjs.map +1 -0
- package/_cjs/utils.cjs +17 -0
- package/_cjs/utils.cjs.map +1 -0
- package/_src/Config/SecretURL.ts +103 -0
- package/_src/Config/internal/configSecretURL.ts +85 -0
- package/_src/Inputify.type.ts +13 -0
- package/_src/Operations.ts +70 -0
- package/_src/Pure.ts +525 -0
- package/_src/Request.ts +106 -0
- package/_src/Widen.type.ts +28 -0
- package/_src/_ext/date.ts +84 -0
- package/_src/_ext/misc.ts +161 -0
- package/_src/_global/stm.ts.bak +35 -0
- package/_src/_global.ext.ts +3 -0
- package/_src/_global.schema.ts +106 -0
- package/_src/_global.ts +119 -0
- package/_src/client/QueryResult.ts +120 -0
- package/_src/client/clientFor.ts +260 -0
- package/_src/client/config.ts +13 -0
- package/_src/client/errors.ts +129 -0
- package/_src/client/fetch.ts +253 -0
- package/_src/client.ts +7 -0
- package/_src/faker.ts +32 -0
- package/_src/ids.ts +35 -0
- package/_src/index.ts +4 -0
- package/_src/refinements.ts +92 -0
- package/_src/schema/_schema.ts.bak +208 -0
- package/_src/schema/api/date.ts.bak +78 -0
- package/_src/schema/api.ts.bak +20 -0
- package/_src/schema/overrides.ts.bak +76 -0
- package/_src/schema/shared.ts.bak +334 -0
- package/_src/schema.test.ts +3 -0
- package/_src/schema.ts +37 -0
- package/_src/service.ts +119 -0
- package/_src/utils.ts +1 -0
- package/dist/Config/SecretURL.d.ts +82 -0
- package/dist/Config/SecretURL.d.ts.map +1 -0
- package/dist/Config/SecretURL.js +49 -0
- package/dist/Config/internal/configSecretURL.d.ts +24 -0
- package/dist/Config/internal/configSecretURL.d.ts.map +1 -0
- package/dist/Config/internal/configSecretURL.js +75 -0
- package/dist/Inputify.type.d.ts +10 -0
- package/dist/Inputify.type.d.ts.map +1 -0
- package/dist/Inputify.type.js +2 -0
- package/dist/Operations.d.ts +170 -0
- package/dist/Operations.d.ts.map +1 -0
- package/dist/Operations.js +87 -0
- package/dist/Pure.d.ts +169 -0
- package/dist/Pure.d.ts.map +1 -0
- package/dist/Pure.js +167 -0
- package/dist/Request.d.ts +49 -0
- package/dist/Request.d.ts.map +1 -0
- package/dist/Request.js +58 -0
- package/dist/Widen.type.d.ts +19 -0
- package/dist/Widen.type.d.ts.map +1 -0
- package/dist/Widen.type.js +2 -0
- package/dist/_ext/date.d.ts +71 -0
- package/dist/_ext/date.d.ts.map +1 -0
- package/dist/_ext/date.js +58 -0
- package/dist/_ext/misc.d.ts +77 -0
- package/dist/_ext/misc.d.ts.map +1 -0
- package/dist/_ext/misc.js +98 -0
- package/dist/_global.d.ts +70 -0
- package/dist/_global.d.ts.map +1 -0
- package/dist/_global.ext.d.ts +3 -0
- package/dist/_global.ext.d.ts.map +1 -0
- package/dist/_global.ext.js +4 -0
- package/dist/_global.js +76 -0
- package/dist/_global.schema.d.ts +6 -0
- package/dist/_global.schema.d.ts.map +1 -0
- package/dist/_global.schema.js +6 -0
- package/dist/client/QueryResult.d.ts +85 -0
- package/dist/client/QueryResult.d.ts.map +1 -0
- package/dist/client/QueryResult.js +85 -0
- package/dist/client/clientFor.d.ts +44 -0
- package/dist/client/clientFor.d.ts.map +1 -0
- package/dist/client/clientFor.js +144 -0
- package/dist/client/config.d.ts +14 -0
- package/dist/client/config.d.ts.map +1 -0
- package/dist/client/config.js +11 -0
- package/dist/client/errors.d.ts +206 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +130 -0
- package/dist/client/fetch.d.ts +61 -0
- package/dist/client/fetch.d.ts.map +1 -0
- package/dist/client/fetch.js +127 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +7 -0
- package/dist/faker.d.ts +7 -0
- package/dist/faker.d.ts.map +1 -0
- package/dist/faker.js +24 -0
- package/dist/ids.d.ts +32 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +17 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/refinements.d.ts +57 -0
- package/dist/refinements.d.ts.map +1 -0
- package/dist/refinements.js +85 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +22 -0
- package/dist/schema.test.d.ts.map +1 -0
- package/dist/service.d.ts +47 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +83 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +2 -0
- package/package.json +315 -0
- package/tsconfig.json +114 -0
- package/tsconfig.json.bak +47 -0
- package/tsplus.config.json +7 -0
- package/vitest.config.ts +5 -0
- package/wallaby.cjs +1 -0
package/_src/ids.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { brandedStringId, withDefaults } from "effect-app/schema"
|
|
2
|
+
import type { StringIdBrand } from "effect-app/schema"
|
|
3
|
+
import type { B } from "@effect-app/schema/schema"
|
|
4
|
+
import type { Simplify } from "effect/Types"
|
|
5
|
+
import { extendM } from "./utils.js"
|
|
6
|
+
|
|
7
|
+
export interface RequestIdBrand extends StringIdBrand {
|
|
8
|
+
readonly RequestId: unique symbol
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @tsplus type RequestId
|
|
13
|
+
*/
|
|
14
|
+
export type RequestId = NonEmptyString255
|
|
15
|
+
// a request id may be made from a span id, which does not comply with StringId schema.
|
|
16
|
+
export const RequestId = extendM(
|
|
17
|
+
Object
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
19
|
+
.assign(Object.create(NonEmptyString255) as {}, NonEmptyString255 as Schema<NonEmptyString255, string>),
|
|
20
|
+
(s) => {
|
|
21
|
+
const make = StringId.make as () => NonEmptyString255
|
|
22
|
+
return ({
|
|
23
|
+
make,
|
|
24
|
+
withDefault: S.withDefaultConstructor(s, make)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
.pipe(withDefaults)
|
|
29
|
+
|
|
30
|
+
export interface UserProfileIdBrand extends Simplify<B.Brand<"UserProfileId"> & StringIdBrand> {}
|
|
31
|
+
/**
|
|
32
|
+
* @tsplus type UserProfileId
|
|
33
|
+
*/
|
|
34
|
+
export type UserProfileId = StringId & UserProfileIdBrand
|
|
35
|
+
export const UserProfileId = brandedStringId<UserProfileIdBrand>()
|
package/_src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Clone } from "@fp-ts/optic"
|
|
2
|
+
import { InvalidStateError } from "./client.js"
|
|
3
|
+
import { clone, copy } from "./utils.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @tsplus getter function asCollectable
|
|
7
|
+
*/
|
|
8
|
+
export function asCollectable<T, T2 extends T>(refinement: Refinement<T, T2>) {
|
|
9
|
+
return Option.liftPredicate(refinement)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus fluent function as
|
|
14
|
+
*/
|
|
15
|
+
export function as<T, T2 extends T>(refinement: Refinement<T, T2>, name: string) {
|
|
16
|
+
return flow(
|
|
17
|
+
asCollectable(refinement),
|
|
18
|
+
(_) => _.encaseInEither(() => new InvalidStateError(`Cannot be ${name}`))
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @tsplus fluent function refinements
|
|
24
|
+
*/
|
|
25
|
+
export function makeAwesome<T, T2 extends T>(refinement: Refinement<T, T2>, name: string) {
|
|
26
|
+
const as = refinement.as(name)
|
|
27
|
+
const validate = {
|
|
28
|
+
is: refinement,
|
|
29
|
+
collect: refinement.asCollectable,
|
|
30
|
+
as,
|
|
31
|
+
lens: Optic.id<T2>()
|
|
32
|
+
}
|
|
33
|
+
function validatei(item: T) {
|
|
34
|
+
return {
|
|
35
|
+
get collect() {
|
|
36
|
+
return validate.collect(item)
|
|
37
|
+
},
|
|
38
|
+
get as() {
|
|
39
|
+
return validate.as(item)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...validate,
|
|
45
|
+
$item: validatei
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The idea is that such refinements are dynamic
|
|
50
|
+
export interface Collect<A, B extends A> {
|
|
51
|
+
(a: A): Option<B>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @tsplus fluent function as
|
|
56
|
+
*/
|
|
57
|
+
export function asOption<T, T2 extends T>(collect: Collect<T, T2>, name: string) {
|
|
58
|
+
return flow(collect, (_) => _.encaseInEither(() => new InvalidStateError({ message: `Cannot be ${name}` })))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @tsplus fluent function refinements
|
|
63
|
+
*/
|
|
64
|
+
export function makeAwesomeCollect<T extends Object, T2 extends T>(collect: Collect<T, T2>, name: string) {
|
|
65
|
+
const as = collect.as(name)
|
|
66
|
+
function is(item: T): item is T2 {
|
|
67
|
+
return collect(item).isSome()
|
|
68
|
+
}
|
|
69
|
+
const validate = {
|
|
70
|
+
collect,
|
|
71
|
+
is,
|
|
72
|
+
as,
|
|
73
|
+
lens: Optic.id<T2>(),
|
|
74
|
+
copy: (item: T2, _copy: Partial<Omit<T2, keyof Clone | keyof Equal>>) => copy(item, _copy),
|
|
75
|
+
clone: (item: T, cloned: T) => clone(item, cloned)
|
|
76
|
+
}
|
|
77
|
+
function validatei(item: T) {
|
|
78
|
+
return {
|
|
79
|
+
get collect() {
|
|
80
|
+
return validate.collect(item)
|
|
81
|
+
},
|
|
82
|
+
get as() {
|
|
83
|
+
return validate.as(item)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...validate,
|
|
89
|
+
$item: validatei
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export type GetCollectedType<T> = T extends { collect: Collect<any, infer U> } ? U : never
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
4
|
+
|
|
5
|
+
import { identity } from "@effect-app/core/Function"
|
|
6
|
+
import type { AnyError, Parser } from "effect-app/schema"
|
|
7
|
+
import {
|
|
8
|
+
Guard,
|
|
9
|
+
maxLengthIdentifier,
|
|
10
|
+
minLengthIdentifier,
|
|
11
|
+
NonEmptyString255,
|
|
12
|
+
NonEmptyString2k,
|
|
13
|
+
nullableIdentifier
|
|
14
|
+
} from "effect-app/schema"
|
|
15
|
+
import * as S from "effect-app/schema"
|
|
16
|
+
import type { Faker } from "@faker-js/faker"
|
|
17
|
+
import { fakerToArb, getFaker } from "../faker.js"
|
|
18
|
+
|
|
19
|
+
export { matchTag } from "@effect-app/core/utils"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A little helper to allow writing `interface X extends Identity<typeof Y>`
|
|
23
|
+
* so you don't need an intermediate type for `typeof Y`
|
|
24
|
+
*/
|
|
25
|
+
export type Identity<T> = T
|
|
26
|
+
|
|
27
|
+
export function fitIntoNonEmptyString255(str: string) {
|
|
28
|
+
if (Guard.is(NonEmptyString255)(str)) {
|
|
29
|
+
return str
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return NonEmptyString255(str.substring(0, 255 - 3) + "...")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function fitIntoNonEmptyString2k(str: string) {
|
|
36
|
+
if (Guard.is(NonEmptyString2k)(str)) {
|
|
37
|
+
return str
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return NonEmptyString2k(str.substring(0, 2047 - 3) + "...")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const fakerArb = (
|
|
44
|
+
gen: (fake: Faker) => () => string
|
|
45
|
+
): (a: any) => S.Arbitrary.Arbitrary<string> => fakerToArb(gen(getFaker()))
|
|
46
|
+
|
|
47
|
+
export function tryParse<X, A>(self: Parser.Parser<X, AnyError, A>) {
|
|
48
|
+
return (a: X, env?: Parser.ParserEnv) => {
|
|
49
|
+
const res = self(a, env).effect
|
|
50
|
+
if (res._tag === "Left") {
|
|
51
|
+
return Option.none()
|
|
52
|
+
}
|
|
53
|
+
const warn = res.right[1]
|
|
54
|
+
if (warn._tag === "Some") {
|
|
55
|
+
return Option.none()
|
|
56
|
+
}
|
|
57
|
+
return Option.some(res.right[0])
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isSchema(
|
|
62
|
+
p: Schema<any, any> | S.AnyField
|
|
63
|
+
): p is Schema<any, any> {
|
|
64
|
+
return !!(p as any)[S.SchemaSym]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getMetadataFromSchemaOrProp(p: Schema<any, any> | S.AnyField) {
|
|
68
|
+
if (isSchema(p)) {
|
|
69
|
+
return getMetadataFromSchema(p)
|
|
70
|
+
}
|
|
71
|
+
return getMetadataFromProp(p)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 1. get metadata from properties, use it to constrain fields
|
|
75
|
+
// 2. use the metadata for custom validation error messges?
|
|
76
|
+
// 3. or leverage the actual validation errors that come from parsing the fields.
|
|
77
|
+
// function getMetadataFromProp_<Field extends S.AnyField>(p: Field) {
|
|
78
|
+
// return {
|
|
79
|
+
// required: p._optional === "required",
|
|
80
|
+
// }
|
|
81
|
+
// }
|
|
82
|
+
export function getMetadataFromProp<Field extends S.AnyField>(p: Field) {
|
|
83
|
+
const schemaMetadata = getMetadataFromSchema(p._schema)
|
|
84
|
+
// const propMetadata = getMetadataFromProp_(p)
|
|
85
|
+
|
|
86
|
+
return schemaMetadata
|
|
87
|
+
// return {
|
|
88
|
+
// ...schemaMetadata,
|
|
89
|
+
// required: propMetadata.required && schemaMetadata.required,
|
|
90
|
+
// }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const numberIds = [
|
|
94
|
+
S.numberIdentifier
|
|
95
|
+
// S.stringNumberFromStringIdentifier, actually input is string
|
|
96
|
+
]
|
|
97
|
+
const intIds = [
|
|
98
|
+
S.intIdentifier,
|
|
99
|
+
S.intFromNumberIdentifier
|
|
100
|
+
]
|
|
101
|
+
const rangeNumberIds = [
|
|
102
|
+
S.rangeIdentifier,
|
|
103
|
+
S.minIdentifier,
|
|
104
|
+
S.maxIdentifier
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
export function getMetadataFromSchema<Self extends Schema<any, any>>(self: Self) {
|
|
108
|
+
const nullable = S.findAnnotation(self, nullableIdentifier)
|
|
109
|
+
const realSelf = nullable?.self ?? self
|
|
110
|
+
const minLength = S.findAnnotation(realSelf, minLengthIdentifier)
|
|
111
|
+
const maxLength = S.findAnnotation(realSelf, maxLengthIdentifier)
|
|
112
|
+
|
|
113
|
+
const min = S.findAnnotation(realSelf, S.minIdentifier)
|
|
114
|
+
const max = S.findAnnotation(realSelf, S.maxIdentifier)
|
|
115
|
+
const range = S.findAnnotation(realSelf, S.rangeIdentifier)
|
|
116
|
+
|
|
117
|
+
const isNumber = numberIds.some((_) => S.findAnnotation(realSelf, _))
|
|
118
|
+
const isInt = intIds.some((_) => S.findAnnotation(realSelf, _))
|
|
119
|
+
const asMin = min || range
|
|
120
|
+
const asMax = max || range
|
|
121
|
+
const typeN = asMin || asMax
|
|
122
|
+
return {
|
|
123
|
+
type: typeN ? typeN.type : isInt ? "int" as const : isNumber ? "float" as const : "text" as const,
|
|
124
|
+
minimum: asMin?.minimum,
|
|
125
|
+
minimumExclusive: asMin?.minimumExclusive,
|
|
126
|
+
maximum: asMax?.maximum,
|
|
127
|
+
maximumExclusive: asMax?.maximumExclusive,
|
|
128
|
+
minLength: minLength?.minLength,
|
|
129
|
+
maxLength: maxLength?.maxLength,
|
|
130
|
+
required: !nullable
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function getRegisterFromSchemaOrProp(p: Schema<any, any> | S.AnyField) {
|
|
135
|
+
if (isSchema(p)) {
|
|
136
|
+
return getRegisterFromSchema(p)
|
|
137
|
+
}
|
|
138
|
+
return getRegisterFromProp(p)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 1. get metadata from properties, use it to constrain fields
|
|
142
|
+
// 2. use the metadata for custom validation error messges?
|
|
143
|
+
// 3. or leverage the actual validation errors that come from parsing the fields.
|
|
144
|
+
|
|
145
|
+
export function getRegisterFromProp<Field extends S.AnyField>(p: Field) {
|
|
146
|
+
const schemaMetadata = getRegisterFromSchema(p._schema)
|
|
147
|
+
// const metadata = getMetadataFromProp_(p)
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...schemaMetadata
|
|
151
|
+
// optional fields should not translate values to undefined, as empty value is not absence
|
|
152
|
+
// ...(!metadata.required
|
|
153
|
+
// ? {
|
|
154
|
+
// transform: {
|
|
155
|
+
// output: (value: any) => (value ? value : undefined),
|
|
156
|
+
// input: (value: any) => (!value ? "" : value),
|
|
157
|
+
// },
|
|
158
|
+
// }
|
|
159
|
+
// : {}),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function getRegisterFromSchema<Self extends Schema<any, any>>(self: Self) {
|
|
164
|
+
// or take from openapi = number type?
|
|
165
|
+
const metadata = getMetadataFromSchema(self)
|
|
166
|
+
const nullable = S.findAnnotation(self, nullableIdentifier)
|
|
167
|
+
|
|
168
|
+
const mapType = numberIds.concat(rangeNumberIds).some((x) => S.findAnnotation(nullable?.self ?? self, x))
|
|
169
|
+
? ("asNumber" as const)
|
|
170
|
+
: ("normal" as const)
|
|
171
|
+
const map = mapValueType(mapType)
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
...(!metadata.required
|
|
175
|
+
? {
|
|
176
|
+
transform: {
|
|
177
|
+
output: (value: any) => map(value === "" ? null : value),
|
|
178
|
+
// for date fields we should not undo null..
|
|
179
|
+
// actually for string fields they appropriately convert to empty string probably anyway, so lets remove
|
|
180
|
+
// input: (value: any) => (value === null || value === undefined ? "" : value),
|
|
181
|
+
input: identity
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
: { transform: { output: map, input: identity } })
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function asNumber(value: any) {
|
|
189
|
+
return value === null || value === undefined
|
|
190
|
+
? value
|
|
191
|
+
: value === ""
|
|
192
|
+
? NaN
|
|
193
|
+
: typeof value === "string"
|
|
194
|
+
? +value.replace(",", ".")
|
|
195
|
+
: +value
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function asDate(value: any) {
|
|
199
|
+
return value === null || value === undefined ? value : new Date(value)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function mapValueType(type: "asNumber" | "asDate" | "normal") {
|
|
203
|
+
return type === "asNumber" ? asNumber : type === "asDate" ? asDate : identity
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export * from "effect-app/schema"
|
|
207
|
+
export * from "./overrides.js"
|
|
208
|
+
export { array, nonEmptyArray, set } from "./overrides.js"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
3
|
+
import { pipe } from "@effect-app/core/Function"
|
|
4
|
+
import { arbitrary, date, encoder, leafE, parseDateE, Parser, parser } from "effect-app/schema"
|
|
5
|
+
import * as Th from "@effect-app/schema/custom/These"
|
|
6
|
+
|
|
7
|
+
import { todayAtUTCNoon } from "../../utils.js"
|
|
8
|
+
|
|
9
|
+
export { matchTag } from "@effect-app/core/utils"
|
|
10
|
+
|
|
11
|
+
// workaround for strange date extension issue.
|
|
12
|
+
const subNow = (amount: number): Date => todayAtUTCNoon().subDays(amount)
|
|
13
|
+
const addNow = (amount: number): Date => todayAtUTCNoon().addDays(amount)
|
|
14
|
+
|
|
15
|
+
const dateParser = Parser.for(date)
|
|
16
|
+
|
|
17
|
+
function isProbablyADate(u: unknown): u is Date {
|
|
18
|
+
return (u instanceof Object && "toISOString" in u && "getTime" in u)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* As we want to use actual Date Objects in inputs,
|
|
23
|
+
* and instead of leveraging the parser as a decoder from JSON, we wish to use it as a validator from Inputs.
|
|
24
|
+
* This won't work with JSON because a Date is represented as an ISO string inside JSON, and when JSON is parsed, it remains a string.
|
|
25
|
+
*/
|
|
26
|
+
export const inputDate = pipe(
|
|
27
|
+
date,
|
|
28
|
+
parser((u, env) =>
|
|
29
|
+
// if it quacks like a ... Date..
|
|
30
|
+
u instanceof Date || isProbablyADate(u)
|
|
31
|
+
? Number.isNaN(u.getTime())
|
|
32
|
+
? Th.fail(leafE(parseDateE(u)))
|
|
33
|
+
: Th.succeed(u)
|
|
34
|
+
: dateParser(u, env)
|
|
35
|
+
),
|
|
36
|
+
encoder((i): Date => i.toISOString() as unknown as Date /* sue me*/),
|
|
37
|
+
arbitrary((FC) =>
|
|
38
|
+
FC.date({
|
|
39
|
+
min: subNow(350),
|
|
40
|
+
max: addNow(350)
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export type inputDate = Date
|
|
46
|
+
export type InputDate = inputDate
|
|
47
|
+
|
|
48
|
+
export const reasonablePastDate = date.pipe(
|
|
49
|
+
arbitrary((FC) =>
|
|
50
|
+
FC.date({
|
|
51
|
+
min: subNow(350),
|
|
52
|
+
max: subNow(1)
|
|
53
|
+
})
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
export type reasonablePastDate = Date
|
|
57
|
+
export type ReasonablePastDate = reasonablePastDate
|
|
58
|
+
|
|
59
|
+
export const reasonableFutureDate = date.pipe(
|
|
60
|
+
arbitrary((FC) =>
|
|
61
|
+
FC.date({
|
|
62
|
+
min: addNow(350),
|
|
63
|
+
max: addNow(1)
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
export type ReasonableFutureDate = Date
|
|
68
|
+
|
|
69
|
+
export const reasonableDate = date.pipe(
|
|
70
|
+
arbitrary((FC) =>
|
|
71
|
+
FC.date({
|
|
72
|
+
min: subNow(350),
|
|
73
|
+
max: addNow(350)
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
export type reasonableDate = Date
|
|
78
|
+
export type ReasonableDate = reasonableDate
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { brand } from "./_schema.js"
|
|
2
|
+
import { PositiveNumber } from "./overrides.js"
|
|
3
|
+
|
|
4
|
+
// codegen:start {preset: barrel, include: ./api/*.ts}
|
|
5
|
+
export * from "./api/date.js"
|
|
6
|
+
// codegen:end
|
|
7
|
+
|
|
8
|
+
// TODO: true decimal
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated - implement true decimal!
|
|
11
|
+
*/
|
|
12
|
+
export const PositiveDecimal = PositiveNumber.pipe(brand<PositiveDecimal>())
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated - implement true decimal!
|
|
15
|
+
*/
|
|
16
|
+
export type PositiveDecimal = PositiveNumber & DecimalBrand
|
|
17
|
+
|
|
18
|
+
export interface DecimalBrand {
|
|
19
|
+
readonly Decimal: unique symbol
|
|
20
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
3
|
+
|
|
4
|
+
import { ReadonlySet } from "@effect-app/core/Prelude"
|
|
5
|
+
import { arbitrary, type PositiveBrand, positiveExcludeZero, type PositiveExcludeZeroBrand } from "effect-app/schema"
|
|
6
|
+
import { brand, positive } from "effect-app/schema"
|
|
7
|
+
import { number } from "effect/Equivalence"
|
|
8
|
+
import { Arbitrary } from "fast-check"
|
|
9
|
+
|
|
10
|
+
export const PositiveNumber = positive("float")(number).pipe(brand<PositiveNumber>())
|
|
11
|
+
export type PositiveNumber = number & PositiveBrand
|
|
12
|
+
|
|
13
|
+
export const PositiveNumberZeroExclusive = positiveExcludeZero("float")(number).pipe(brand<PositiveNumber>())
|
|
14
|
+
export type PositiveNumberZeroExclusive = number & PositiveExcludeZeroBrand
|
|
15
|
+
|
|
16
|
+
export interface CentimeterBrand extends PositiveBrand {
|
|
17
|
+
readonly CentimeterBrand: unique symbol
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type Centimeter = number & CentimeterBrand
|
|
21
|
+
|
|
22
|
+
export const Centimeter = positive("float")(number).pipe(brand<Centimeter>())
|
|
23
|
+
|
|
24
|
+
export interface KilogramBrand extends PositiveBrand {
|
|
25
|
+
readonly KilogramBrand: unique symbol
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Kilogram = number & KilogramBrand
|
|
29
|
+
|
|
30
|
+
export const Kilogram = positive("float")(number).pipe(brand<Kilogram>())
|
|
31
|
+
|
|
32
|
+
// Limit arbitrary collections to generate a max of 6 entries
|
|
33
|
+
// TODO: dictionary, map
|
|
34
|
+
const MAX_LENGTH = 6
|
|
35
|
+
|
|
36
|
+
export function nonEmptyArray<To, ConstructorInput, From, Api>(
|
|
37
|
+
self: Schema<unknown, To, ConstructorInput, From, Api>
|
|
38
|
+
) {
|
|
39
|
+
const arbitrarySelf = Arbitrary.for(self)
|
|
40
|
+
|
|
41
|
+
return nonEmptyArrayOriginal(self).pipe(
|
|
42
|
+
arbitrary(
|
|
43
|
+
(_) =>
|
|
44
|
+
_.array(arbitrarySelf(_), {
|
|
45
|
+
minLength: 1,
|
|
46
|
+
maxLength: MAX_LENGTH
|
|
47
|
+
}) as any as Arbitrary.Arbitrary<NonEmptyReadonlyArray<To>>
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function array<To, ConstructorInput, From, Api>(
|
|
53
|
+
self: Schema<unknown, To, ConstructorInput, From, Api>
|
|
54
|
+
) {
|
|
55
|
+
const arbitrarySelf = Arbitrary.for(self)
|
|
56
|
+
|
|
57
|
+
return arrayOriginal(self).pipe(
|
|
58
|
+
arbitrary(
|
|
59
|
+
(_) =>
|
|
60
|
+
_.array(arbitrarySelf(_), {
|
|
61
|
+
maxLength: MAX_LENGTH
|
|
62
|
+
}) as any as Arbitrary.Arbitrary<Array<To>>
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function set<To, ConstructorInput, From, Api>(
|
|
68
|
+
self: Schema<unknown, To, ConstructorInput, From, Api>,
|
|
69
|
+
ord: Order<To>,
|
|
70
|
+
eq: Equivalence<To>
|
|
71
|
+
) {
|
|
72
|
+
const arbitrarySelf = Arbitrary.for(self)
|
|
73
|
+
return setOriginal(self, ord, eq).pipe(
|
|
74
|
+
arbitrary((_) => _.uniqueArray(arbitrarySelf(_), { maxLength: MAX_LENGTH }).map(ReadonlySet.fromArray(eq)))
|
|
75
|
+
)
|
|
76
|
+
}
|