effect-app 4.0.0-beta.5 → 4.0.0-beta.52

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/dist/Config.d.ts +7 -0
  3. package/dist/Config.d.ts.map +1 -0
  4. package/dist/Config.js +6 -0
  5. package/dist/ConfigProvider.d.ts +39 -0
  6. package/dist/ConfigProvider.d.ts.map +1 -0
  7. package/dist/ConfigProvider.js +42 -0
  8. package/dist/Effect.d.ts.map +1 -1
  9. package/dist/Effect.js +3 -2
  10. package/dist/Operations.d.ts +51 -15
  11. package/dist/Operations.d.ts.map +1 -1
  12. package/dist/Pure.d.ts.map +1 -1
  13. package/dist/Pure.js +11 -11
  14. package/dist/Schema/Class.d.ts +39 -1
  15. package/dist/Schema/Class.d.ts.map +1 -1
  16. package/dist/Schema/Class.js +89 -12
  17. package/dist/Schema/SpecialJsonSchema.d.ts +40 -0
  18. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  19. package/dist/Schema/SpecialJsonSchema.js +199 -0
  20. package/dist/Schema/SpecialOpenApi.d.ts +30 -0
  21. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  22. package/dist/Schema/SpecialOpenApi.js +120 -0
  23. package/dist/Schema/brand.d.ts +8 -5
  24. package/dist/Schema/brand.d.ts.map +1 -1
  25. package/dist/Schema/brand.js +1 -1
  26. package/dist/Schema/email.d.ts.map +1 -1
  27. package/dist/Schema/email.js +4 -3
  28. package/dist/Schema/ext.d.ts +177 -44
  29. package/dist/Schema/ext.d.ts.map +1 -1
  30. package/dist/Schema/ext.js +144 -35
  31. package/dist/Schema/moreStrings.d.ts.map +1 -1
  32. package/dist/Schema/moreStrings.js +6 -4
  33. package/dist/Schema/numbers.d.ts +8 -8
  34. package/dist/Schema/numbers.js +2 -2
  35. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  36. package/dist/Schema/phoneNumber.js +3 -2
  37. package/dist/Schema.d.ts +21 -54
  38. package/dist/Schema.d.ts.map +1 -1
  39. package/dist/Schema.js +43 -64
  40. package/dist/ServiceMap.d.ts +3 -3
  41. package/dist/ServiceMap.d.ts.map +1 -1
  42. package/dist/ServiceMap.js +1 -1
  43. package/dist/client/apiClientFactory.d.ts +1 -1
  44. package/dist/client/apiClientFactory.d.ts.map +1 -1
  45. package/dist/client/apiClientFactory.js +8 -9
  46. package/dist/client/errors.d.ts +8 -0
  47. package/dist/client/errors.d.ts.map +1 -1
  48. package/dist/client/errors.js +34 -10
  49. package/dist/client/makeClient.d.ts +13 -12
  50. package/dist/client/makeClient.d.ts.map +1 -1
  51. package/dist/client/makeClient.js +7 -15
  52. package/dist/http/Request.d.ts.map +1 -1
  53. package/dist/http/Request.js +5 -5
  54. package/dist/ids.d.ts +1 -1
  55. package/dist/ids.d.ts.map +1 -1
  56. package/dist/ids.js +1 -1
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +3 -2
  60. package/dist/utils.d.ts +18 -0
  61. package/dist/utils.d.ts.map +1 -1
  62. package/dist/utils.js +24 -5
  63. package/package.json +24 -12
  64. package/src/Config.ts +14 -0
  65. package/src/ConfigProvider.ts +48 -0
  66. package/src/Effect.ts +3 -2
  67. package/src/Pure.ts +12 -13
  68. package/src/Schema/Class.ts +114 -16
  69. package/src/Schema/SpecialJsonSchema.ts +216 -0
  70. package/src/Schema/SpecialOpenApi.ts +126 -0
  71. package/src/Schema/brand.ts +13 -7
  72. package/src/Schema/email.ts +4 -2
  73. package/src/Schema/ext.ts +213 -56
  74. package/src/Schema/moreStrings.ts +10 -6
  75. package/src/Schema/numbers.ts +2 -2
  76. package/src/Schema/phoneNumber.ts +3 -1
  77. package/src/Schema.ts +79 -103
  78. package/src/ServiceMap.ts +7 -6
  79. package/src/client/apiClientFactory.ts +12 -15
  80. package/src/client/errors.ts +45 -12
  81. package/src/client/makeClient.ts +33 -26
  82. package/src/http/Request.ts +7 -4
  83. package/src/ids.ts +1 -1
  84. package/src/index.ts +2 -1
  85. package/src/utils.ts +26 -4
  86. package/test/dist/moreStrings.test.d.ts.map +1 -0
  87. package/test/dist/rpc.test.d.ts.map +1 -1
  88. package/test/dist/special.test.d.ts.map +1 -0
  89. package/test/moreStrings.test.ts +17 -0
  90. package/test/rpc.test.ts +26 -5
  91. package/test/schema.test.ts +292 -1
  92. package/test/special.test.ts +525 -0
  93. package/test/utils.test.ts +1 -1
  94. package/tsconfig.base.json +0 -1
  95. package/tsconfig.json +0 -1
  96. package/dist/Struct.d.ts +0 -44
  97. package/dist/Struct.d.ts.map +0 -1
  98. package/dist/Struct.js +0 -29
  99. package/src/Struct.ts +0 -54
