effect-app 4.0.0-beta.310 → 4.0.0-beta.312
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/CHANGELOG.md +31 -0
- package/dist/Effect.js +2 -2
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +49 -8
- package/dist/Schema/Class.d.ts +28 -17
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +16 -8
- package/dist/Schema/SpecialJsonSchema.d.ts +1 -1
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -1
- package/dist/Schema/SpecialJsonSchema.js +3 -4
- package/dist/Schema/ext.d.ts +5 -5
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +9 -10
- package/dist/Schema/moreStrings.js +1 -1
- package/package.json +3 -3
- package/src/Effect.ts +1 -1
- package/src/Model/Repository/internal/internal.ts +51 -7
- package/src/Schema/Class.ts +45 -31
- package/src/Schema/SpecialJsonSchema.ts +2 -4
- package/src/Schema/ext.ts +9 -10
- package/src/Schema/moreStrings.ts +2 -2
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/moreStrings.test.ts +1 -1
- package/test/rpc.test.ts +1 -1
- package/test/special.test.ts +27 -28
package/src/Schema/Class.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type * as Cause from "effect/Cause"
|
|
3
3
|
import * as Effect from "effect/Effect"
|
|
4
|
-
import * as Option from "effect/Option"
|
|
5
4
|
import * as S from "effect/Schema"
|
|
6
5
|
import * as SchemaGetter from "effect/SchemaGetter"
|
|
7
6
|
import * as SchemaIssue from "effect/SchemaIssue"
|
|
@@ -14,9 +13,11 @@ import * as SchemaParser from "./SchemaParser.ts"
|
|
|
14
13
|
|
|
15
14
|
type ClassAnnotations<Self> = S.Annotations.Declaration<Self, readonly [any]>
|
|
16
15
|
|
|
17
|
-
export interface EnhancedClass<
|
|
18
|
-
|
|
19
|
-
{
|
|
16
|
+
export interface EnhancedClass<
|
|
17
|
+
Self,
|
|
18
|
+
SchemaS extends S.Constraint & { readonly fields: S.Struct.Fields },
|
|
19
|
+
Inherited
|
|
20
|
+
> extends S.Class<Self, SchemaS, Inherited> {
|
|
20
21
|
/**
|
|
21
22
|
* See `copyOrigin` docs in `utils.ts` for return-type design details.
|
|
22
23
|
*/
|
|
@@ -39,12 +40,22 @@ export declare const ExtendedSchemaNoEncoded: unique symbol
|
|
|
39
40
|
|
|
40
41
|
export type ExtendedSchemaNoEncoded = typeof ExtendedSchemaNoEncoded
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Override schema `Encoded` while keeping `Constraint` (intersection, not `Omit`).
|
|
45
|
+
* Free type-params for `Encoded` keep the conditional unresolved — callers that
|
|
46
|
+
* need Encoded overrides use the intersection form of the class factory return type.
|
|
47
|
+
*/
|
|
48
|
+
type WithEncoded<SchemaS extends S.Constraint, Encoded> = SchemaS & { readonly Encoded: Encoded }
|
|
43
49
|
|
|
44
|
-
type ExtendedSchema<SchemaS extends S.
|
|
50
|
+
type ExtendedSchema<SchemaS extends S.Constraint & { readonly fields: S.Struct.Fields }, Encoded> = [
|
|
51
|
+
Encoded
|
|
52
|
+
] extends [ExtendedSchemaNoEncoded] ? SchemaS
|
|
45
53
|
: WithEncoded<SchemaS, Encoded>
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
/** Struct fields schema, optionally with a caller-supplied Encoded override. */
|
|
56
|
+
type ClassFieldsSchema<Fields extends S.Struct.Fields, Encoded> = ExtendedSchema<S.Struct<Fields>, Encoded>
|
|
57
|
+
|
|
58
|
+
export type Class<Self, S extends S.Constraint & { readonly fields: S.Struct.Fields }, Inherited> = EnhancedClass<
|
|
48
59
|
Self,
|
|
49
60
|
S,
|
|
50
61
|
Inherited
|
|
@@ -73,7 +84,7 @@ function makeRelaxedDeclaration(
|
|
|
73
84
|
if (input !== null && typeof input === "object") {
|
|
74
85
|
return decodeStruct(input, options)
|
|
75
86
|
}
|
|
76
|
-
return Effect.fail(new SchemaIssue.InvalidType(self,
|
|
87
|
+
return Effect.fail(new SchemaIssue.InvalidType(self, input))
|
|
77
88
|
},
|
|
78
89
|
ast.annotations,
|
|
79
90
|
ast.checks,
|
|
@@ -114,7 +125,7 @@ export const Class: <Self = never, Encoded = ExtendedSchemaNoEncoded, Brand = {}
|
|
|
114
125
|
) => [Self] extends [never] ? MissingSelfGeneric<"Class">
|
|
115
126
|
: EnhancedClass<
|
|
116
127
|
Self,
|
|
117
|
-
|
|
128
|
+
ClassFieldsSchema<Fields, Encoded>,
|
|
118
129
|
Brand
|
|
119
130
|
> = (identifier) => (fields, annotations, options) => {
|
|
120
131
|
const relaxed = options?.strict === false
|
|
@@ -182,7 +193,7 @@ export const TaggedClass: <Self = never, Encoded = ExtendedSchemaNoEncoded, Bran
|
|
|
182
193
|
) => [Self] extends [never] ? MissingSelfGeneric<"TaggedClass">
|
|
183
194
|
: EnhancedClass<
|
|
184
195
|
Self,
|
|
185
|
-
|
|
196
|
+
ClassFieldsSchema<{ readonly _tag: S.tag<Tag> } & Fields, Encoded>,
|
|
186
197
|
Brand
|
|
187
198
|
> = (identifier) => (tag, fields, annotations, options) => {
|
|
188
199
|
const relaxed = options?.strict === false
|
|
@@ -227,11 +238,11 @@ export const ErrorClass: <Self = never, Encoded = ExtendedSchemaNoEncoded, Brand
|
|
|
227
238
|
) => [Self] extends [never] ? MissingSelfGeneric<"ErrorClass">
|
|
228
239
|
: EnhancedClass<
|
|
229
240
|
Self,
|
|
230
|
-
|
|
241
|
+
ClassFieldsSchema<Fields, Encoded>,
|
|
231
242
|
Cause.YieldableError & Brand
|
|
232
243
|
> = (identifier) => (fields, annotations, options) => {
|
|
233
244
|
const relaxed = options?.strict === false
|
|
234
|
-
const Base = (S.
|
|
245
|
+
const Base = (S.Error as any)(identifier)(fields, { ...concurrencyUnbounded, ...annotations })
|
|
235
246
|
const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")!
|
|
236
247
|
const astCache = new WeakMap<any, SchemaAST.Declaration>()
|
|
237
248
|
const copyCache = new WeakMap<any, ReturnType<typeof copyOrigin>>()
|
|
@@ -273,11 +284,11 @@ export const TaggedErrorClass: <Self = never, Encoded = ExtendedSchemaNoEncoded,
|
|
|
273
284
|
) => [Self] extends [never] ? MissingSelfGeneric<"TaggedErrorClass">
|
|
274
285
|
: EnhancedClass<
|
|
275
286
|
Self,
|
|
276
|
-
|
|
287
|
+
ClassFieldsSchema<{ readonly _tag: S.tag<Tag> } & Fields, Encoded>,
|
|
277
288
|
Cause.YieldableError & Brand
|
|
278
289
|
> = (identifier) => (tag, fields, annotations, options) => {
|
|
279
290
|
const relaxed = options?.strict === false
|
|
280
|
-
const Base = (S.
|
|
291
|
+
const Base = (S.TaggedError as any)(identifier)(tag, fields, { ...concurrencyUnbounded, ...annotations })
|
|
281
292
|
const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")!
|
|
282
293
|
const astCache = new WeakMap<any, SchemaAST.Declaration>()
|
|
283
294
|
const copyCache = new WeakMap<any, ReturnType<typeof copyOrigin>>()
|
|
@@ -305,9 +316,9 @@ export const TaggedErrorClass: <Self = never, Encoded = ExtendedSchemaNoEncoded,
|
|
|
305
316
|
} as any
|
|
306
317
|
}
|
|
307
318
|
|
|
308
|
-
export interface Opaque<Self, Encoded, SchemaS extends S.Top, Brand>
|
|
309
|
-
extends
|
|
310
|
-
|
|
319
|
+
export interface Opaque<Self, Encoded, SchemaS extends S.Top, Brand> extends S.Opaque<Self, SchemaS, Brand> {
|
|
320
|
+
readonly "Encoded": Encoded extends ExtendedSchemaNoEncoded ? SchemaS["Encoded"] : Encoded
|
|
321
|
+
}
|
|
311
322
|
|
|
312
323
|
export const Opaque: <Self, Encoded = ExtendedSchemaNoEncoded, Brand = {}>() => <S extends S.Top>(
|
|
313
324
|
schema: S
|
|
@@ -338,9 +349,8 @@ export const Opaque: <Self, Encoded = ExtendedSchemaNoEncoded, Brand = {}>() =>
|
|
|
338
349
|
* (`~type.make.in`) and other derived members are still computed from the struct. See
|
|
339
350
|
* a future `OpaqueShape`-style helper if those also need to be supplied.
|
|
340
351
|
*/
|
|
341
|
-
export interface OpaqueType<Self, Encoded, SchemaS extends S.Top, Brand>
|
|
342
|
-
extends
|
|
343
|
-
{
|
|
352
|
+
export interface OpaqueType<Self, Encoded, SchemaS extends S.Top, Brand> extends S.Opaque<Self, SchemaS, Brand> {
|
|
353
|
+
readonly "Encoded": Encoded extends ExtendedSchemaNoEncoded ? SchemaS["Encoded"] : Encoded
|
|
344
354
|
new(_: never): Self
|
|
345
355
|
}
|
|
346
356
|
|
|
@@ -348,12 +358,6 @@ export const OpaqueType: <Self, Encoded = ExtendedSchemaNoEncoded, Brand = {}>()
|
|
|
348
358
|
schema: S
|
|
349
359
|
) => OpaqueType<Self, Encoded, S, Brand> & Omit<S, keyof S.Top> = S.Opaque as any
|
|
350
360
|
|
|
351
|
-
// Override both the `Encoded` and make-input (`~type.make.in`) members in one go,
|
|
352
|
-
// like `ExtendedSchema` does for `Encoded` alone.
|
|
353
|
-
type ExtendedShape<SchemaS extends S.Top, Encoded, MakeIn> =
|
|
354
|
-
& Omit<SchemaS, "Encoded" | "~type.make.in">
|
|
355
|
-
& { readonly Encoded: Encoded; readonly "~type.make.in": MakeIn }
|
|
356
|
-
|
|
357
361
|
type OpaqueFacadeConstructorArgs<MakeIn> = {} extends MakeIn ? [props?: MakeIn, options?: S.MakeOptions]
|
|
358
362
|
: [props: MakeIn, options?: S.MakeOptions]
|
|
359
363
|
|
|
@@ -376,7 +380,9 @@ type OpaqueClassFacadeInput<DecodingServices, EncodingServices> =
|
|
|
376
380
|
|
|
377
381
|
type PrototypeFunction = Function & { prototype: object }
|
|
378
382
|
|
|
379
|
-
|
|
383
|
+
// Class schemas expose a constructor descriptor under this key (was previously
|
|
384
|
+
// identified via a `~effect/Schema/Class` annotation id).
|
|
385
|
+
const ConstructorAnnotationKey = "~constructor"
|
|
380
386
|
|
|
381
387
|
const getOwnOrInheritedPropertyDescriptor = (value: object, property: PropertyKey): PropertyDescriptor | undefined => {
|
|
382
388
|
let current: object | null = value
|
|
@@ -389,13 +395,19 @@ const getOwnOrInheritedPropertyDescriptor = (value: object, property: PropertyKe
|
|
|
389
395
|
|
|
390
396
|
const isClassSchemaConstructor = (value: unknown): value is PrototypeFunction => {
|
|
391
397
|
if (typeof value !== "function") return false
|
|
398
|
+
// Transformed schemas (e.g. `Class.pipe(S.encodeKeys(...))`) are also functions with a
|
|
399
|
+
// Declaration AST that still carries `~constructor`, but they are not constructable classes.
|
|
400
|
+
// Real Schema.Class constructors expose a static `identifier`.
|
|
401
|
+
if (typeof (value as { readonly identifier?: unknown }).identifier !== "string") return false
|
|
392
402
|
const ast = (value as { readonly ast?: unknown }).ast
|
|
393
|
-
return SchemaAST.isAST(ast)
|
|
403
|
+
return SchemaAST.isAST(ast)
|
|
404
|
+
&& SchemaAST.isDeclaration(ast)
|
|
405
|
+
&& ast.annotations?.[ConstructorAnnotationKey] !== undefined
|
|
394
406
|
}
|
|
395
407
|
|
|
396
408
|
const getFacadeClassSchema = (schema: OpaqueFacadeInput<any, any>): PrototypeFunction | undefined => {
|
|
397
409
|
if (isClassSchemaConstructor(schema)) return schema
|
|
398
|
-
const target = "to" in schema ? schema.to : undefined
|
|
410
|
+
const target = "to" in schema ? (schema as { readonly to?: unknown }).to : undefined
|
|
399
411
|
return isClassSchemaConstructor(target) ? target : undefined
|
|
400
412
|
}
|
|
401
413
|
|
|
@@ -451,8 +463,10 @@ type OpaqueFacadeStatics<SchemaS extends S.Top> = Omit<SchemaS, Exclude<keyof S.
|
|
|
451
463
|
* {@link OpaqueType}.
|
|
452
464
|
*/
|
|
453
465
|
export interface OpaqueShape<Self, Encoded, MakeIn, SchemaS extends S.Top, Brand>
|
|
454
|
-
extends S.Opaque<Self,
|
|
466
|
+
extends S.Opaque<Self, SchemaS, Brand>
|
|
455
467
|
{
|
|
468
|
+
readonly "Encoded": Encoded
|
|
469
|
+
readonly "~type.make.in": MakeIn
|
|
456
470
|
new(_: never): Self
|
|
457
471
|
}
|
|
458
472
|
|
|
@@ -625,7 +639,7 @@ export function OpaqueErrorFacadeClass<
|
|
|
625
639
|
& OpaqueErrorFacadeClass<Self, Encoded, MakeIn, DecodingServices, EncodingServices, Brand>
|
|
626
640
|
& OpaqueFacadeStatics<SchemaS>
|
|
627
641
|
|
|
628
|
-
if (
|
|
642
|
+
if (isClassSchemaConstructor(schema)) {
|
|
629
643
|
return schema as SchemaS & FacadeSchema
|
|
630
644
|
}
|
|
631
645
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* post-processes the output (e.g. flattens simple allOf).
|
|
4
4
|
*/
|
|
5
5
|
import type * as JsonSchema from "effect/JsonSchema"
|
|
6
|
-
import
|
|
7
|
-
import * as SchemaRepresentation from "effect/SchemaRepresentation"
|
|
6
|
+
import * as Schema from "effect/Schema"
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Converts a schema to a JSON Schema Document (draft-2020-12), with
|
|
@@ -14,8 +13,7 @@ export function specialJsonSchemaDocument(
|
|
|
14
13
|
schema: Schema.Top,
|
|
15
14
|
options?: Schema.ToJsonSchemaOptions
|
|
16
15
|
): JsonSchema.Document<"draft-2020-12"> {
|
|
17
|
-
const
|
|
18
|
-
const jd = SchemaRepresentation.toJsonSchemaDocument(doc, options)
|
|
16
|
+
const jd = Schema.toJsonSchemaDocument(schema, options)
|
|
19
17
|
const processedDefs: JsonSchema.Definitions = {}
|
|
20
18
|
for (const [key, def] of Object.entries(jd.definitions)) {
|
|
21
19
|
processedDefs[key] = postProcessJsonSchema(def)
|
package/src/Schema/ext.ts
CHANGED
|
@@ -36,7 +36,6 @@ import { pipe } from "effect/Function"
|
|
|
36
36
|
import * as Function from "effect/Function"
|
|
37
37
|
import * as Option from "effect/Option"
|
|
38
38
|
import * as S from "effect/Schema"
|
|
39
|
-
import { isDateValid } from "effect/Schema"
|
|
40
39
|
import * as SchemaIssue from "effect/SchemaIssue"
|
|
41
40
|
import * as SchemaTransformation from "effect/SchemaTransformation"
|
|
42
41
|
import * as Struct from "effect/Struct"
|
|
@@ -136,13 +135,13 @@ const DateValidString = S.String.annotate({
|
|
|
136
135
|
format: "date-time"
|
|
137
136
|
})
|
|
138
137
|
|
|
138
|
+
// Schema.Date rejects invalid Dates since beta.91+; no separate isDateValid check needed.
|
|
139
139
|
const DateValidFromString = DateValidString
|
|
140
140
|
.pipe(
|
|
141
141
|
S.decodeTo(S.Date, SchemaTransformation.dateFromString)
|
|
142
142
|
)
|
|
143
|
-
.check(isDateValid())
|
|
144
143
|
|
|
145
|
-
/** Like the default Schema `
|
|
144
|
+
/** Like the default Schema `Date` (valid only) but from String, with default helpers. */
|
|
146
145
|
export const DateValid = extendM(DateValidFromString, (s) => ({
|
|
147
146
|
/**
|
|
148
147
|
* Construction-only default `new Date()`. Applied only when the field is
|
|
@@ -504,9 +503,9 @@ export type WithDefaults<Self extends S.Top> = (
|
|
|
504
503
|
// export type UnionToIntersection3<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I
|
|
505
504
|
// : never
|
|
506
505
|
|
|
507
|
-
/** Union of `
|
|
506
|
+
/** Union of core `Schema.Date` (Date objects) and string-encoded `Date`, with default helpers. */
|
|
508
507
|
export const inputDate = extendM(
|
|
509
|
-
S.Union([S.
|
|
508
|
+
S.Union([S.Date, Date]),
|
|
510
509
|
(s) => ({
|
|
511
510
|
/**
|
|
512
511
|
* Construction-only default `new Date()`. Applied only when the field is
|
|
@@ -597,7 +596,7 @@ export interface DropConstructorDefaultLambda extends Struct.Lambda {
|
|
|
597
596
|
export const dropConstructorDefault: DropConstructorDefaultLambda = Struct.lambda<DropConstructorDefaultLambda>(
|
|
598
597
|
(schema) => {
|
|
599
598
|
const context = schema.ast.context
|
|
600
|
-
if (!context?.
|
|
599
|
+
if (!context?.constructorDefault) return schema
|
|
601
600
|
return schema.rebuild(
|
|
602
601
|
SchemaASTInternal.replaceContext(
|
|
603
602
|
schema.ast,
|
|
@@ -636,8 +635,8 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
|
|
|
636
635
|
encode: (i: any) =>
|
|
637
636
|
Effect.fail(
|
|
638
637
|
new SchemaIssue.Forbidden(
|
|
639
|
-
|
|
640
|
-
|
|
638
|
+
{ message: "One way schema transformation, encoding is not allowed" },
|
|
639
|
+
i
|
|
641
640
|
)
|
|
642
641
|
)
|
|
643
642
|
})
|
|
@@ -661,8 +660,8 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
661
660
|
encode: (i: any) =>
|
|
662
661
|
Effect.fail(
|
|
663
662
|
new SchemaIssue.Forbidden(
|
|
664
|
-
|
|
665
|
-
|
|
663
|
+
{ message: "One way schema transformation, encoding is not allowed" },
|
|
664
|
+
i
|
|
666
665
|
)
|
|
667
666
|
)
|
|
668
667
|
})
|
|
@@ -177,7 +177,7 @@ const StringIdSchemaBase = pipe(
|
|
|
177
177
|
)
|
|
178
178
|
const makeStringId = (s?: string): StringId =>
|
|
179
179
|
s !== undefined ? S.decodeSync(StringIdSchemaBase)(s) : nanoid() as unknown as StringId
|
|
180
|
-
const StringIdArb = (): S.
|
|
180
|
+
const StringIdArb = (): S.Arbitrary<StringId> => (fc) =>
|
|
181
181
|
fc
|
|
182
182
|
.uint8Array({ minLength: length, maxLength: length })
|
|
183
183
|
.map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId)
|
|
@@ -226,7 +226,7 @@ export function prefixedStringId<Type extends StringId>() {
|
|
|
226
226
|
) => {
|
|
227
227
|
type FullPrefix = `${Prefix}${Separator}`
|
|
228
228
|
const pref = `${prefix}${separator ?? "-"}` as FullPrefix
|
|
229
|
-
const arb = (): S.
|
|
229
|
+
const arb = (): S.Arbitrary<Type> => (fc) =>
|
|
230
230
|
StringIdArb()(fc).map(
|
|
231
231
|
(x) => (pref + x.substring(0, 50 - pref.length)) as Type
|
|
232
232
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.test.d.ts","sourceRoot":"","sources":["../rpc.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;AAE7C,qBAAa,iBAAkB,SAAQ,sBAIrC;CAAG
|
|
1
|
+
{"version":3,"file":"rpc.test.d.ts","sourceRoot":"","sources":["../rpc.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;AAE7C,qBAAa,iBAAkB,SAAQ,sBAIrC;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAI0B,KAAK;;;;AAKpC,qBAAa,KAAM,SAAQ,UAQzB;CAAG"}
|
package/test/moreStrings.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ const isNanoId = (value: string) => value.length === 21 && Array.from(value).eve
|
|
|
9
9
|
|
|
10
10
|
test("StringId arbitrary generates nanoid-shaped values", () => {
|
|
11
11
|
fc.assert(
|
|
12
|
-
fc.property(S.toArbitrary(S.StringId), (value) => {
|
|
12
|
+
fc.property(S.toArbitrary(S.StringId)(fc), (value) => {
|
|
13
13
|
expect(isNanoId(value)).toBe(true)
|
|
14
14
|
expect(S.is(S.StringId)(value)).toBe(true)
|
|
15
15
|
})
|
package/test/rpc.test.ts
CHANGED
|
@@ -58,5 +58,5 @@ test("Void decodes and encodes as void", () => {
|
|
|
58
58
|
expectTypeOf<typeof _statsRequestType>().toEqualTypeOf<"query">()
|
|
59
59
|
expectTypeOf(statsFromMake).toEqualTypeOf<Stats>()
|
|
60
60
|
expectTypeOf(statsFromMakeOption).toEqualTypeOf<Option.Option<Stats>>()
|
|
61
|
-
expectTypeOf(statsFromMakeEffect).toEqualTypeOf<Effect.Effect<Stats, S.
|
|
61
|
+
expectTypeOf(statsFromMakeEffect).toEqualTypeOf<Effect.Effect<Stats, S.SchemaIssue.Issue>>()
|
|
62
62
|
})
|
package/test/special.test.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { deduplicateOpenApiSchemas } from "effect-app/Schema/SpecialOpenApi"
|
|
|
7
7
|
import * as Effect from "effect/Effect"
|
|
8
8
|
import * as Option from "effect/Option"
|
|
9
9
|
import * as Predicate from "effect/Predicate"
|
|
10
|
-
import * as Schema from "effect/Schema"
|
|
11
10
|
import * as S from "effect/Schema"
|
|
12
11
|
import * as SchemaGetter from "effect/SchemaGetter"
|
|
13
12
|
import { describe, expect, it } from "vitest"
|
|
@@ -492,8 +491,8 @@ describe("OpaqueFacade", () => {
|
|
|
492
491
|
never,
|
|
493
492
|
never
|
|
494
493
|
>()(
|
|
495
|
-
//
|
|
496
|
-
AppSchema.Struct({ value: S.String })
|
|
494
|
+
// Runtime guard: only class schemas are accepted (struct schemas throw).
|
|
495
|
+
AppSchema.Struct({ value: S.String }) as never
|
|
497
496
|
)
|
|
498
497
|
|
|
499
498
|
expect(makeStructErrorFacade).toThrow("OpaqueErrorFacadeClass requires a class schema")
|
|
@@ -642,36 +641,36 @@ describe("TaggedError", () => {
|
|
|
642
641
|
|
|
643
642
|
describe("SpecialJsonSchema", () => {
|
|
644
643
|
it("nullable to optional — from NullOr", () => {
|
|
645
|
-
const nullableDecodedUndefinedEncoded = (schema:
|
|
644
|
+
const nullableDecodedUndefinedEncoded = (schema: S.Top) => {
|
|
646
645
|
const isNullableSchema = "members" in schema
|
|
647
646
|
&& globalThis.Array.isArray((schema as any).members)
|
|
648
647
|
&& (schema as any).members.length === 2
|
|
649
648
|
&& (schema as any).members.some((member: any) => member.ast._tag === "Null")
|
|
650
649
|
|
|
651
|
-
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<
|
|
650
|
+
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<S.Top> : undefined
|
|
652
651
|
const innerSchema = nullableMembers
|
|
653
652
|
? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
|
|
654
653
|
: schema
|
|
655
654
|
|
|
656
|
-
const nullableSchema = isNullableSchema ? schema :
|
|
655
|
+
const nullableSchema = isNullableSchema ? schema : S.NullOr(schema)
|
|
657
656
|
|
|
658
657
|
return nullableSchema.pipe(
|
|
659
|
-
|
|
658
|
+
S.encodeTo(S.optionalKey(innerSchema), {
|
|
660
659
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
661
660
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
662
661
|
})
|
|
663
662
|
)
|
|
664
663
|
}
|
|
665
664
|
|
|
666
|
-
const fromNullOr = nullableDecodedUndefinedEncoded(
|
|
667
|
-
const structFromNullOr =
|
|
665
|
+
const fromNullOr = nullableDecodedUndefinedEncoded(S.NullOr(S.String))
|
|
666
|
+
const structFromNullOr = S.Struct({ status: fromNullOr })
|
|
668
667
|
|
|
669
|
-
const encode =
|
|
668
|
+
const encode = S.encodeUnknownSync(structFromNullOr as any)
|
|
670
669
|
const encodedNull = encode({ status: null })
|
|
671
670
|
expect("status" in encodedNull).toBe(false)
|
|
672
671
|
expect(encode({ status: "test" })).toStrictEqual({ status: "test" })
|
|
673
672
|
|
|
674
|
-
const decode =
|
|
673
|
+
const decode = S.decodeUnknownSync(structFromNullOr as any)
|
|
675
674
|
expect(decode({})).toStrictEqual({ status: null })
|
|
676
675
|
expect(decode({ status: "test" })).toStrictEqual({ status: "test" })
|
|
677
676
|
|
|
@@ -689,24 +688,24 @@ describe("SpecialJsonSchema", () => {
|
|
|
689
688
|
})
|
|
690
689
|
|
|
691
690
|
it("identifies X universally — deduplicates same-fingerprint references", () => {
|
|
692
|
-
const X =
|
|
691
|
+
const X = S.String.annotate({ title: "X", identifier: "X" })
|
|
693
692
|
|
|
694
|
-
const s =
|
|
695
|
-
a:
|
|
696
|
-
|
|
693
|
+
const s = S.Struct({
|
|
694
|
+
a: S.NullOr(X).pipe(
|
|
695
|
+
S.encodeTo(S.optionalKey(X), {
|
|
697
696
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
698
697
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
699
698
|
})
|
|
700
699
|
),
|
|
701
|
-
b:
|
|
702
|
-
|
|
700
|
+
b: S.NullOr(X).pipe(
|
|
701
|
+
S.encodeTo(S.optionalKey(X), {
|
|
703
702
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
704
703
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
705
704
|
})
|
|
706
705
|
),
|
|
707
|
-
c:
|
|
706
|
+
c: S.NullOr(X),
|
|
708
707
|
d: X,
|
|
709
|
-
e: X.pipe(
|
|
708
|
+
e: X.pipe(S.optionalKey)
|
|
710
709
|
})
|
|
711
710
|
|
|
712
711
|
const doc = specialJsonSchemaDocument(s)
|
|
@@ -738,16 +737,16 @@ describe("SpecialJsonSchema", () => {
|
|
|
738
737
|
})
|
|
739
738
|
|
|
740
739
|
it("shared annotated schema via helper — deduplicates", () => {
|
|
741
|
-
const X =
|
|
740
|
+
const X = S.String.annotate({ title: "X", identifier: "X" })
|
|
742
741
|
|
|
743
742
|
const cache = new WeakMap()
|
|
744
|
-
const nullableDecodedUndefinedEncoded = (schema:
|
|
743
|
+
const nullableDecodedUndefinedEncoded = (schema: S.Top) => {
|
|
745
744
|
const isNullableSchema = "members" in schema
|
|
746
745
|
&& globalThis.Array.isArray((schema as any).members)
|
|
747
746
|
&& (schema as any).members.length === 2
|
|
748
747
|
&& (schema as any).members.some((member: any) => member.ast._tag === "Null")
|
|
749
748
|
|
|
750
|
-
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<
|
|
749
|
+
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<S.Top> : undefined
|
|
751
750
|
const innerSchema = nullableMembers
|
|
752
751
|
? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
|
|
753
752
|
: schema
|
|
@@ -755,9 +754,9 @@ describe("SpecialJsonSchema", () => {
|
|
|
755
754
|
const cached = cache.get(innerSchema.ast)
|
|
756
755
|
if (cached !== undefined) return cached
|
|
757
756
|
|
|
758
|
-
const nullableSchema = isNullableSchema ? schema :
|
|
757
|
+
const nullableSchema = isNullableSchema ? schema : S.NullOr(schema)
|
|
759
758
|
const out = nullableSchema.pipe(
|
|
760
|
-
|
|
759
|
+
S.encodeTo(S.optionalKey(innerSchema), {
|
|
761
760
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
762
761
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
763
762
|
})
|
|
@@ -767,12 +766,12 @@ describe("SpecialJsonSchema", () => {
|
|
|
767
766
|
return out
|
|
768
767
|
}
|
|
769
768
|
|
|
770
|
-
const structWithShared =
|
|
769
|
+
const structWithShared = S.Struct({
|
|
771
770
|
a: nullableDecodedUndefinedEncoded(X),
|
|
772
|
-
b: nullableDecodedUndefinedEncoded(
|
|
773
|
-
c:
|
|
771
|
+
b: nullableDecodedUndefinedEncoded(S.NullOr(X)),
|
|
772
|
+
c: S.NullOr(X),
|
|
774
773
|
d: X,
|
|
775
|
-
e: X.pipe(
|
|
774
|
+
e: X.pipe(S.optionalKey)
|
|
776
775
|
})
|
|
777
776
|
|
|
778
777
|
const doc = specialJsonSchemaDocument(structWithShared)
|