| 1 | import type { Effect } from "effect/Effect"; |
| 2 | import * as S from "effect/Schema"; |
| 3 | import * as AST from "effect/SchemaAST"; |
| 4 | import type { Sink } from "effect/Sink"; |
| 5 | import type { Stream } from "effect/Stream"; |
| 6 | |
| 7 | export * from "effect/Schema"; |
| 8 | |
| 9 | export const isNullishSchema = (schema: S.Schema<any>) => |
| 10 | isNullSchema(schema) || isUndefinedSchema(schema); |
| 11 | export const isNullSchema = (schema: S.Schema<any>) => AST.isNull(schema.ast); |
| 12 | export const isUndefinedSchema = (schema: S.Schema<any>) => |
| 13 | AST.isUndefined(schema.ast); |
| 14 | export const isBooleanSchema = (schema: S.Schema<any>) => |
| 15 | AST.isBoolean(schema.ast); |
| 16 | export const isStringSchema = (schema: S.Schema<any>) => |
| 17 | AST.isString(schema.ast); |
| 18 | export const isNumberSchema = (schema: S.Schema<any>) => |
| 19 | AST.isNumber(schema.ast); |
| 20 | |
| 21 | export const isRecordLikeSchema = (schema: S.Schema<any>) => |
| 22 | isMapSchema(schema) || |
| 23 | isRecordSchema(schema) || |
| 24 | isStructSchema(schema) || |
| 25 | isClassSchema(schema) || |
| 26 | false; |
| 27 | |
| 28 | export const isMapSchema = (schema: S.Schema<any>): boolean => |
| 29 | getDeclarationRepresentationId(schema.ast) === "effect/schema/ReadonlyMap"; |
| 30 | |
| 31 | export const isClassSchema = (schema: S.Schema<any>) => { |
| 32 | const ast = schema.ast; |
| 33 | if (AST.isDeclaration(ast)) { |
| 34 | return AST.isObjects(ast.typeParameters[0]); |
| 35 | } |
| 36 | return false; |
| 37 | }; |
| 38 | |
| 39 | export const isStructSchema = (schema: S.Schema<any>) => { |
| 40 | return AST.isObjects(schema.ast); |
| 41 | }; |
| 42 | |
| 43 | export const isRecordSchema = (schema: S.Schema<any>) => { |
| 44 | const ast = schema.ast; |
| 45 | return AST.isObjects(ast) && ast.indexSignatures?.length > 0; |
| 46 | }; |
| 47 | |
| 48 | export const isListSchema = (schema: S.Schema<any>) => { |
| 49 | return AST.isArrays(schema.ast); |
| 50 | }; |
| 51 | |
| 52 | export const isSetSchema = (schema: S.Schema<any>): boolean => |
| 53 | getDeclarationRepresentationId(schema.ast) === "effect/schema/ReadonlySet"; |
| 54 | |
| 55 | /** |
| 56 | * Effect's built-in declaration schemas (ReadonlyMap, ReadonlySet, ...) carry a |
| 57 | * `representation` annotation identifying the underlying type constructor. |
| 58 | */ |
| 59 | const getDeclarationRepresentationId = (ast: AST.AST): string | undefined => { |
| 60 | if (AST.isDeclaration(ast)) { |
| 61 | return (ast.annotations as any)?.representation?.id; |
| 62 | } |
| 63 | return undefined; |
| 64 | }; |
| 65 | |
| 66 | export const getSetValueAST = (schema: S.Schema<any>): AST.AST | undefined => { |
| 67 | const ast = schema.ast; |
| 68 | if (AST.isDeclaration(ast) && isSetSchema(schema)) { |
| 69 | return ast.typeParameters[0]; |
| 70 | } |
| 71 | return undefined; |
| 72 | }; |
| 73 | |
| 74 | /** A Schema representing a Schema */ |
| 75 | export type Field = S.Top; |
| 76 | export const Field = S.suspend( |
| 77 | (): [Field] extends [any] ? S.Schema<Field> : never => S.Any, |
| 78 | ); |
| 79 | |
| 80 | type FunctionType = (...args: any[]) => any; |
| 81 | export type Function<F extends FunctionType = FunctionType> = S.Schema<F>; |
| 82 | export const Function: <F extends FunctionType>() => Function<F> = S.suspend( |
| 83 | (): S.Schema<FunctionType> => S.Any, |
| 84 | ) as any; |
| 85 | |
| 86 | export type CreatedAt = Date; |
| 87 | export const CreatedAt = S.Date.annotate({ |
| 88 | description: "The timestamp of when this record was created", |
| 89 | }); |
| 90 | |
| 91 | export type UpdatedAt = Date; |
| 92 | export const UpdatedAt = S.Date.annotate({ |
| 93 | description: "The timestamp of when this record was last updated", |
| 94 | }); |
| 95 | |
| 96 | export type AnyClassSchema< |
| 97 | Self = any, |
| 98 | Fields extends S.Top & { fields: S.Struct.Fields } = S.Top & { |
| 99 | fields: S.Struct.Fields; |
| 100 | }, |
| 101 | > = S.Class<Self, Fields, any>; |
| 102 | |
| 103 | export type AnyClass = new (...args: any[]) => any; |
| 104 | |
| 105 | export type AnyErrorSchema = S.Class<any, any, any>; |
| 106 | |
| 107 | export type SchemaWithTemplate< |
| 108 | Schema extends S.Schema<any>, |
| 109 | References extends any[] = any[], |
| 110 | > = Schema & { |
| 111 | template: TemplateStringsArray; |
| 112 | references: References; |
| 113 | }; |
| 114 | |
| 115 | export type SchemaExt = |
| 116 | | FunctionSchema |
| 117 | | EffectSchema |
| 118 | | StreamSchema |
| 119 | | SinkSchema; |
| 120 | |
| 121 | export interface SchemaExtBase<A> extends S.Schema<A> { |
| 122 | <References extends any[]>( |
| 123 | template: TemplateStringsArray, |
| 124 | ...references: References |
| 125 | ): SchemaWithTemplate<this, References>; |
| 126 | } |
| 127 | |
| 128 | export interface FunctionSchema< |
| 129 | Input extends S.Top | undefined = S.Top | undefined, |
| 130 | Output extends S.Top = S.Top, |
| 131 | > extends SchemaExtBase< |
| 132 | ( |
| 133 | ...args: Input extends undefined |
| 134 | ? [] |
| 135 | : [input: S.Schema.Type<Exclude<Input, undefined>>] |
| 136 | ) => S.Schema.Type<Output> |
| 137 | > { |
| 138 | input: Input; |
| 139 | output: Output; |
| 140 | } |
| 141 | |
| 142 | export interface EffectSchema< |
| 143 | A extends S.Top = S.Top, |
| 144 | Err extends S.Top = S.Top, |
| 145 | Req extends S.Top = S.Top, |
| 146 | > extends SchemaExtBase< |
| 147 | Effect<S.Schema.Type<A>, S.Schema.Type<Err>, S.Schema.Type<Req>> |
| 148 | > { |
| 149 | A: A; |
| 150 | Err: Err; |
| 151 | Req: Req; |
| 152 | } |
| 153 | |
| 154 | export interface StreamSchema< |
| 155 | A extends S.Top = S.Top, |
| 156 | Err extends S.Top = S.Top, |
| 157 | Req extends S.Top = S.Top, |
| 158 | > extends SchemaExtBase< |
| 159 | Stream<S.Schema.Type<A>, S.Schema.Type<Err>, S.Schema.Type<Req>> |
| 160 | > { |
| 161 | A: A; |
| 162 | Err: Err; |
| 163 | Req: Req; |
| 164 | } |
| 165 | |
| 166 | export interface SinkSchema< |
| 167 | A extends S.Top = S.Top, |
| 168 | In extends S.Top = S.Top, |
| 169 | L extends S.Top = S.Top, |
| 170 | Err extends S.Top = S.Top, |
| 171 | Req extends S.Top = S.Top, |
| 172 | > extends SchemaExtBase< |
| 173 | Sink< |
| 174 | S.Schema.Type<A>, |
| 175 | S.Schema.Type<In>, |
| 176 | S.Schema.Type<L>, |
| 177 | S.Schema.Type<Err>, |
| 178 | S.Schema.Type<Req> |
| 179 | > |
| 180 | > { |
| 181 | A: A; |
| 182 | In: In; |
| 183 | L: L; |
| 184 | Err: Err; |
| 185 | Req: Req; |
| 186 | } |
| 187 | |
| 188 | export const makeExtSchema = <Schema extends SchemaExt>( |
| 189 | schema: Schema, |
| 190 | ): SchemaExt => { |
| 191 | const s = S.Any.annotate({ |
| 192 | aspect: schema, |
| 193 | }); |
| 194 | return new Proxy(() => {}, { |
| 195 | get: (_target, prop) => s[prop as keyof typeof s], |
| 196 | apply: (_target, _thisArg, [template, ...references]) => { |
| 197 | return S.annotate({ |
| 198 | aspect: { |
| 199 | ...schema, |
| 200 | template, |
| 201 | references, |
| 202 | }, |
| 203 | }); |
| 204 | }, |
| 205 | }) as any as SchemaExt; |
| 206 | }; |
| 207 | |
| 208 | // export type def = typeof def; |
| 209 | |
| 210 | export interface func< |
| 211 | Input extends undefined | S.Top | S.Top[], |
| 212 | Output extends S.Top, |
| 213 | > extends S.Schema< |
| 214 | Input extends undefined |
| 215 | ? () => S.Schema.Type<Output> |
| 216 | : Input extends S.Top[] |
| 217 | ? (...args: TypeArray<Input>) => S.Schema.Type<Output> |
| 218 | : (input: S.Schema.Type<Extract<Input, S.Top>>) => S.Schema.Type<Output> |
| 219 | > {} |
| 220 | |
| 221 | type TypeArray<T extends S.Top[]> = T extends [ |
| 222 | infer Head, |
| 223 | ...infer Tail extends S.Top[], |
| 224 | ] |
| 225 | ? Head extends S.Top |
| 226 | ? [S.Schema.Type<Head>, ...TypeArray<Tail>] |
| 227 | : never |
| 228 | : []; |
| 229 | |
| 230 | export const func: { |
| 231 | <Output extends S.Schema<any>>( |
| 232 | output: Output, |
| 233 | ): func<undefined, Output> & { |
| 234 | <R extends any[]>( |
| 235 | template: TemplateStringsArray, |
| 236 | ...references: R |
| 237 | ): SchemaWithTemplate<func<undefined, Output>, R>; |
| 238 | }; |
| 239 | <Input extends S.Schema<any>, Output extends S.Schema<any>>( |
| 240 | input: Input, |
| 241 | output: Output, |
| 242 | ): func<Input, Output> & { |
| 243 | <R extends any[]>( |
| 244 | template: TemplateStringsArray, |
| 245 | ...references: R |
| 246 | ): SchemaWithTemplate<func<Input, Output>, R>; |
| 247 | }; |
| 248 | <const Args extends S.Schema<any>[], Output extends S.Schema<any>>( |
| 249 | args: Args, |
| 250 | output: Output, |
| 251 | ): func<Args, Output> & { |
| 252 | <R extends any[]>( |
| 253 | template: TemplateStringsArray, |
| 254 | ...references: R |
| 255 | ): SchemaWithTemplate<func<Args, Output>, R>; |
| 256 | }; |
| 257 | } = ((input: any, output: any) => |
| 258 | S.Any.annotate({ |
| 259 | aspect: { |
| 260 | type: "fn", |
| 261 | input: output ? input : undefined, |
| 262 | output: output ?? input, |
| 263 | }, |
| 264 | })) as any; |
| 265 | |
| 266 | export interface effect< |
| 267 | A extends S.Top, |
| 268 | Err extends S.Top, |
| 269 | Req extends S.Top, |
| 270 | > extends S.Schema< |
| 271 | Effect<S.Schema.Type<A>, S.Schema.Type<Err>, S.Schema.Type<Req>> |
| 272 | > {} |
| 273 | |
| 274 | export const effect = < |
| 275 | A extends S.Schema<any>, |
| 276 | Err extends S.Schema<any> | S.Never = S.Never, |
| 277 | Req extends S.Schema<any> | S.Any = S.Any, |
| 278 | >( |
| 279 | a: A, |
| 280 | err: Err = S.Never as any, |
| 281 | req: Req = S.Never as any, |
| 282 | ): effect<A, Err, Req> => |
| 283 | S.Any.annotate({ |
| 284 | aspect: { |
| 285 | type: "effect", |
| 286 | a: a, |
| 287 | err: err, |
| 288 | req: req, |
| 289 | }, |
| 290 | }); |
| 291 | |
| 292 | export const stream = < |
| 293 | A extends S.Schema<any>, |
| 294 | Err extends S.Schema<any> | S.Never = S.Never, |
| 295 | Req extends S.Schema<any> | S.Never = S.Never, |
| 296 | >( |
| 297 | a: A, |
| 298 | err: Err = S.Never as any, |
| 299 | req: Req = S.Never as any, |
| 300 | ) => |
| 301 | S.Any.annotate({ |
| 302 | aspect: { |
| 303 | type: "stream", |
| 304 | a: a, |
| 305 | err: err, |
| 306 | req: req, |
| 307 | }, |
| 308 | }); |
| 309 | |
| 310 | export const sink = < |
| 311 | A extends S.Schema<any>, |
| 312 | In extends S.Schema<any>, |
| 313 | L extends S.Schema<any>, |
| 314 | Err extends S.Schema<any> | S.Never = S.Never, |
| 315 | Req extends S.Schema<any> | S.Never = S.Never, |
| 316 | >( |
| 317 | a: A, |
| 318 | _in: In = S.Never as any, |
| 319 | l: L = S.Never as any, |
| 320 | err: Err = S.Never as any, |
| 321 | req: Req = S.Never as any, |
| 322 | ) => |
| 323 | S.Any.annotate({ |
| 324 | aspect: { |
| 325 | type: "sink", |
| 326 | a: a, |
| 327 | in: _in, |
| 328 | l: l, |
| 329 | err: err, |
| 330 | req: req, |
| 331 | }, |
| 332 | }); |