@@ -0,0 +1,525 @@
1
+ import { Option, Predicate, Schema, SchemaGetter } from "effect"
2
+ import { InvalidStateError, LoginError, NotFoundError, NotLoggedInError, OptimisticConcurrencyException, ServiceUnavailableError, UnauthorizedError, ValidationError } from "effect-app/client/errors"
3
+ import { Class, TaggedClass } from "effect-app/Schema/Class"
4
+ import { specialJsonSchemaDocument } from "effect-app/Schema/SpecialJsonSchema"
5
+ import { deduplicateOpenApiSchemas } from "effect-app/Schema/SpecialOpenApi"
6
+ import * as S from "effect/Schema"
7
+ import { describe, expect, it } from "vitest"
8
+
9
+ describe("Class", () => {
10
+ it("encoding accepts plain objects matching the struct (Fields argument)", () => {
11
+ class A extends Class<A>("A")({ a: S.String }) {}
12
+
13
+ // Encoding a class instance still works
14
+ expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
15
+
16
+ // Encoding a plain object matching the struct now succeeds
17
+ expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
18
+
19
+ // Encoding null still fails
20
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
21
+ })
22
+
23
+ it("encoding accepts plain objects matching the struct (Struct argument)", () => {
24
+ class A extends Class<A>("A")(S.Struct({ a: S.String })) {}
25
+
26
+ expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
27
+ expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
28
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
29
+ })
30
+
31
+ it("decoding still works normally", () => {
32
+ class A extends Class<A>("A")({ a: S.String }) {}
33
+
34
+ const decoded = S.decodeUnknownSync(A)({ a: "hello" })
35
+ expect(decoded).toBeInstanceOf(A)
36
+ expect((decoded as A).a).toBe("hello")
37
+
38
+ expect(() => S.decodeUnknownSync(A)(null)).toThrow()
39
+ expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
40
+ })
41
+
42
+ it("rejects values that don't match the struct", () => {
43
+ class A extends Class<A>("A")({ a: S.String }) {}
44
+
45
+ expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
46
+ expect(() => S.encodeUnknownSync(A)("not an object")).toThrow()
47
+ })
48
+
49
+ it("returns a class constructor — new and instanceof work", () => {
50
+ class A extends Class<A>("A")({ a: S.String }) {}
51
+
52
+ const instance = new A({ a: "hello" })
53
+ expect(instance).toBeInstanceOf(A)
54
+ expect(instance.a).toBe("hello")
55
+ })
56
+
57
+ it("preserves fields and identifier", () => {
58
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
59
+
60
+ expect(A.identifier).toBe("A")
61
+ expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
62
+ })
63
+ })
64
+
65
+ describe("Class constructor", () => {
66
+ it("works as a base class — new, instanceof, encoding plain objects", () => {
67
+ class A extends Class<A>("A")({ a: S.String }) {}
68
+
69
+ // Construction
70
+ const instance = new A({ a: "hello" })
71
+ expect(instance).toBeInstanceOf(A)
72
+ expect(instance.a).toBe("hello")
73
+
74
+ // Encoding a class instance
75
+ expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
76
+
77
+ // Encoding a plain object
78
+ expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
79
+
80
+ // Encoding invalid input fails
81
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
82
+ expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
83
+ })
84
+
85
+ it("decoding works normally", () => {
86
+ class A extends Class<A>("A")({ a: S.String }) {}
87
+
88
+ const decoded = S.decodeUnknownSync(A)({ a: "hello" })
89
+ expect(decoded).toBeInstanceOf(A)
90
+ expect((decoded as A).a).toBe("hello")
91
+
92
+ expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
93
+ })
94
+
95
+ it("exposes fields, identifier, pick, omit", () => {
96
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
97
+
98
+ expect(A.identifier).toBe("A")
99
+ expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
100
+ expect(A.pick("a")).toStrictEqual({ a: A.fields.a })
101
+ expect(A.omit("b")).toStrictEqual({ a: A.fields.a })
102
+ })
103
+ })
104
+
105
+ describe("TaggedClass constructor", () => {
106
+ it("works as a base class with _tag — new, instanceof, encoding plain objects", () => {
107
+ class Circle extends TaggedClass<Circle>()("Circle", { radius: S.Number }) {}
108
+
109
+ // Construction
110
+ const instance = new Circle({ radius: 5 })
111
+ expect(instance).toBeInstanceOf(Circle)
112
+ expect(instance._tag).toBe("Circle")
113
+ expect(instance.radius).toBe(5)
114
+
115
+ // Encoding a class instance
116
+ expect(S.encodeUnknownSync(Circle)(instance)).toStrictEqual({ _tag: "Circle", radius: 5 })
117
+
118
+ // Encoding a plain object
119
+ expect(S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: 10 })).toStrictEqual({ _tag: "Circle", radius: 10 })
120
+
121
+ // Encoding invalid input fails
122
+ expect(() => S.encodeUnknownSync(Circle)(null)).toThrow()
123
+ expect(() => S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: "nope" })).toThrow()
124
+ })
125
+
126
+ it("decoding works normally", () => {
127
+ class Circle extends TaggedClass<Circle>()("Circle", { radius: S.Number }) {}
128
+
129
+ const decoded = S.decodeUnknownSync(Circle)({ _tag: "Circle", radius: 5 })
130
+ expect(decoded).toBeInstanceOf(Circle)
131
+ expect((decoded as Circle).radius).toBe(5)
132
+ expect((decoded as Circle)._tag).toBe("Circle")
133
+ })
134
+
135
+ it("exposes fields, identifier, pick, omit", () => {
136
+ class Circle extends TaggedClass<Circle>()("Circle", { radius: S.Number }) {}
137
+
138
+ expect(Circle.identifier).toBe("Circle")
139
+ expect(Object.keys(Circle.fields)).toContain("_tag")
140
+ expect(Object.keys(Circle.fields)).toContain("radius")
141
+ expect(Circle.pick("radius")).toStrictEqual({ radius: Circle.fields.radius })
142
+ })
143
+ })
144
+
145
+ describe("TaggedError", () => {
146
+ it("InvalidStateError toString includes the message", () => {
147
+ const error = new InvalidStateError("something went wrong")
148
+ expect(error.toString()).toContain("something went wrong")
149
+ })
150
+
151
+ it("NotFoundError toString includes the message", () => {
152
+ const error = new NotFoundError({ type: "User", id: "123" })
153
+ expect(error.toString()).toContain("Didn't find User")
154
+ expect(error.toString()).toContain("123")
155
+ })
156
+
157
+ it("ServiceUnavailableError toString includes the message", () => {
158
+ const error = new ServiceUnavailableError("service down")
159
+ expect(error.toString()).toContain("service down")
160
+ })
161
+
162
+ it("ValidationError toString includes the message", () => {
163
+ const error = new ValidationError({ errors: ["field required"] })
164
+ expect(error.toString()).toContain("Validation failed")
165
+ expect(error.toString()).toContain("field required")
166
+ })
167
+
168
+ it("NotLoggedInError toString includes the message", () => {
169
+ const error = new NotLoggedInError("not logged in")
170
+ expect(error.toString()).toContain("not logged in")
171
+ })
172
+
173
+ it("LoginError toString includes the message", () => {
174
+ const error = new LoginError("login failed")
175
+ expect(error.toString()).toContain("login failed")
176
+ })
177
+
178
+ it("UnauthorizedError toString includes the message", () => {
179
+ const error = new UnauthorizedError("forbidden")
180
+ expect(error.toString()).toContain("forbidden")
181
+ })
182
+
183
+ it("OptimisticConcurrencyException toString includes the message", () => {
184
+ const error = new OptimisticConcurrencyException({ message: "conflict" })
185
+ expect(error.toString()).toContain("conflict")
186
+ })
187
+
188
+ it("OptimisticConcurrencyException from details toString includes the message", () => {
189
+ const error = new OptimisticConcurrencyException({ type: "User", id: "123", code: 409 })
190
+ expect(error.toString()).toContain("Existing User 123 record changed")
191
+ })
192
+ })
193
+
194
+ describe("SpecialJsonSchema", () => {
195
+ it("nullable to optional — from NullOr", () => {
196
+ const nullableDecodedUndefinedEncoded = (schema: Schema.Top) => {
197
+ const isNullableSchema = "members" in schema
198
+ && globalThis.Array.isArray((schema as any).members)
199
+ && (schema as any).members.length === 2
200
+ && (schema as any).members.some((member: any) => member.ast._tag === "Null")
201
+
202
+ const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<Schema.Top> : undefined
203
+ const innerSchema = nullableMembers
204
+ ? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
205
+ : schema
206
+
207
+ const nullableSchema = isNullableSchema ? schema : Schema.NullOr(schema)
208
+
209
+ return nullableSchema.pipe(
210
+ Schema.encodeTo(Schema.optionalKey(innerSchema), {
211
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
212
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
213
+ })
214
+ )
215
+ }
216
+
217
+ const fromNullOr = nullableDecodedUndefinedEncoded(Schema.NullOr(Schema.String))
218
+ const structFromNullOr = Schema.Struct({ status: fromNullOr })
219
+
220
+ const encode = Schema.encodeUnknownSync(structFromNullOr as any)
221
+ const encodedNull = encode({ status: null }) as any
222
+ expect("status" in encodedNull).toBe(false)
223
+ expect(encode({ status: "test" })).toStrictEqual({ status: "test" })
224
+
225
+ const decode = Schema.decodeUnknownSync(structFromNullOr as any)
226
+ expect(decode({})).toStrictEqual({ status: null })
227
+ expect(decode({ status: "test" })).toStrictEqual({ status: "test" })
228
+
229
+ const doc = specialJsonSchemaDocument(structFromNullOr)
230
+ expect(doc).toStrictEqual({
231
+ dialect: "draft-2020-12",
232
+ schema: {
233
+ "type": "object",
234
+ "properties": {
235
+ "status": { "type": "string" }
236
+ },
237
+ "additionalProperties": false
238
+ },
239
+ definitions: {}
240
+ })
241
+ })
242
+
243
+ it("identifies X universally — deduplicates same-fingerprint references", () => {
244
+ const X = Schema.String.annotate({ title: "X", identifier: "X" })
245
+
246
+ const s = Schema.Struct({
247
+ a: Schema.NullOr(X).pipe(
248
+ Schema.encodeTo(Schema.optionalKey(X), {
249
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
250
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
251
+ })
252
+ ),
253
+ b: Schema.NullOr(X).pipe(
254
+ Schema.encodeTo(Schema.optionalKey(X), {
255
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
256
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
257
+ })
258
+ ),
259
+ c: Schema.NullOr(X),
260
+ d: X,
261
+ e: X.pipe(Schema.optionalKey)
262
+ })
263
+
264
+ const doc = specialJsonSchemaDocument(s)
265
+ expect(doc).toStrictEqual({
266
+ dialect: "draft-2020-12",
267
+ schema: {
268
+ "type": "object",
269
+ "properties": {
270
+ "a": { "$ref": "#/$defs/X" },
271
+ "b": { "$ref": "#/$defs/X" },
272
+ "c": {
273
+ "anyOf": [
274
+ { "$ref": "#/$defs/X" },
275
+ { "type": "null" }
276
+ ]
277
+ },
278
+ "d": { "$ref": "#/$defs/X" },
279
+ "e": { "$ref": "#/$defs/X" }
280
+ },
281
+ "required": ["c", "d"],
282
+ "additionalProperties": false
283
+ },
284
+ definitions: {
285
+ X: {
286
+ "type": "string",
287
+ "title": "X"
288
+ }
289
+ }
290
+ })
291
+ })
292
+
293
+ it("shared annotated schema via helper — deduplicates", () => {
294
+ const X = Schema.String.annotate({ title: "X", identifier: "X" })
295
+
296
+ const cache = new WeakMap()
297
+ const nullableDecodedUndefinedEncoded = (schema: Schema.Top) => {
298
+ const isNullableSchema = "members" in schema
299
+ && globalThis.Array.isArray((schema as any).members)
300
+ && (schema as any).members.length === 2
301
+ && (schema as any).members.some((member: any) => member.ast._tag === "Null")
302
+
303
+ const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<Schema.Top> : undefined
304
+ const innerSchema = nullableMembers
305
+ ? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
306
+ : schema
307
+
308
+ const cached = cache.get(innerSchema.ast)
309
+ if (cached !== undefined) return cached
310
+
311
+ const nullableSchema = isNullableSchema ? schema : Schema.NullOr(schema)
312
+ const out = nullableSchema.pipe(
313
+ Schema.encodeTo(Schema.optionalKey(innerSchema), {
314
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
315
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
316
+ })
317
+ )
318
+
319
+ cache.set(innerSchema.ast, out)
320
+ return out
321
+ }
322
+
323
+ const structWithShared = Schema.Struct({
324
+ a: nullableDecodedUndefinedEncoded(X),
325
+ b: nullableDecodedUndefinedEncoded(Schema.NullOr(X)),
326
+ c: Schema.NullOr(X),
327
+ d: X,
328
+ e: X.pipe(Schema.optionalKey)
329
+ })
330
+
331
+ const doc = specialJsonSchemaDocument(structWithShared)
332
+ expect(doc).toStrictEqual({
333
+ dialect: "draft-2020-12",
334
+ schema: {
335
+ "type": "object",
336
+ "properties": {
337
+ "a": { "$ref": "#/$defs/X" },
338
+ "b": { "$ref": "#/$defs/X" },
339
+ "c": {
340
+ "anyOf": [
341
+ { "$ref": "#/$defs/X" },
342
+ { "type": "null" }
343
+ ]
344
+ },
345
+ "d": { "$ref": "#/$defs/X" },
346
+ "e": { "$ref": "#/$defs/X" }
347
+ },
348
+ "required": ["c", "d"],
349
+ "additionalProperties": false
350
+ },
351
+ definitions: {
352
+ X: {
353
+ "type": "string",
354
+ "title": "X"
355
+ }
356
+ }
357
+ })
358
+ })
359
+ })
360
+
361
+ describe("SpecialOpenApi", () => {
362
+ it("deduplicates identical components.schemas entries with same base identifier", () => {
363
+ const spec = {
364
+ openapi: "3.1.0",
365
+ info: { title: "Test", version: "1.0" },
366
+ paths: {
367
+ "/foo": {
368
+ get: {
369
+ responses: {
370
+ 200: {
371
+ content: {
372
+ "application/json": {
373
+ schema: { $ref: "#/components/schemas/X" }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+ },
380
+ "/bar": {
381
+ get: {
382
+ responses: {
383
+ 200: {
384
+ content: {
385
+ "application/json": {
386
+ schema: { $ref: "#/components/schemas/X1" }
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ },
394
+ components: {
395
+ schemas: {
396
+ X: { type: "string", title: "X" },
397
+ X1: { type: "string", title: "X" }
398
+ }
399
+ }
400
+ }
401
+
402
+ const result = deduplicateOpenApiSchemas(spec) as any
403
+
404
+ // X1 should be removed, and $ref to X1 rewritten to X
405
+ expect(result.components.schemas).toStrictEqual({
406
+ X: { type: "string", title: "X" }
407
+ })
408
+ expect(
409
+ result.paths["/bar"].get.responses[200].content["application/json"].schema
410
+ )
411
+ .toStrictEqual({ $ref: "#/components/schemas/X" })
412
+ })
413
+
414
+ it("does not deduplicate entries with different representations", () => {
415
+ const spec = {
416
+ openapi: "3.1.0",
417
+ info: { title: "Test", version: "1.0" },
418
+ paths: {},
419
+ components: {
420
+ schemas: {
421
+ X: { type: "string", title: "X" },
422
+ X1: { type: "number", title: "X" }
423
+ }
424
+ }
425
+ }
426
+
427
+ const result = deduplicateOpenApiSchemas(spec) as any
428
+
429
+ // Both should remain since they have different representations
430
+ expect(result.components.schemas).toStrictEqual({
431
+ X: { type: "string", title: "X" },
432
+ X1: { type: "number", title: "X" }
433
+ })
434
+ })
435
+
436
+ it("returns spec unchanged when no duplicates exist", () => {
437
+ const spec = {
438
+ openapi: "3.1.0",
439
+ info: { title: "Test", version: "1.0" },
440
+ paths: {},
441
+ components: {
442
+ schemas: {
443
+ Foo: { type: "string" },
444
+ Bar: { type: "number" }
445
+ }
446
+ }
447
+ }
448
+
449
+ const result = deduplicateOpenApiSchemas(spec)
450
+ expect(result).toBe(spec) // same reference, no cloning needed
451
+ })
452
+
453
+ it("rewrites nested $ref pointers in allOf/anyOf/oneOf", () => {
454
+ const spec = {
455
+ openapi: "3.1.0",
456
+ info: { title: "Test", version: "1.0" },
457
+ paths: {
458
+ "/baz": {
459
+ post: {
460
+ requestBody: {
461
+ content: {
462
+ "application/json": {
463
+ schema: {
464
+ anyOf: [
465
+ { $ref: "#/components/schemas/Y1" },
466
+ { type: "null" }
467
+ ]
468
+ }
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ },
475
+ components: {
476
+ schemas: {
477
+ Y: { type: "object", properties: { name: { type: "string" } } },
478
+ Y1: { type: "object", properties: { name: { type: "string" } } }
479
+ }
480
+ }
481
+ }
482
+
483
+ const result = deduplicateOpenApiSchemas(spec) as any
484
+
485
+ expect(result.components.schemas).toStrictEqual({
486
+ Y: { type: "object", properties: { name: { type: "string" } } }
487
+ })
488
+ expect(
489
+ result.paths["/baz"].post.requestBody.content["application/json"].schema.anyOf[0]
490
+ )
491
+ .toStrictEqual({ $ref: "#/components/schemas/Y" })
492
+ })
493
+
494
+ it("rewrites $ref pointers inside definitions themselves", () => {
495
+ const spec = {
496
+ openapi: "3.1.0",
497
+ info: { title: "Test", version: "1.0" },
498
+ paths: {},
499
+ components: {
500
+ schemas: {
501
+ Inner: { type: "string" },
502
+ Inner1: { type: "string" },
503
+ Outer: {
504
+ type: "object",
505
+ properties: {
506
+ field: { $ref: "#/components/schemas/Inner1" }
507
+ }
508
+ }
509
+ }
510
+ }
511
+ }
512
+
513
+ const result = deduplicateOpenApiSchemas(spec) as any
514
+
515
+ expect(Object.keys(result.components.schemas)).toStrictEqual(["Inner", "Outer"])
516
+ expect(result.components.schemas.Outer.properties.field).toStrictEqual({
517
+ $ref: "#/components/schemas/Inner"
518
+ })
519
+ })
520
+
521
+ it("handles spec without components gracefully", () => {
522
+ const spec = { openapi: "3.1.0", info: { title: "Test", version: "1.0" }, paths: {} }
523
+ expect(deduplicateOpenApiSchemas(spec)).toBe(spec)
524
+ })
525
+ })
@@ -111,7 +111,7 @@ test("works with schema class", () => {
111
111
  name: S.String,
112
112
  state: S.Union([
113
113
  S.Struct({ a: S.String, _tag: S.Literal("a") }),
114
- S.Struct({ b: S.Number, _tag: S.Literal("b") })
114
+ S.Struct({ b: S.Finite, _tag: S.Literal("b") })
115
115
  ])
116
116
  }) {}
117
117
 
@@ -21,7 +21,6 @@
21
21
  "noImplicitThis": true,
22
22
  "resolveJsonModule": true,
23
23
  "moduleResolution": "Node16",
24
- "downlevelIteration": true,
25
24
  "noErrorTruncation": true,
26
25
  "forceConsistentCasingInFileNames": true
27
26
  },
package/tsconfig.json CHANGED
@@ -32,7 +32,6 @@
32
32
  "outDir": "build/dist",
33
33
  "resolveJsonModule": true,
34
34
  "moduleResolution": "Node16",
35
- "downlevelIteration": true,
36
35
  "noErrorTruncation": true,
37
36
  "forceConsistentCasingInFileNames": true,
38
37
  "types": [
package/dist/Struct.d.ts DELETED
@@ -1,44 +0,0 @@
1
- import type { Types } from "effect";
2
- export * from "effect/Struct";
3
- /**
4
- * Create a new object by picking properties of an existing object.
5
- *
6
- * @example
7
- * import { pick } from "effect/Struct"
8
- * import { pipe } from "effect/Function"
9
- *
10
- * assert.deepStrictEqual(pipe({ a: "a", b: 1, c: true }, pick("a", "b")), { a: "a", b: 1 })
11
- * assert.deepStrictEqual(pick({ a: "a", b: 1, c: true }, "a", "b"), { a: "a", b: 1 })
12
- *
13
- * @since 2.0.0
14
- */
15
- export declare const pick: {
16
- <Keys extends Array<PropertyKey>>(keys: Keys): <S extends {
17
- [K in Keys[number]]?: any;
18
- }>(s: S) => Types.MatchRecord<S, {
19
- [K in Keys[number]]?: S[K];
20
- }, Pick<S, Keys[number]>>;
21
- <S extends object, Keys extends Array<keyof S>>(s: S, keys: Keys): Types.MatchRecord<S, {
22
- [K in Keys[number]]?: S[K];
23
- }, Pick<S, Keys[number]>>;
24
- };
25
- export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
26
- /**
27
- * Create a new object by omitting properties of an existing object.
28
- *
29
- * @example
30
- * import { omit } from "effect/Struct"
31
- * import { pipe } from "effect/Function"
32
- *
33
- * assert.deepStrictEqual(pipe({ a: "a", b: 1, c: true }, omit("c")), { a: "a", b: 1 })
34
- * assert.deepStrictEqual(omit({ a: "a", b: 1, c: true }, "c"), { a: "a", b: 1 })
35
- *
36
- * @since 2.0.0
37
- */
38
- export declare const omit: {
39
- <Keys extends Array<PropertyKey>>(keys: Keys): <S extends {
40
- [K in Keys[number]]?: any;
41
- }>(s: S) => DistributiveOmit<S, Keys[number]>;
42
- <S extends object, Keys extends Array<keyof S>>(s: S, keys: Keys): DistributiveOmit<S, Keys[number]>;
43
- };
44
- //# sourceMappingURL=Struct.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Struct.d.ts","sourceRoot":"","sources":["../src/Struct.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAGnC,cAAc,eAAe,CAAA;AAE7B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE;IACjB,CAAC,IAAI,SAAS,KAAK,CAAC,WAAW,CAAC,EAC9B,IAAI,EAAE,IAAI,GACT,CAAC,CAAC,SAAS;SAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG;KAAE,EACzC,CAAC,EAAE,CAAC,KACD,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE;SAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KAAE,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAChF,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,EAC5C,CAAC,EAAE,CAAC,EACJ,IAAI,EAAE,IAAI,GACT,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE;SAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KAAE,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;CACjE,CAAA;AAEf,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7E,KAAK,CAAA;AAET;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE;IACjB,CAAC,IAAI,SAAS,KAAK,CAAC,WAAW,CAAC,EAC9B,IAAI,EAAE,IAAI,GACT,CAAC,CAAC,SAAS;SAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG;KAAE,EAAE,CAAC,EAAE,CAAC,KAAK,gBAAgB,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IACvF,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,EAC5C,CAAC,EAAE,CAAC,EACJ,IAAI,EAAE,IAAI,GACT,gBAAgB,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;CAChB,CAAA"}
package/dist/Struct.js DELETED
@@ -1,29 +0,0 @@
1
- import * as Struct from "effect/Struct";
2
- export * from "effect/Struct";
3
- /**
4
- * Create a new object by picking properties of an existing object.
5
- *
6
- * @example
7
- * import { pick } from "effect/Struct"
8
- * import { pipe } from "effect/Function"
9
- *
10
- * assert.deepStrictEqual(pipe({ a: "a", b: 1, c: true }, pick("a", "b")), { a: "a", b: 1 })
11
- * assert.deepStrictEqual(pick({ a: "a", b: 1, c: true }, "a", "b"), { a: "a", b: 1 })
12
- *
13
- * @since 2.0.0
14
- */
15
- export const pick = Struct.pick;
16
- /**
17
- * Create a new object by omitting properties of an existing object.
18
- *
19
- * @example
20
- * import { omit } from "effect/Struct"
21
- * import { pipe } from "effect/Function"
22
- *
23
- * assert.deepStrictEqual(pipe({ a: "a", b: 1, c: true }, omit("c")), { a: "a", b: 1 })
24
- * assert.deepStrictEqual(omit({ a: "a", b: 1, c: true }, "c"), { a: "a", b: 1 })
25
- *
26
- * @since 2.0.0
27
- */
28
- export const omit = Struct.omit;
29
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3RydWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1N0cnVjdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEtBQUssTUFBTSxNQUFNLGVBQWUsQ0FBQTtBQUV2QyxjQUFjLGVBQWUsQ0FBQTtBQUU3Qjs7Ozs7Ozs7Ozs7R0FXRztBQUNILE1BQU0sQ0FBQyxNQUFNLElBQUksR0FVYixNQUFNLENBQUMsSUFBSSxDQUFBO0FBS2Y7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLENBQUMsTUFBTSxJQUFJLEdBUWIsTUFBTSxDQUFDLElBQVcsQ0FBQSJ9
package/src/Struct.ts DELETED
@@ -1,54 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { Types } from "effect"
3
- import * as Struct from "effect/Struct"
4
-
5
- export * from "effect/Struct"
6
-
7
- /**
8
- * Create a new object by picking properties of an existing object.
9
- *
10
- * @example
11
- * import { pick } from "effect/Struct"
12
- * import { pipe } from "effect/Function"
13
- *
14
- * assert.deepStrictEqual(pipe({ a: "a", b: 1, c: true }, pick("a", "b")), { a: "a", b: 1 })
15
- * assert.deepStrictEqual(pick({ a: "a", b: 1, c: true }, "a", "b"), { a: "a", b: 1 })
16
- *
17
- * @since 2.0.0
18
- */
19
- export const pick: {
20
- <Keys extends Array<PropertyKey>>(
21
- keys: Keys
22
- ): <S extends { [K in Keys[number]]?: any }>(
23
- s: S
24
- ) => Types.MatchRecord<S, { [K in Keys[number]]?: S[K] }, Pick<S, Keys[number]>>
25
- <S extends object, Keys extends Array<keyof S>>(
26
- s: S,
27
- keys: Keys
28
- ): Types.MatchRecord<S, { [K in Keys[number]]?: S[K] }, Pick<S, Keys[number]>>
29
- } = Struct.pick
30
-
31
- export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K>
32
- : never
33
-
34
- /**
35
- * Create a new object by omitting properties of an existing object.
36
- *
37
- * @example
38
- * import { omit } from "effect/Struct"
39
- * import { pipe } from "effect/Function"
40
- *
41
- * assert.deepStrictEqual(pipe({ a: "a", b: 1, c: true }, omit("c")), { a: "a", b: 1 })
42
- * assert.deepStrictEqual(omit({ a: "a", b: 1, c: true }, "c"), { a: "a", b: 1 })
43
- *
44
- * @since 2.0.0
45
- */
46
- export const omit: {
47
- <Keys extends Array<PropertyKey>>(
48
- keys: Keys
49
- ): <S extends { [K in Keys[number]]?: any }>(s: S) => DistributiveOmit<S, Keys[number]>
50
- <S extends object, Keys extends Array<keyof S>>(
51
- s: S,
52
- keys: Keys
53
- ): DistributiveOmit<S, Keys[number]>
54
- } = Struct.omit as any