1import type { Effect } from "effect/Effect";
2import * as S from "effect/Schema";
3import * as AST from "effect/SchemaAST";
4import type { Sink } from "effect/Sink";
5import type { Stream } from "effect/Stream";
6
7export * from "effect/Schema";
8
9export const isNullishSchema = (schema: S.Schema<any>) =>
10 isNullSchema(schema) || isUndefinedSchema(schema);
11export const isNullSchema = (schema: S.Schema<any>) => AST.isNull(schema.ast);
12export const isUndefinedSchema = (schema: S.Schema<any>) =>
13 AST.isUndefined(schema.ast);
14export const isBooleanSchema = (schema: S.Schema<any>) =>
15 AST.isBoolean(schema.ast);
16export const isStringSchema = (schema: S.Schema<any>) =>
17 AST.isString(schema.ast);
18export const isNumberSchema = (schema: S.Schema<any>) =>
19 AST.isNumber(schema.ast);
20
21export const isRecordLikeSchema = (schema: S.Schema<any>) =>
22 isMapSchema(schema) ||
23 isRecordSchema(schema) ||
24 isStructSchema(schema) ||
25 isClassSchema(schema) ||
26 false;
27
28export const isMapSchema = (schema: S.Schema<any>): boolean =>
29 getDeclarationRepresentationId(schema.ast) === "effect/schema/ReadonlyMap";
30
31export 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
39export const isStructSchema = (schema: S.Schema<any>) => {
40 return AST.isObjects(schema.ast);
41};
42
43export const isRecordSchema = (schema: S.Schema<any>) => {
44 const ast = schema.ast;
45 return AST.isObjects(ast) && ast.indexSignatures?.length > 0;
46};
47
48export const isListSchema = (schema: S.Schema<any>) => {
49 return AST.isArrays(schema.ast);
50};
51
52export 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 */
59const 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
66export 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 */
75export type Field = S.Top;
76export const Field = S.suspend(
77 (): [Field] extends [any] ? S.Schema<Field> : never => S.Any,
78);
79
80type FunctionType = (...args: any[]) => any;
81export type Function<F extends FunctionType = FunctionType> = S.Schema<F>;
82export const Function: <F extends FunctionType>() => Function<F> = S.suspend(
83 (): S.Schema<FunctionType> => S.Any,
84) as any;
85
86export type CreatedAt = Date;
87export const CreatedAt = S.Date.annotate({
88 description: "The timestamp of when this record was created",
89});
90
91export type UpdatedAt = Date;
92export const UpdatedAt = S.Date.annotate({
93 description: "The timestamp of when this record was last updated",
94});
95
96export 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
103export type AnyClass = new (...args: any[]) => any;
104
105export type AnyErrorSchema = S.Class<any, any, any>;
106
107export type SchemaWithTemplate<
108 Schema extends S.Schema<any>,
109 References extends any[] = any[],
110> = Schema & {
111 template: TemplateStringsArray;
112 references: References;
113};
114
115export type SchemaExt =
116 | FunctionSchema
117 | EffectSchema
118 | StreamSchema
119 | SinkSchema;
120
121export interface SchemaExtBase<A> extends S.Schema<A> {
122 <References extends any[]>(
123 template: TemplateStringsArray,
124 ...references: References
125 ): SchemaWithTemplate<this, References>;
126}
127
128export 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
142export 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
154export 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
166export 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
188export 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
210export 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
221type 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
230export 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
266export 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
274export 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
292export 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
310export 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 });