@effect-app/infra 4.0.0-beta.325 → 4.0.0-beta.326

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.
@@ -4,6 +4,7 @@ import { makeRepo, ValidationError, ValidationResult } from "effect-app/Model/Re
4
4
  import { RepositoryRegistryLive } from "effect-app/Model/Repository/Registry"
5
5
  import * as S from "effect-app/Schema"
6
6
  import { setupRequestContextFromCurrent } from "effect-app/setupRequest"
7
+ import type { JsonRecord } from "effect-app/Store"
7
8
  import { describe, expect, it } from "vitest"
8
9
  import { MemoryStoreLive } from "../src/Store/Memory.js"
9
10
 
@@ -16,6 +17,26 @@ class SimpleItem extends S.Class<SimpleItem>("SimpleItem")({
16
17
  count: S.NonNegativeInt
17
18
  }) {}
18
19
 
20
+ /**
21
+ * `count` is stored as a *string*, so its Encoded shape accepts anything the
22
+ * JSON document happens to hold while the domain decode still demands a finite
23
+ * number.
24
+ *
25
+ * That split is what `validateSample` exists for, and it is the only way to
26
+ * observe a repository-level validation error now that `jitM` runs at the store
27
+ * boundary: the store's `toCodecJson(toEncoded(schema))` decode is strict, so a
28
+ * `jitM` that produced a value the *Encoded* shape rejects would fail loudly in
29
+ * the store instead of ever reaching the repository.
30
+ */
31
+ class LooseCountItem extends S.Class<LooseCountItem>("LooseCountItem")({
32
+ id: S.String,
33
+ name: S.NonEmptyString255,
34
+ count: S.FiniteFromString
35
+ }) {}
36
+
37
+ /** build a `LooseCountItem` from its encoded (stored) form */
38
+ const looseItem = (id: string, name: string, count: string) => S.decodeSync(LooseCountItem)({ id, name, count })
39
+
19
40
  describe("validateSample", () => {
20
41
  it("returns success when all items pass validation", () =>
21
42
  Effect
@@ -43,23 +64,23 @@ describe("validateSample", () => {
43
64
  Effect.runPromise
44
65
  ))
45
66
 
46
- it("returns errors when jitM produces invalid data", () =>
67
+ it("returns errors when jitM produces data the domain schema rejects", () =>
47
68
  Effect
48
69
  .gen(function*() {
49
- // jitM that corrupts one specific item's count to be negative
50
- const corruptingJitM = (pm: typeof SimpleItem.Encoded) => {
51
- if (pm.id === "2" || pm.id === "3") {
52
- return { ...pm, count: -999 } // make count negative (invalid for NonNegativeInt)
70
+ // jitM that corrupts two specific items' count into a non-numeric string
71
+ const corruptingJitM = (json: JsonRecord): JsonRecord => {
72
+ if (json["id"] === "2" || json["id"] === "3") {
73
+ return { ...json, count: "not-a-number" } // still a string, so the store decode is happy
53
74
  }
54
- return pm
75
+ return json
55
76
  }
56
77
 
57
- const repo = yield* makeRepo("CorruptItem", SimpleItem, {
78
+ const repo = yield* makeRepo("CorruptItem", LooseCountItem, {
58
79
  jitM: corruptingJitM,
59
80
  makeInitial: Effect.succeed([
60
- new SimpleItem({ id: "1", name: S.NonEmptyString255("Valid"), count: S.NonNegativeInt(10) }),
61
- new SimpleItem({ id: "2", name: S.NonEmptyString255("WillBeInvalid1"), count: S.NonNegativeInt(20) }),
62
- new SimpleItem({ id: "3", name: S.NonEmptyString255("WillBeInvalid2"), count: S.NonNegativeInt(30) })
81
+ looseItem("1", "Valid", "10"),
82
+ looseItem("2", "WillBeInvalid1", "20"),
83
+ looseItem("3", "WillBeInvalid2", "30")
63
84
  ])
64
85
  })
65
86
 
@@ -152,9 +173,9 @@ describe("validateSample", () => {
152
173
 
153
174
  // jitM that adds default status for items
154
175
  const repo = yield* makeRepo("ItemWithStatus", ItemWithStatus, {
155
- jitM: (pm) => ({
156
- ...pm,
157
- status: pm.status ?? "active" // default to active if missing
176
+ jitM: (json) => ({
177
+ ...json,
178
+ status: json["status"] ?? "active" // default to active if missing
158
179
  }),
159
180
  makeInitial: Effect.succeed([
160
181
  new ItemWithStatus({ id: "1", status: "active" }),
@@ -180,16 +201,14 @@ describe("validateSample", () => {
180
201
  Effect
181
202
  .gen(function*() {
182
203
  // jitM that corrupts the data
183
- const corruptingJitM = (pm: typeof SimpleItem.Encoded) => ({
184
- ...pm,
185
- count: -999 // always corrupt count
204
+ const corruptingJitM = (json: JsonRecord): JsonRecord => ({
205
+ ...json,
206
+ count: "not-a-number" // always corrupt count
186
207
  })
187
208
 
188
- const repo = yield* makeRepo("ContextErrorTest", SimpleItem, {
209
+ const repo = yield* makeRepo("ContextErrorTest", LooseCountItem, {
189
210
  jitM: corruptingJitM,
190
- makeInitial: Effect.succeed([
191
- new SimpleItem({ id: "bad-item", name: S.NonEmptyString255("Test"), count: S.NonNegativeInt(100) })
192
- ])
211
+ makeInitial: Effect.succeed([looseItem("bad-item", "Test", "100")])
193
212
  })
194
213
 
195
214
  const result = yield* repo.validateSample({ percentage: 1.0 })
@@ -199,23 +218,20 @@ describe("validateSample", () => {
199
218
  const error = result.errors[0]!
200
219
  expect(error.id).toBe("bad-item")
201
220
 
202
- // rawData should contain the original db data (with valid count)
221
+ // rawData is what the store returned: jitM already ran at the store
222
+ // boundary, so the corrupted count is what the repository decoded
203
223
  expect(error.rawData).toMatchObject({
204
224
  id: "bad-item",
205
225
  name: "Test",
206
- count: 100
226
+ count: "not-a-number"
207
227
  })
208
228
 
209
- // jitMResult should contain the corrupted data
210
- expect(error.jitMResult).toMatchObject({
211
- id: "bad-item",
212
- name: "Test",
213
- count: -999
214
- })
229
+ // jitMResult is kept for compatibility and is identical to rawData
230
+ expect(error.jitMResult).toEqual(error.rawData)
215
231
 
216
232
  // error should be a SchemaError
217
233
  expect(error.error).toBeDefined()
218
- expect((error.error as any)._tag).toBe("SchemaError")
234
+ expect((error.error as { _tag?: string })._tag).toBe("SchemaError")
219
235
  })
220
236
  .pipe(
221
237
  Effect.provide(TestStoreLive),