@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.220
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 +1640 -0
- package/_check.sh +1 -1
- package/dist/CUPS.d.ts +7 -7
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +10 -12
- package/dist/Emailer/Sendgrid.d.ts +14 -14
- package/dist/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/Emailer/Sendgrid.js +16 -15
- package/dist/Emailer/fake.d.ts +1 -1
- package/dist/Emailer/service.d.ts +10 -4
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/Emailer.d.ts +1 -1
- package/dist/MainFiberSet.d.ts +9 -9
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +3 -3
- package/dist/Model/Repository/Registry.d.ts +20 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +17 -0
- package/dist/Model/Repository/ext.d.ts +33 -15
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +54 -2
- package/dist/Model/Repository/internal/internal.d.ts +6 -6
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +103 -51
- package/dist/Model/Repository/legacy.d.ts +1 -1
- package/dist/Model/Repository/makeRepo.d.ts +7 -6
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +5 -1
- package/dist/Model/Repository/service.d.ts +28 -23
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/Repository/validation.d.ts +46 -17
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/Repository/validation.js +5 -5
- package/dist/Model/Repository.d.ts +2 -1
- package/dist/Model/Repository.d.ts.map +1 -1
- package/dist/Model/Repository.js +2 -1
- package/dist/Model/dsl.d.ts +4 -4
- package/dist/Model/dsl.d.ts.map +1 -1
- package/dist/Model/filter/filterApi.d.ts +5 -5
- package/dist/Model/filter/filterApi.d.ts.map +1 -1
- package/dist/Model/filter/types/errors.d.ts +1 -1
- package/dist/Model/filter/types/fields.d.ts +1 -1
- package/dist/Model/filter/types/path/common.d.ts +1 -1
- package/dist/Model/filter/types/path/eager.d.ts +1 -1
- package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
- package/dist/Model/filter/types/path/eager.js +1 -1
- package/dist/Model/filter/types/path/index.d.ts +1 -1
- package/dist/Model/filter/types/utils.d.ts +1 -1
- package/dist/Model/filter/types/validator.d.ts +1 -1
- package/dist/Model/filter/types.d.ts +1 -1
- package/dist/Model/query/dsl.d.ts +139 -16
- package/dist/Model/query/dsl.d.ts.map +1 -1
- package/dist/Model/query/dsl.js +187 -1
- package/dist/Model/query/new-kid-interpreter.d.ts +76 -7
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.js +122 -6
- package/dist/Model/query.d.ts +1 -1
- package/dist/Model.d.ts +2 -1
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +2 -1
- package/dist/QueueMaker/SQLQueue.d.ts +5 -7
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +130 -116
- package/dist/QueueMaker/errors.d.ts +2 -2
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts +7 -4
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +75 -63
- package/dist/QueueMaker/sbqueue.d.ts +6 -3
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +52 -53
- package/dist/QueueMaker/service.d.ts +1 -1
- package/dist/RequestContext.d.ts +74 -35
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +13 -14
- package/dist/RequestFiberSet.d.ts +7 -7
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +3 -3
- package/dist/Store/ContextMapContainer.d.ts +19 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +13 -3
- package/dist/Store/Cosmos/query.d.ts +5 -1
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +113 -34
- package/dist/Store/Cosmos.d.ts +1 -1
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +335 -243
- package/dist/Store/Disk.d.ts +2 -2
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +72 -35
- package/dist/Store/Memory.d.ts +6 -4
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +242 -58
- package/dist/Store/SQL/Pg.d.ts +4 -0
- package/dist/Store/SQL/Pg.d.ts.map +1 -0
- package/dist/Store/SQL/Pg.js +231 -0
- package/dist/Store/SQL/query.d.ts +42 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +479 -0
- package/dist/Store/SQL.d.ts +20 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +446 -0
- package/dist/Store/codeFilter.d.ts +1 -1
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +4 -2
- package/dist/Store/index.d.ts +5 -2
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +15 -3
- package/dist/Store/service.d.ts +22 -8
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +24 -6
- package/dist/Store/utils.d.ts +1 -1
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/Store.d.ts +1 -1
- package/dist/adapters/SQL/Model.d.ts +31 -42
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +29 -38
- package/dist/adapters/SQL.d.ts +1 -1
- package/dist/adapters/ServiceBus.d.ts +11 -11
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +25 -21
- package/dist/adapters/cosmos-client.d.ts +3 -3
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -3
- package/dist/adapters/index.d.ts +8 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +8 -2
- package/dist/adapters/logger.d.ts +1 -1
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +3 -3
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +3 -3
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +3 -3
- package/dist/adapters/redis-client.d.ts +3 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +8 -8
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/codec.d.ts +1 -1
- package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
- package/dist/api/internal/RequestContextMiddleware.js +9 -6
- package/dist/api/internal/auth.d.ts +44 -6
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +160 -29
- package/dist/api/internal/events.d.ts +3 -3
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +10 -8
- package/dist/api/internal/health.d.ts +1 -1
- package/dist/api/layerUtils.d.ts +6 -6
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/middlewares.d.ts +1 -1
- package/dist/api/reportError.d.ts +1 -1
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +39 -3
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +48 -16
- package/dist/api/routing/middleware.d.ts +1 -2
- package/dist/api/routing/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware.js +1 -2
- package/dist/api/routing/schema/jwt.d.ts +1 -1
- package/dist/api/routing/schema/jwt.d.ts.map +1 -1
- package/dist/api/routing/tsort.d.ts +1 -1
- package/dist/api/routing/tsort.d.ts.map +1 -1
- package/dist/api/routing/utils.d.ts +3 -3
- package/dist/api/routing/utils.d.ts.map +1 -1
- package/dist/api/routing.d.ts +80 -37
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +109 -41
- package/dist/api/setupRequest.d.ts +8 -5
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +12 -7
- package/dist/api/util.d.ts +1 -1
- package/dist/arbs.d.ts +1 -1
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +5 -3
- package/dist/errorReporter.d.ts +4 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +20 -25
- package/dist/errors.d.ts +1 -1
- package/dist/fileUtil.d.ts +1 -1
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/logger/jsonLogger.d.ts +1 -1
- package/dist/logger/logFmtLogger.d.ts +1 -1
- package/dist/logger/shared.d.ts +1 -1
- package/dist/logger/shared.js +2 -2
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/otel.d.ts +75 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +65 -0
- package/dist/rateLimit.d.ts +9 -3
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +5 -11
- package/dist/test.d.ts +2 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +1 -1
- package/dist/vitest.d.ts +1 -1
- package/examples/query.ts +42 -38
- package/package.json +46 -37
- package/src/CUPS.ts +9 -11
- package/src/Emailer/Sendgrid.ts +17 -14
- package/src/Emailer/service.ts +9 -3
- package/src/MainFiberSet.ts +5 -6
- package/src/Model/Repository/Registry.ts +33 -0
- package/src/Model/Repository/ext.ts +96 -10
- package/src/Model/Repository/internal/internal.ts +218 -149
- package/src/Model/Repository/makeRepo.ts +12 -10
- package/src/Model/Repository/service.ts +31 -22
- package/src/Model/Repository/validation.ts +4 -4
- package/src/Model/Repository.ts +1 -0
- package/src/Model/dsl.ts +3 -3
- package/src/Model/filter/types/path/eager.ts +1 -2
- package/src/Model/query/dsl.ts +348 -18
- package/src/Model/query/new-kid-interpreter.ts +206 -6
- package/src/Model.ts +1 -0
- package/src/QueueMaker/SQLQueue.ts +144 -152
- package/src/QueueMaker/memQueue.ts +104 -103
- package/src/QueueMaker/sbqueue.ts +70 -86
- package/src/RequestContext.ts +14 -16
- package/src/RequestFiberSet.ts +2 -2
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos/query.ts +140 -43
- package/src/Store/Cosmos.ts +482 -349
- package/src/Store/Disk.ts +102 -65
- package/src/Store/Memory.ts +275 -87
- package/src/Store/SQL/Pg.ts +361 -0
- package/src/Store/SQL/query.ts +539 -0
- package/src/Store/SQL.ts +731 -0
- package/src/Store/codeFilter.ts +3 -1
- package/src/Store/index.ts +17 -2
- package/src/Store/service.ts +41 -10
- package/src/Store/utils.ts +23 -22
- package/src/adapters/SQL/Model.ts +41 -40
- package/src/adapters/ServiceBus.ts +125 -121
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/index.ts +7 -0
- package/src/adapters/memQueue.ts +2 -2
- package/src/adapters/mongo-client.ts +2 -2
- package/src/adapters/redis-client.ts +2 -2
- package/src/api/ContextProvider.ts +12 -13
- package/src/api/internal/RequestContextMiddleware.ts +15 -5
- package/src/api/internal/auth.ts +246 -44
- package/src/api/internal/events.ts +13 -9
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +55 -14
- package/src/api/routing/middleware.ts +0 -2
- package/src/api/routing.ts +296 -131
- package/src/api/setupRequest.ts +28 -8
- package/src/arbs.ts +4 -2
- package/src/errorReporter.ts +62 -74
- package/src/logger/shared.ts +1 -1
- package/src/otel.ts +152 -0
- package/src/rateLimit.ts +30 -22
- package/src/test.ts +1 -1
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +11 -11
- package/test/controller.test.ts +21 -30
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/date-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +26 -12
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +12 -10
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/repository-ext.test.d.ts.map +1 -0
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/router-generator.test.d.ts.map +1 -0
- package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
- package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
- package/test/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +11 -9
- package/test/query.test.ts +813 -38
- package/test/rawQuery.test.ts +301 -20
- package/test/repository-ext.test.ts +60 -0
- package/test/requires.test.ts +6 -6
- package/test/router-generator.test.ts +183 -0
- package/test/routing-interruptibility.test.ts +63 -0
- package/test/rpc-e2e-invalidation.test.ts +251 -0
- package/test/rpc-multi-middleware.test.ts +78 -9
- package/test/rpc-stream-fullstack.test.ts +300 -0
- package/test/sql-store.test.ts +1592 -0
- package/test/validateSample.test.ts +15 -12
- package/tsconfig.examples.json +1 -1
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +2 -2
- package/tsconfig.src.json +35 -35
- package/tsconfig.test.json +2 -2
- package/dist/Operations.d.ts +0 -55
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -102
- package/dist/OperationsRepo.d.ts +0 -41
- package/dist/OperationsRepo.d.ts.map +0 -1
- package/dist/OperationsRepo.js +0 -14
- package/eslint.config.mjs +0 -24
- package/src/Operations.ts +0 -235
- package/src/OperationsRepo.ts +0 -16
package/test/query.test.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
/* eslint-disable unused-imports/no-unused-vars */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
import {
|
|
4
|
+
import { SchemaTransformation } from "effect"
|
|
5
|
+
import { Context, Effect, flow, Layer, Option, pipe, S, Struct } from "effect-app"
|
|
5
6
|
import { inspect } from "util"
|
|
6
7
|
import { expect, expectTypeOf, it } from "vitest"
|
|
7
8
|
import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
|
|
8
|
-
import { and, count, make, one, or, order, page, project, type QueryEnd, type QueryProjection, type QueryWhere, toFilter, where } from "../src/Model/query.js"
|
|
9
|
+
import { and, computed, count, expr, make, one, or, order, page, project, projectComputed, type QueryEnd, type QueryProjection, type QueryWhere, relation, toFilter, where } from "../src/Model/query.js"
|
|
9
10
|
import { makeRepo } from "../src/Model/Repository.js"
|
|
11
|
+
import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
|
|
10
12
|
import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
|
|
11
13
|
import { SomeService } from "./fixtures.js"
|
|
12
14
|
|
|
15
|
+
const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
|
|
16
|
+
|
|
13
17
|
const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
|
|
14
|
-
const num = S.Struct({ _tag: S.Literal("number"), value: S.
|
|
18
|
+
const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
|
|
15
19
|
const someUnion = S.Union([str, num])
|
|
16
20
|
|
|
17
21
|
export class Something extends S.Class<Something>("Something")({
|
|
18
|
-
id: S.StringId.
|
|
22
|
+
id: S.StringId.withConstructorDefault,
|
|
19
23
|
displayName: S.NonEmptyString255,
|
|
20
|
-
name: S.NullOr(S.NonEmptyString255).
|
|
21
|
-
n: S.Date.
|
|
22
|
-
union: someUnion.pipe(S.
|
|
24
|
+
name: S.NullOr(S.NonEmptyString255).withConstructorDefault,
|
|
25
|
+
n: S.Date.withConstructorDefault,
|
|
26
|
+
union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
|
|
23
27
|
}) {}
|
|
24
28
|
export declare namespace Something {
|
|
25
29
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
@@ -90,8 +94,8 @@ it("works", () => {
|
|
|
90
94
|
|
|
91
95
|
const processed = memFilter(interpreted)(items.map((_) =>
|
|
92
96
|
S.encodeUnknownSync(S.Struct({
|
|
93
|
-
...
|
|
94
|
-
displayName: S.
|
|
97
|
+
...Struct.omit(Something.fields, ["displayName"]),
|
|
98
|
+
displayName: S.Literals(["Verona", "Riley"])
|
|
95
99
|
}))(_)
|
|
96
100
|
))
|
|
97
101
|
|
|
@@ -99,7 +103,7 @@ it("works", () => {
|
|
|
99
103
|
})
|
|
100
104
|
|
|
101
105
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
|
102
|
-
class SomethingRepo extends
|
|
106
|
+
class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
|
|
103
107
|
make: Effect.gen(function*() {
|
|
104
108
|
return yield* makeRepo("Something", Something, {})
|
|
105
109
|
})
|
|
@@ -112,7 +116,7 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo",
|
|
|
112
116
|
})
|
|
113
117
|
)
|
|
114
118
|
.pipe(
|
|
115
|
-
Layer.provide(
|
|
119
|
+
Layer.provide(TestStoreLive)
|
|
116
120
|
)
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -236,15 +240,15 @@ it("collect", () =>
|
|
|
236
240
|
Effect.runPromise
|
|
237
241
|
))
|
|
238
242
|
|
|
239
|
-
class Person extends S.
|
|
243
|
+
class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
|
|
240
244
|
id: S.String,
|
|
241
245
|
surname: S.String
|
|
242
246
|
}) {}
|
|
243
|
-
class Animal extends S.
|
|
247
|
+
class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
|
|
244
248
|
id: S.String,
|
|
245
249
|
surname: S.String
|
|
246
250
|
}) {}
|
|
247
|
-
class Test extends S.
|
|
251
|
+
class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
|
|
248
252
|
id: S.String
|
|
249
253
|
}) {}
|
|
250
254
|
|
|
@@ -279,7 +283,7 @@ it(
|
|
|
279
283
|
expect(result).toEqual([])
|
|
280
284
|
expect(result2).toEqual([])
|
|
281
285
|
})
|
|
282
|
-
.pipe(Effect.provide(
|
|
286
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
283
287
|
)
|
|
284
288
|
|
|
285
289
|
it(
|
|
@@ -465,7 +469,7 @@ it(
|
|
|
465
469
|
|
|
466
470
|
expect([]).toEqual([])
|
|
467
471
|
})
|
|
468
|
-
.pipe(Effect.provide(
|
|
472
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
469
473
|
)
|
|
470
474
|
|
|
471
475
|
it(
|
|
@@ -508,7 +512,7 @@ it(
|
|
|
508
512
|
|
|
509
513
|
expect([]).toEqual([])
|
|
510
514
|
})
|
|
511
|
-
.pipe(Effect.provide(
|
|
515
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
512
516
|
)
|
|
513
517
|
|
|
514
518
|
it(
|
|
@@ -519,8 +523,8 @@ it(
|
|
|
519
523
|
const schema = S.Struct({
|
|
520
524
|
id: S.String,
|
|
521
525
|
createdAt: S.Date.pipe(
|
|
522
|
-
S.withDecodingDefault(() => new Date().toISOString()),
|
|
523
|
-
S.withConstructorDefault(() =>
|
|
526
|
+
S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
|
|
527
|
+
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
524
528
|
)
|
|
525
529
|
})
|
|
526
530
|
const repo = yield* makeRepo(
|
|
@@ -532,8 +536,8 @@ it(
|
|
|
532
536
|
const outputSchema = S.Struct({
|
|
533
537
|
id: S.Literal("123"),
|
|
534
538
|
createdAt: S.Date.pipe(
|
|
535
|
-
S.withDecodingDefault(() => new Date().toISOString()),
|
|
536
|
-
S.withConstructorDefault(() =>
|
|
539
|
+
S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
|
|
540
|
+
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
537
541
|
)
|
|
538
542
|
})
|
|
539
543
|
|
|
@@ -541,9 +545,246 @@ it(
|
|
|
541
545
|
|
|
542
546
|
expect(result).toEqual([])
|
|
543
547
|
})
|
|
544
|
-
.pipe(Effect.provide(
|
|
548
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
545
549
|
)
|
|
546
550
|
|
|
551
|
+
it(
|
|
552
|
+
"project with encodeKeys in projection maps encoded keys",
|
|
553
|
+
() =>
|
|
554
|
+
Effect
|
|
555
|
+
.gen(function*() {
|
|
556
|
+
const schema = S.Struct({
|
|
557
|
+
id: S.String,
|
|
558
|
+
a: S.Number
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
const repo = yield* makeRepo(
|
|
562
|
+
"test",
|
|
563
|
+
schema,
|
|
564
|
+
{
|
|
565
|
+
makeInitial: Effect.sync(() => [{ id: "1", a: 1 }])
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
const outputSchema = S.Struct({ b: S.Number }).pipe(S.encodeKeys({ b: "a" }))
|
|
570
|
+
|
|
571
|
+
const result = yield* repo.query(project(outputSchema))
|
|
572
|
+
|
|
573
|
+
expect(result).toStrictEqual([{ b: 1 }])
|
|
574
|
+
})
|
|
575
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
it("projectComputed sets computed IR and forces project mode", () => {
|
|
579
|
+
const baseSchema = S.Struct({
|
|
580
|
+
id: S.String,
|
|
581
|
+
items: S.Array(S.Struct({
|
|
582
|
+
state: S.Struct({
|
|
583
|
+
_tag: S.String
|
|
584
|
+
})
|
|
585
|
+
}))
|
|
586
|
+
})
|
|
587
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
588
|
+
projectComputed(
|
|
589
|
+
S.Struct({
|
|
590
|
+
pickedCount: S.NonNegativeInt
|
|
591
|
+
}),
|
|
592
|
+
computed({
|
|
593
|
+
pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count(where("state._tag", "Picked"))
|
|
594
|
+
})
|
|
595
|
+
)
|
|
596
|
+
)
|
|
597
|
+
const interpreted = toFilter(query, baseSchema)
|
|
598
|
+
expect(interpreted.mode).toBe("project")
|
|
599
|
+
expect(interpreted.select).toEqual([
|
|
600
|
+
{
|
|
601
|
+
key: "pickedCount",
|
|
602
|
+
computed: {
|
|
603
|
+
_tag: "relation-count",
|
|
604
|
+
path: "items",
|
|
605
|
+
filter: [{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }]
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
])
|
|
609
|
+
expect(interpreted.computed?.["pickedCount"]?._tag).toBe("relation-count")
|
|
610
|
+
expect(interpreted.computed?.["pickedCount"]?.path).toBe("items")
|
|
611
|
+
expect(interpreted.computed?.["pickedCount"]?.filter).toEqual([
|
|
612
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
613
|
+
])
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
it("projectComputed validates extra computed keys", () => {
|
|
617
|
+
const baseSchema = S.Struct({
|
|
618
|
+
id: S.String,
|
|
619
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
620
|
+
})
|
|
621
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
622
|
+
projectComputed(
|
|
623
|
+
S.Struct({ id: S.String }),
|
|
624
|
+
computed({
|
|
625
|
+
pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count()
|
|
626
|
+
})
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Computed projection keys must exist in projection schema")
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
it("projection schema with computed fields fails without computed map", () => {
|
|
633
|
+
const baseSchema = S.Struct({
|
|
634
|
+
id: S.String,
|
|
635
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
636
|
+
})
|
|
637
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
638
|
+
projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
|
|
639
|
+
)
|
|
640
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
it("projectComputed.every emits relation-every IR", () => {
|
|
644
|
+
const baseSchema = S.Struct({
|
|
645
|
+
id: S.String,
|
|
646
|
+
items: S.Array(S.Struct({ state: S.Struct({ _tag: S.String }) }))
|
|
647
|
+
})
|
|
648
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
649
|
+
projectComputed(
|
|
650
|
+
S.Struct({ allPicked: S.Boolean }),
|
|
651
|
+
computed({
|
|
652
|
+
allPicked: relation<S.Codec.Encoded<typeof baseSchema>>("items").every(where("state._tag", "Picked"))
|
|
653
|
+
})
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
const interpreted = toFilter(query, baseSchema)
|
|
657
|
+
expect(interpreted.computed?.["allPicked"]?._tag).toBe("relation-every")
|
|
658
|
+
expect(interpreted.computed?.["allPicked"]?.path).toBe("items")
|
|
659
|
+
expect(interpreted.computed?.["allPicked"]?.filter).toEqual([
|
|
660
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
661
|
+
])
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
it("projectComputed.distinctCount emits relation-distinct-count IR with field", () => {
|
|
665
|
+
const baseSchema = S.Struct({
|
|
666
|
+
id: S.String,
|
|
667
|
+
items: S.Array(S.Struct({ rowId: S.String, state: S.Struct({ _tag: S.String }) }))
|
|
668
|
+
})
|
|
669
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
670
|
+
projectComputed(
|
|
671
|
+
S.Struct({ positionCount: S.NonNegativeInt }),
|
|
672
|
+
computed({
|
|
673
|
+
positionCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").distinctCount(
|
|
674
|
+
"rowId",
|
|
675
|
+
where("state._tag", "neq", "cancelled")
|
|
676
|
+
)
|
|
677
|
+
})
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
const interpreted = toFilter(query, baseSchema)
|
|
681
|
+
const ir = interpreted.computed?.["positionCount"]
|
|
682
|
+
expect(ir?._tag).toBe("relation-distinct-count")
|
|
683
|
+
expect((ir as { field: string } | undefined)?.field).toBe("rowId")
|
|
684
|
+
expect(ir?.filter).toEqual([
|
|
685
|
+
{ t: "where", path: "items.-1.state._tag", op: "neq", value: "cancelled" }
|
|
686
|
+
])
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
it("projectComputed.sum emits relation-sum IR with field", () => {
|
|
690
|
+
const baseSchema = S.Struct({
|
|
691
|
+
id: S.String,
|
|
692
|
+
items: S.Array(S.Struct({ weight: S.Number }))
|
|
693
|
+
})
|
|
694
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
695
|
+
projectComputed(
|
|
696
|
+
S.Struct({ totalWeight: S.Number }),
|
|
697
|
+
computed({ totalWeight: relation<S.Codec.Encoded<typeof baseSchema>>("items").sum("weight") })
|
|
698
|
+
)
|
|
699
|
+
)
|
|
700
|
+
const interpreted = toFilter(query, baseSchema)
|
|
701
|
+
const ir = interpreted.computed?.["totalWeight"]
|
|
702
|
+
expect(ir?._tag).toBe("relation-sum")
|
|
703
|
+
expect((ir as { field: string } | undefined)?.field).toBe("weight")
|
|
704
|
+
expect(ir?.filter).toEqual([])
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
it("projectComputed.collect / collectDistinct emit relation-collect IR", () => {
|
|
708
|
+
const baseSchema = S.Struct({
|
|
709
|
+
id: S.String,
|
|
710
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
711
|
+
})
|
|
712
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
713
|
+
projectComputed(
|
|
714
|
+
S.Struct({
|
|
715
|
+
all: S.Array(S.String),
|
|
716
|
+
distinct: S.Array(S.String)
|
|
717
|
+
}),
|
|
718
|
+
computed({
|
|
719
|
+
all: relation<S.Codec.Encoded<typeof baseSchema>>("items").collect("articleId"),
|
|
720
|
+
distinct: relation<S.Codec.Encoded<typeof baseSchema>>("items").collectDistinct("articleId")
|
|
721
|
+
})
|
|
722
|
+
)
|
|
723
|
+
)
|
|
724
|
+
const interpreted = toFilter(query, baseSchema)
|
|
725
|
+
const all = interpreted.computed?.["all"]
|
|
726
|
+
const distinct = interpreted.computed?.["distinct"]
|
|
727
|
+
expect(all?._tag).toBe("relation-collect")
|
|
728
|
+
expect((all as { distinct: boolean } | undefined)?.distinct).toBe(false)
|
|
729
|
+
expect(distinct?._tag).toBe("relation-collect")
|
|
730
|
+
expect((distinct as { distinct: boolean } | undefined)?.distinct).toBe(true)
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
it("projectComputed.sumExpr emits relation-sum-expr IR", () => {
|
|
734
|
+
const baseSchema = S.Struct({
|
|
735
|
+
id: S.String,
|
|
736
|
+
items: S.Array(S.Struct({
|
|
737
|
+
weight: S.Number,
|
|
738
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
739
|
+
}))
|
|
740
|
+
})
|
|
741
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
742
|
+
projectComputed(
|
|
743
|
+
S.Struct({ total: S.Number }),
|
|
744
|
+
computed({
|
|
745
|
+
total: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExpr(
|
|
746
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
747
|
+
)
|
|
748
|
+
})
|
|
749
|
+
)
|
|
750
|
+
)
|
|
751
|
+
const interpreted = toFilter(query, baseSchema)
|
|
752
|
+
const ir = interpreted.computed?.["total"]
|
|
753
|
+
expect(ir?._tag).toBe("relation-sum-expr")
|
|
754
|
+
expect((ir as { expression: unknown } | undefined)?.expression).toEqual({
|
|
755
|
+
_tag: "mul",
|
|
756
|
+
left: { _tag: "field", field: "weight" },
|
|
757
|
+
right: { _tag: "field", field: "tradeUnit.amount" }
|
|
758
|
+
})
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
it("projectComputed.sumExprBy emits relation-sum-expr-by IR", () => {
|
|
762
|
+
const baseSchema = S.Struct({
|
|
763
|
+
id: S.String,
|
|
764
|
+
items: S.Array(S.Struct({
|
|
765
|
+
weight: S.Number,
|
|
766
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
767
|
+
}))
|
|
768
|
+
})
|
|
769
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
770
|
+
projectComputed(
|
|
771
|
+
S.Struct({
|
|
772
|
+
totals: S.Array(S.Struct({ unit: S.String, total: S.Number }))
|
|
773
|
+
}),
|
|
774
|
+
computed({
|
|
775
|
+
totals: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExprBy(
|
|
776
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount")),
|
|
777
|
+
{ unit: "tradeUnit.unit" }
|
|
778
|
+
)
|
|
779
|
+
})
|
|
780
|
+
)
|
|
781
|
+
)
|
|
782
|
+
const interpreted = toFilter(query, baseSchema)
|
|
783
|
+
const ir = interpreted.computed?.["totals"]
|
|
784
|
+
expect(ir?._tag).toBe("relation-sum-expr-by")
|
|
785
|
+
expect((ir as { unit: string } | undefined)?.unit).toBe("tradeUnit.unit")
|
|
786
|
+
})
|
|
787
|
+
|
|
547
788
|
it(
|
|
548
789
|
"doesn't mess when refining fields",
|
|
549
790
|
() =>
|
|
@@ -551,7 +792,7 @@ it(
|
|
|
551
792
|
.gen(function*() {
|
|
552
793
|
const schema = S.Struct({
|
|
553
794
|
id: S.String,
|
|
554
|
-
literals: S.
|
|
795
|
+
literals: S.Literals(["a", "b", "c"])
|
|
555
796
|
})
|
|
556
797
|
|
|
557
798
|
type Schema = typeof schema.Type
|
|
@@ -571,7 +812,7 @@ it(
|
|
|
571
812
|
|
|
572
813
|
expect(result).toEqual([])
|
|
573
814
|
})
|
|
574
|
-
.pipe(Effect.provide(
|
|
815
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
575
816
|
)
|
|
576
817
|
|
|
577
818
|
it(
|
|
@@ -581,7 +822,7 @@ it(
|
|
|
581
822
|
.gen(function*() {
|
|
582
823
|
const schema = S.Struct({
|
|
583
824
|
id: S.String,
|
|
584
|
-
literals: S.Union([S.
|
|
825
|
+
literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
|
|
585
826
|
})
|
|
586
827
|
|
|
587
828
|
type Schema = typeof schema.Type
|
|
@@ -615,7 +856,7 @@ it(
|
|
|
615
856
|
|
|
616
857
|
expect(result).toEqual([])
|
|
617
858
|
})
|
|
618
|
-
.pipe(Effect.provide(
|
|
859
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
619
860
|
)
|
|
620
861
|
|
|
621
862
|
it(
|
|
@@ -659,7 +900,7 @@ it(
|
|
|
659
900
|
|
|
660
901
|
expect(result).toEqual([])
|
|
661
902
|
})
|
|
662
|
-
.pipe(Effect.provide(
|
|
903
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
|
|
663
904
|
)
|
|
664
905
|
|
|
665
906
|
it("remove null from one constituent of a tagged union", () =>
|
|
@@ -672,7 +913,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
672
913
|
|
|
673
914
|
class BB extends S.Class<BB>("BB")({
|
|
674
915
|
id: S.Literal("BB"),
|
|
675
|
-
b: S.NullOr(S.
|
|
916
|
+
b: S.NullOr(S.Finite)
|
|
676
917
|
}) {}
|
|
677
918
|
|
|
678
919
|
type Union = AA | BB
|
|
@@ -708,7 +949,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
708
949
|
})[]
|
|
709
950
|
>()
|
|
710
951
|
})
|
|
711
|
-
.pipe(Effect.provide(
|
|
952
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
712
953
|
|
|
713
954
|
it("refine 3", () =>
|
|
714
955
|
Effect
|
|
@@ -746,7 +987,7 @@ it("refine 3", () =>
|
|
|
746
987
|
const resQuer1 = yield* repo.query(where("id", "AA"))
|
|
747
988
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
748
989
|
})
|
|
749
|
-
.pipe(Effect.provide(
|
|
990
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
750
991
|
|
|
751
992
|
it("my test", () =>
|
|
752
993
|
Effect
|
|
@@ -764,7 +1005,7 @@ it("my test", () =>
|
|
|
764
1005
|
)
|
|
765
1006
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
766
1007
|
})
|
|
767
|
-
.pipe(Effect.provide(
|
|
1008
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
768
1009
|
|
|
769
1010
|
it("refine inner without imposing a projection", () =>
|
|
770
1011
|
Effect
|
|
@@ -808,7 +1049,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
808
1049
|
where("union._tag", "AA"),
|
|
809
1050
|
// But if I wanna the whole Data as output ignoring the inner refinement
|
|
810
1051
|
// I wanna be able to do so
|
|
811
|
-
project(
|
|
1052
|
+
project(Data.mapFields(Struct.pick(["union"])))
|
|
812
1053
|
)
|
|
813
1054
|
|
|
814
1055
|
expectTypeOf(query2).toEqualTypeOf<
|
|
@@ -839,7 +1080,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
839
1080
|
}[]
|
|
840
1081
|
>()
|
|
841
1082
|
})
|
|
842
|
-
.pipe(Effect.provide(
|
|
1083
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
843
1084
|
|
|
844
1085
|
it("does not allow string queries on arrays", () =>
|
|
845
1086
|
Effect
|
|
@@ -874,7 +1115,7 @@ it("does not allow string queries on arrays", () =>
|
|
|
874
1115
|
expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
875
1116
|
expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
876
1117
|
})
|
|
877
|
-
.pipe(Effect.provide(
|
|
1118
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
878
1119
|
|
|
879
1120
|
it("test array.length", () =>
|
|
880
1121
|
Effect
|
|
@@ -915,7 +1156,7 @@ it("test array.length", () =>
|
|
|
915
1156
|
QueryWhere<Something, Something>
|
|
916
1157
|
>()
|
|
917
1158
|
})
|
|
918
|
-
.pipe(Effect.provide(
|
|
1159
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
919
1160
|
|
|
920
1161
|
it("distribution over union", () =>
|
|
921
1162
|
Effect
|
|
@@ -939,7 +1180,7 @@ it("distribution over union", () =>
|
|
|
939
1180
|
})[]
|
|
940
1181
|
>()
|
|
941
1182
|
})
|
|
942
|
-
.pipe(Effect.provide(
|
|
1183
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
943
1184
|
|
|
944
1185
|
it("refine nested union", () =>
|
|
945
1186
|
Effect
|
|
@@ -980,7 +1221,158 @@ it("refine nested union", () =>
|
|
|
980
1221
|
}[]
|
|
981
1222
|
>()
|
|
982
1223
|
})
|
|
983
|
-
.pipe(Effect.provide(
|
|
1224
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
1225
|
+
|
|
1226
|
+
it("find with transformed id", () =>
|
|
1227
|
+
Effect
|
|
1228
|
+
.gen(function*() {
|
|
1229
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1230
|
+
|
|
1231
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1232
|
+
configuratorId: ConfiguratorId,
|
|
1233
|
+
label: S.NonEmptyString50
|
|
1234
|
+
}) {}
|
|
1235
|
+
|
|
1236
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1237
|
+
S.decodeTo(
|
|
1238
|
+
S.toType(PreconfigurationId),
|
|
1239
|
+
SchemaTransformation.transformOrFail({
|
|
1240
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1241
|
+
const values = value.split("_")
|
|
1242
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1243
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1244
|
+
values.join("_")
|
|
1245
|
+
)
|
|
1246
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1247
|
+
}),
|
|
1248
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1249
|
+
})
|
|
1250
|
+
),
|
|
1251
|
+
S.revealCodec
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
const Preconfiguration = S.Struct({
|
|
1255
|
+
id: PreconfigurationIdFromString,
|
|
1256
|
+
name: S.String
|
|
1257
|
+
})
|
|
1258
|
+
|
|
1259
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
|
|
1260
|
+
|
|
1261
|
+
const id = new PreconfigurationId({
|
|
1262
|
+
configuratorId: S.NonEmptyString255("myConfigurator"),
|
|
1263
|
+
label: S.NonEmptyString50("myLabel")
|
|
1264
|
+
})
|
|
1265
|
+
const item = { id, name: "test preconfig" }
|
|
1266
|
+
|
|
1267
|
+
yield* repo.saveAndPublish([item])
|
|
1268
|
+
|
|
1269
|
+
const found = yield* repo.find(id)
|
|
1270
|
+
expect(Option.isSome(found)).toBe(true)
|
|
1271
|
+
expect(Option.getOrThrow(found).name).toBe("test preconfig")
|
|
1272
|
+
expect(Option.getOrThrow(found).id).toEqual(id)
|
|
1273
|
+
|
|
1274
|
+
const notFound = yield* repo.find(
|
|
1275
|
+
new PreconfigurationId({
|
|
1276
|
+
configuratorId: S.NonEmptyString255("other"),
|
|
1277
|
+
label: S.NonEmptyString50("nope")
|
|
1278
|
+
})
|
|
1279
|
+
)
|
|
1280
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1281
|
+
})
|
|
1282
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
1283
|
+
|
|
1284
|
+
it("find with transformed id in tagged union", () =>
|
|
1285
|
+
Effect
|
|
1286
|
+
.gen(function*() {
|
|
1287
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1288
|
+
|
|
1289
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1290
|
+
configuratorId: ConfiguratorId,
|
|
1291
|
+
label: S.NonEmptyString50
|
|
1292
|
+
}) {}
|
|
1293
|
+
|
|
1294
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1295
|
+
S.decodeTo(
|
|
1296
|
+
S.toType(PreconfigurationId),
|
|
1297
|
+
SchemaTransformation.transformOrFail({
|
|
1298
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1299
|
+
const values = value.split("_")
|
|
1300
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1301
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1302
|
+
values.join("_")
|
|
1303
|
+
)
|
|
1304
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1305
|
+
}),
|
|
1306
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1307
|
+
})
|
|
1308
|
+
),
|
|
1309
|
+
S.revealCodec
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
class Draft extends S.TaggedClass<Draft>()("Draft", {
|
|
1313
|
+
id: PreconfigurationIdFromString,
|
|
1314
|
+
name: S.String
|
|
1315
|
+
}) {}
|
|
1316
|
+
|
|
1317
|
+
class Published extends S.TaggedClass<Published>()("Published", {
|
|
1318
|
+
id: PreconfigurationIdFromString,
|
|
1319
|
+
name: S.String,
|
|
1320
|
+
publishedAt: S.String
|
|
1321
|
+
}) {}
|
|
1322
|
+
|
|
1323
|
+
class Archived extends S.TaggedClass<Archived>()("Archived", {
|
|
1324
|
+
id: PreconfigurationIdFromString,
|
|
1325
|
+
name: S.String,
|
|
1326
|
+
archivedAt: S.String
|
|
1327
|
+
}) {}
|
|
1328
|
+
|
|
1329
|
+
const Preconfiguration = S.Union([Draft, Published, Archived])
|
|
1330
|
+
|
|
1331
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
|
|
1332
|
+
|
|
1333
|
+
const id1 = new PreconfigurationId({
|
|
1334
|
+
configuratorId: S.NonEmptyString255("conf1"),
|
|
1335
|
+
label: S.NonEmptyString50("draft1")
|
|
1336
|
+
})
|
|
1337
|
+
const id2 = new PreconfigurationId({
|
|
1338
|
+
configuratorId: S.NonEmptyString255("conf2"),
|
|
1339
|
+
label: S.NonEmptyString50("pub1")
|
|
1340
|
+
})
|
|
1341
|
+
const id3 = new PreconfigurationId({
|
|
1342
|
+
configuratorId: S.NonEmptyString255("conf3"),
|
|
1343
|
+
label: S.NonEmptyString50("arch1")
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
const draft = new Draft({ id: id1, name: "my draft" })
|
|
1347
|
+
const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
|
|
1348
|
+
const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
|
|
1349
|
+
|
|
1350
|
+
yield* repo.saveAndPublish([draft, published, archived])
|
|
1351
|
+
|
|
1352
|
+
// find each by their PreconfigurationId instance
|
|
1353
|
+
const foundDraft = yield* repo.find(id1)
|
|
1354
|
+
expect(Option.isSome(foundDraft)).toBe(true)
|
|
1355
|
+
expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
|
|
1356
|
+
expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
|
|
1357
|
+
|
|
1358
|
+
const foundPublished = yield* repo.find(id2)
|
|
1359
|
+
expect(Option.isSome(foundPublished)).toBe(true)
|
|
1360
|
+
expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
|
|
1361
|
+
|
|
1362
|
+
const foundArchived = yield* repo.find(id3)
|
|
1363
|
+
expect(Option.isSome(foundArchived)).toBe(true)
|
|
1364
|
+
expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
|
|
1365
|
+
|
|
1366
|
+
// not found
|
|
1367
|
+
const notFound = yield* repo.find(
|
|
1368
|
+
new PreconfigurationId({
|
|
1369
|
+
configuratorId: S.NonEmptyString255("nope"),
|
|
1370
|
+
label: S.NonEmptyString50("nope")
|
|
1371
|
+
})
|
|
1372
|
+
)
|
|
1373
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1374
|
+
})
|
|
1375
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
984
1376
|
|
|
985
1377
|
it("refine union with nested union", () =>
|
|
986
1378
|
Effect
|
|
@@ -1095,4 +1487,387 @@ it("refine union with nested union", () =>
|
|
|
1095
1487
|
})[]
|
|
1096
1488
|
>()
|
|
1097
1489
|
})
|
|
1098
|
-
.pipe(Effect.provide(
|
|
1490
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
|
|
1491
|
+
|
|
1492
|
+
// ---------------------------------------------------------------------------
|
|
1493
|
+
// memFilter: computed projection execution (in-memory) and code filter coverage
|
|
1494
|
+
// ---------------------------------------------------------------------------
|
|
1495
|
+
|
|
1496
|
+
const computedBaseSchema = S.Struct({
|
|
1497
|
+
id: S.String,
|
|
1498
|
+
status: S.Literals(["active", "archived"]),
|
|
1499
|
+
items: S.Array(S.Struct({
|
|
1500
|
+
id: S.String,
|
|
1501
|
+
tag: S.Literals(["a", "b", "c"]),
|
|
1502
|
+
qty: S.Finite,
|
|
1503
|
+
note: S.String
|
|
1504
|
+
}))
|
|
1505
|
+
})
|
|
1506
|
+
type ComputedBase = S.Codec.Encoded<typeof computedBaseSchema>
|
|
1507
|
+
|
|
1508
|
+
const computedRows: ComputedBase[] = [
|
|
1509
|
+
{
|
|
1510
|
+
id: "r1",
|
|
1511
|
+
status: "active",
|
|
1512
|
+
items: [
|
|
1513
|
+
{ id: "i1", tag: "a", qty: 10, note: "alpha" },
|
|
1514
|
+
{ id: "i2", tag: "a", qty: 20, note: "alpha" },
|
|
1515
|
+
{ id: "i3", tag: "b", qty: 5, note: "beta" }
|
|
1516
|
+
]
|
|
1517
|
+
},
|
|
1518
|
+
{ id: "r2", status: "active", items: [] },
|
|
1519
|
+
{
|
|
1520
|
+
id: "r3",
|
|
1521
|
+
status: "archived",
|
|
1522
|
+
items: [
|
|
1523
|
+
{ id: "i4", tag: "b", qty: 7, note: "gamma" },
|
|
1524
|
+
{ id: "i5", tag: "b", qty: 7, note: "gamma" },
|
|
1525
|
+
{ id: "i6", tag: "c", qty: 3, note: "delta" }
|
|
1526
|
+
]
|
|
1527
|
+
}
|
|
1528
|
+
]
|
|
1529
|
+
|
|
1530
|
+
it("memFilter: relation-count with filter", () => {
|
|
1531
|
+
const q = make<ComputedBase>().pipe(
|
|
1532
|
+
projectComputed(
|
|
1533
|
+
S.Struct({ id: S.String, aCount: S.NonNegativeInt }),
|
|
1534
|
+
computed({
|
|
1535
|
+
aCount: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1536
|
+
})
|
|
1537
|
+
)
|
|
1538
|
+
)
|
|
1539
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1540
|
+
{ id: "r1", aCount: 2 },
|
|
1541
|
+
{ id: "r2", aCount: 0 },
|
|
1542
|
+
{ id: "r3", aCount: 0 }
|
|
1543
|
+
])
|
|
1544
|
+
})
|
|
1545
|
+
|
|
1546
|
+
it("memFilter: relation-any / every with filter", () => {
|
|
1547
|
+
const q = make<ComputedBase>().pipe(
|
|
1548
|
+
projectComputed(
|
|
1549
|
+
S.Struct({
|
|
1550
|
+
id: S.String,
|
|
1551
|
+
hasA: S.Boolean,
|
|
1552
|
+
allB: S.Boolean
|
|
1553
|
+
}),
|
|
1554
|
+
computed({
|
|
1555
|
+
hasA: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1556
|
+
allB: relation<ComputedBase>("items").every(where("tag", "b"))
|
|
1557
|
+
})
|
|
1558
|
+
)
|
|
1559
|
+
)
|
|
1560
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1561
|
+
{ id: "r1", hasA: true, allB: false },
|
|
1562
|
+
// empty array: any → false, every → true (JS Array.every on [] is true)
|
|
1563
|
+
{ id: "r2", hasA: false, allB: true },
|
|
1564
|
+
{ id: "r3", hasA: false, allB: false }
|
|
1565
|
+
])
|
|
1566
|
+
})
|
|
1567
|
+
|
|
1568
|
+
it("memFilter: relation-distinct-count with filter", () => {
|
|
1569
|
+
const q = make<ComputedBase>().pipe(
|
|
1570
|
+
projectComputed(
|
|
1571
|
+
S.Struct({ id: S.String, distinctNotes: S.NonNegativeInt }),
|
|
1572
|
+
computed({
|
|
1573
|
+
distinctNotes: relation<ComputedBase>("items").distinctCount("note", where("tag", "neq", "c"))
|
|
1574
|
+
})
|
|
1575
|
+
)
|
|
1576
|
+
)
|
|
1577
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1578
|
+
{ id: "r1", distinctNotes: 2 }, // alpha, beta
|
|
1579
|
+
{ id: "r2", distinctNotes: 0 },
|
|
1580
|
+
{ id: "r3", distinctNotes: 1 } // gamma (delta filtered out)
|
|
1581
|
+
])
|
|
1582
|
+
})
|
|
1583
|
+
|
|
1584
|
+
it("memFilter: relation-sum with filter", () => {
|
|
1585
|
+
const q = make<ComputedBase>().pipe(
|
|
1586
|
+
projectComputed(
|
|
1587
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
1588
|
+
computed({
|
|
1589
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
1590
|
+
})
|
|
1591
|
+
)
|
|
1592
|
+
)
|
|
1593
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1594
|
+
{ id: "r1", totalQty: 35 },
|
|
1595
|
+
{ id: "r2", totalQty: 0 },
|
|
1596
|
+
{ id: "r3", totalQty: 14 }
|
|
1597
|
+
])
|
|
1598
|
+
})
|
|
1599
|
+
|
|
1600
|
+
it("memFilter: relation-collect / collectDistinct with filter", () => {
|
|
1601
|
+
const q = make<ComputedBase>().pipe(
|
|
1602
|
+
projectComputed(
|
|
1603
|
+
S.Struct({
|
|
1604
|
+
id: S.String,
|
|
1605
|
+
notes: S.Array(S.String),
|
|
1606
|
+
distinctNotes: S.Array(S.String)
|
|
1607
|
+
}),
|
|
1608
|
+
computed({
|
|
1609
|
+
notes: relation<ComputedBase>("items").collect("note", where("tag", "neq", "c")),
|
|
1610
|
+
distinctNotes: relation<ComputedBase>("items").collectDistinct("note", where("tag", "neq", "c"))
|
|
1611
|
+
})
|
|
1612
|
+
)
|
|
1613
|
+
)
|
|
1614
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1615
|
+
{ id: "r1", notes: ["alpha", "alpha", "beta"], distinctNotes: ["alpha", "beta"] },
|
|
1616
|
+
{ id: "r2", notes: [], distinctNotes: [] },
|
|
1617
|
+
{ id: "r3", notes: ["gamma", "gamma"], distinctNotes: ["gamma"] }
|
|
1618
|
+
])
|
|
1619
|
+
})
|
|
1620
|
+
|
|
1621
|
+
it("memFilter: computed projection with multi-statement relation filter", () => {
|
|
1622
|
+
const q = make<ComputedBase>().pipe(
|
|
1623
|
+
projectComputed(
|
|
1624
|
+
S.Struct({ id: S.String, hits: S.NonNegativeInt }),
|
|
1625
|
+
computed({
|
|
1626
|
+
hits: relation<ComputedBase>("items").count(
|
|
1627
|
+
flow(
|
|
1628
|
+
where("tag", "a"),
|
|
1629
|
+
and("qty", "gt", 10)
|
|
1630
|
+
)
|
|
1631
|
+
)
|
|
1632
|
+
})
|
|
1633
|
+
)
|
|
1634
|
+
)
|
|
1635
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1636
|
+
{ id: "r1", hits: 1 }, // only i2 (a, qty 20)
|
|
1637
|
+
{ id: "r2", hits: 0 },
|
|
1638
|
+
{ id: "r3", hits: 0 }
|
|
1639
|
+
])
|
|
1640
|
+
})
|
|
1641
|
+
|
|
1642
|
+
it("memFilter: relation-sum-expr / sum-expr-by / sum-expr-normalized", () => {
|
|
1643
|
+
const schema = S.Struct({
|
|
1644
|
+
id: S.String,
|
|
1645
|
+
items: S.Array(S.Struct({
|
|
1646
|
+
weight: S.Finite,
|
|
1647
|
+
tradeUnit: S.Struct({ amount: S.Finite, unit: S.String })
|
|
1648
|
+
}))
|
|
1649
|
+
})
|
|
1650
|
+
type Row = S.Codec.Encoded<typeof schema>
|
|
1651
|
+
const rows: Row[] = [
|
|
1652
|
+
{
|
|
1653
|
+
id: "r1",
|
|
1654
|
+
items: [
|
|
1655
|
+
{ weight: 2, tradeUnit: { amount: 5, unit: "kg" } },
|
|
1656
|
+
{ weight: 4, tradeUnit: { amount: 1000, unit: "g" } },
|
|
1657
|
+
{ weight: 3, tradeUnit: { amount: 1, unit: "kg" } }
|
|
1658
|
+
]
|
|
1659
|
+
},
|
|
1660
|
+
{ id: "r2", items: [] }
|
|
1661
|
+
]
|
|
1662
|
+
const weighted = expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
1663
|
+
const q = make<Row>().pipe(
|
|
1664
|
+
projectComputed(
|
|
1665
|
+
S.Struct({
|
|
1666
|
+
id: S.String,
|
|
1667
|
+
totalRaw: S.Finite,
|
|
1668
|
+
totalsByUnit: S.Array(S.Struct({ unit: S.String, total: S.Finite })),
|
|
1669
|
+
totalKg: S.Finite
|
|
1670
|
+
}),
|
|
1671
|
+
computed({
|
|
1672
|
+
totalRaw: relation<Row>("items").sumExpr(weighted, where("weight", "gte", 0)),
|
|
1673
|
+
totalsByUnit: relation<Row>("items").sumExprBy(weighted, { unit: "tradeUnit.unit" }, where("weight", "gte", 0)),
|
|
1674
|
+
totalKg: relation<Row>("items").sumExprNormalized(weighted, {
|
|
1675
|
+
unit: "tradeUnit.unit",
|
|
1676
|
+
toBase: "kg",
|
|
1677
|
+
factors: { g: 0.001 }
|
|
1678
|
+
}, where("weight", "gte", 0))
|
|
1679
|
+
})
|
|
1680
|
+
)
|
|
1681
|
+
)
|
|
1682
|
+
expect(memFilter(toFilter(q, schema))(rows)).toEqual([
|
|
1683
|
+
{
|
|
1684
|
+
id: "r1",
|
|
1685
|
+
totalRaw: 4013,
|
|
1686
|
+
totalsByUnit: [{ unit: "kg", total: 13 }, { unit: "g", total: 4000 }],
|
|
1687
|
+
totalKg: 17
|
|
1688
|
+
},
|
|
1689
|
+
{ id: "r2", totalRaw: 0, totalsByUnit: [], totalKg: 0 }
|
|
1690
|
+
])
|
|
1691
|
+
})
|
|
1692
|
+
|
|
1693
|
+
it("memFilter: computed projection combined with root where filter", () => {
|
|
1694
|
+
const q = make<ComputedBase>().pipe(
|
|
1695
|
+
where("id", "neq", "r3"),
|
|
1696
|
+
projectComputed(
|
|
1697
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
1698
|
+
computed({
|
|
1699
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
1700
|
+
})
|
|
1701
|
+
)
|
|
1702
|
+
)
|
|
1703
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1704
|
+
{ id: "r1", totalQty: 35 },
|
|
1705
|
+
{ id: "r2", totalQty: 0 }
|
|
1706
|
+
])
|
|
1707
|
+
})
|
|
1708
|
+
|
|
1709
|
+
it("memFilter: computed projection with order/limit/skip applied to base rows", () => {
|
|
1710
|
+
const q = make<ComputedBase>().pipe(
|
|
1711
|
+
order("id", "DESC"),
|
|
1712
|
+
page({ skip: 1, take: 1 }),
|
|
1713
|
+
projectComputed(
|
|
1714
|
+
S.Struct({ id: S.String, total: S.NonNegativeInt }),
|
|
1715
|
+
computed({
|
|
1716
|
+
total: relation<ComputedBase>("items").count(where("qty", "gte", 0))
|
|
1717
|
+
})
|
|
1718
|
+
)
|
|
1719
|
+
)
|
|
1720
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1721
|
+
{ id: "r2", total: 0 }
|
|
1722
|
+
])
|
|
1723
|
+
})
|
|
1724
|
+
|
|
1725
|
+
it("memFilter: computed projection - relation missing on row returns empty value", () => {
|
|
1726
|
+
const partial: ComputedBase[] = [
|
|
1727
|
+
{ id: "x1" } as ComputedBase,
|
|
1728
|
+
{ id: "x2", status: "active", items: undefined as unknown as ComputedBase["items"] }
|
|
1729
|
+
]
|
|
1730
|
+
const q = make<ComputedBase>().pipe(
|
|
1731
|
+
projectComputed(
|
|
1732
|
+
S.Struct({
|
|
1733
|
+
id: S.String,
|
|
1734
|
+
c: S.NonNegativeInt,
|
|
1735
|
+
s: S.Finite,
|
|
1736
|
+
any_: S.Boolean,
|
|
1737
|
+
every_: S.Boolean,
|
|
1738
|
+
coll: S.Array(S.String)
|
|
1739
|
+
}),
|
|
1740
|
+
computed({
|
|
1741
|
+
c: relation<ComputedBase>("items").count(where("tag", "a")),
|
|
1742
|
+
s: relation<ComputedBase>("items").sum("qty", where("tag", "a")),
|
|
1743
|
+
any_: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1744
|
+
every_: relation<ComputedBase>("items").every(where("tag", "a")),
|
|
1745
|
+
coll: relation<ComputedBase>("items").collect("note", where("tag", "a"))
|
|
1746
|
+
})
|
|
1747
|
+
)
|
|
1748
|
+
)
|
|
1749
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(partial)).toEqual([
|
|
1750
|
+
{ id: "x1", c: 0, s: 0, any_: false, every_: true, coll: [] },
|
|
1751
|
+
{ id: "x2", c: 0, s: 0, any_: false, every_: true, coll: [] }
|
|
1752
|
+
])
|
|
1753
|
+
})
|
|
1754
|
+
|
|
1755
|
+
it("memFilter: rejects extra computed keys not in projection schema", () => {
|
|
1756
|
+
const q = make<ComputedBase>().pipe(
|
|
1757
|
+
projectComputed(
|
|
1758
|
+
S.Struct({ id: S.String }),
|
|
1759
|
+
computed({
|
|
1760
|
+
bogus: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1761
|
+
})
|
|
1762
|
+
)
|
|
1763
|
+
)
|
|
1764
|
+
expect(() => toFilter(q, computedBaseSchema)).toThrowError(
|
|
1765
|
+
"Computed projection keys must exist in projection schema"
|
|
1766
|
+
)
|
|
1767
|
+
})
|
|
1768
|
+
|
|
1769
|
+
// ---------------------------------------------------------------------------
|
|
1770
|
+
// memFilter: code filter (where/and/or/scopes) execution coverage
|
|
1771
|
+
// ---------------------------------------------------------------------------
|
|
1772
|
+
|
|
1773
|
+
type CFRow = {
|
|
1774
|
+
readonly id: string
|
|
1775
|
+
readonly tag: "x" | "y" | "z"
|
|
1776
|
+
readonly qty: number
|
|
1777
|
+
readonly desc: string
|
|
1778
|
+
readonly tags: ReadonlyArray<string>
|
|
1779
|
+
readonly nested: { readonly kind: "k1" | "k2"; readonly v: number }
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
const cfRows: CFRow[] = [
|
|
1783
|
+
{ id: "1", tag: "x", qty: 10, desc: "Hello World", tags: ["red", "green"], nested: { kind: "k1", v: 1 } },
|
|
1784
|
+
{ id: "2", tag: "y", qty: 20, desc: "Goodbye", tags: ["blue"], nested: { kind: "k2", v: 5 } },
|
|
1785
|
+
{ id: "3", tag: "z", qty: 0, desc: "Hello again", tags: ["red", "blue", "green"], nested: { kind: "k1", v: 9 } },
|
|
1786
|
+
{ id: "4", tag: "y", qty: 30, desc: "World cup", tags: [], nested: { kind: "k2", v: 0 } }
|
|
1787
|
+
]
|
|
1788
|
+
|
|
1789
|
+
const runCF = (q: any) => (memFilter(toFilter(q))(cfRows) as unknown as readonly CFRow[]).map((_) => _.id)
|
|
1790
|
+
|
|
1791
|
+
it("codeFilter: where + and chain", () => {
|
|
1792
|
+
const q = make<CFRow>().pipe(
|
|
1793
|
+
where("tag", "y"),
|
|
1794
|
+
and("qty", "gt", 25)
|
|
1795
|
+
)
|
|
1796
|
+
expect(runCF(q)).toEqual(["4"])
|
|
1797
|
+
})
|
|
1798
|
+
|
|
1799
|
+
it("codeFilter: where + or chain", () => {
|
|
1800
|
+
const q = make<CFRow>().pipe(
|
|
1801
|
+
where("tag", "x"),
|
|
1802
|
+
or("tag", "z")
|
|
1803
|
+
)
|
|
1804
|
+
expect(runCF(q).sort()).toEqual(["1", "3"])
|
|
1805
|
+
})
|
|
1806
|
+
|
|
1807
|
+
it("codeFilter: nested scope precedence (a AND (b OR c))", () => {
|
|
1808
|
+
const q = make<CFRow>().pipe(
|
|
1809
|
+
where("tag", "y"),
|
|
1810
|
+
and(
|
|
1811
|
+
where("qty", "gt", 25),
|
|
1812
|
+
or("desc", "contains", "good")
|
|
1813
|
+
)
|
|
1814
|
+
)
|
|
1815
|
+
// tag=y AND (qty>25 OR desc contains "good") → row 2 (Goodbye) and row 4 (qty 30)
|
|
1816
|
+
expect(runCF(q).sort()).toEqual(["2", "4"])
|
|
1817
|
+
})
|
|
1818
|
+
|
|
1819
|
+
it("codeFilter: contains/startsWith/endsWith are case-insensitive", () => {
|
|
1820
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "contains", "WORLD"))).sort()).toEqual(["1", "4"])
|
|
1821
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "startsWith", "hello"))).sort()).toEqual(["1", "3"])
|
|
1822
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "endsWith", "AGAIN")))).toEqual(["3"])
|
|
1823
|
+
})
|
|
1824
|
+
|
|
1825
|
+
it("codeFilter: array includes / includes-any / includes-all", () => {
|
|
1826
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes", "red"))).sort()).toEqual(["1", "3"])
|
|
1827
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-any", ["blue", "green"]))).sort()).toEqual([
|
|
1828
|
+
"1",
|
|
1829
|
+
"2",
|
|
1830
|
+
"3"
|
|
1831
|
+
])
|
|
1832
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-all", ["red", "blue"])))).toEqual(["3"])
|
|
1833
|
+
})
|
|
1834
|
+
|
|
1835
|
+
it("codeFilter: in / notIn", () => {
|
|
1836
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "in", ["x", "z"]))).sort()).toEqual(["1", "3"])
|
|
1837
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "notIn", ["x", "z"]))).sort()).toEqual(["2", "4"])
|
|
1838
|
+
})
|
|
1839
|
+
|
|
1840
|
+
it("codeFilter: gt / gte / lt / lte / neq", () => {
|
|
1841
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gt", 10))).sort()).toEqual(["2", "4"])
|
|
1842
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gte", 10))).sort()).toEqual(["1", "2", "4"])
|
|
1843
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lt", 10)))).toEqual(["3"])
|
|
1844
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lte", 10))).sort()).toEqual(["1", "3"])
|
|
1845
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "neq", 0))).sort()).toEqual(["1", "2", "4"])
|
|
1846
|
+
})
|
|
1847
|
+
|
|
1848
|
+
it("codeFilter: nested path access through dot notation", () => {
|
|
1849
|
+
expect(runCF(make<CFRow>().pipe(where("nested.kind", "k1"))).sort()).toEqual(["1", "3"])
|
|
1850
|
+
expect(
|
|
1851
|
+
runCF(
|
|
1852
|
+
make<CFRow>().pipe(
|
|
1853
|
+
where("nested.kind", "k2"),
|
|
1854
|
+
and("nested.v", "gt", 0)
|
|
1855
|
+
)
|
|
1856
|
+
)
|
|
1857
|
+
)
|
|
1858
|
+
.toEqual(["2"])
|
|
1859
|
+
})
|
|
1860
|
+
|
|
1861
|
+
it("codeFilter: array length predicates", () => {
|
|
1862
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", 0)))).toEqual(["4"])
|
|
1863
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", "gte", 2))).sort()).toEqual(["1", "3"])
|
|
1864
|
+
})
|
|
1865
|
+
|
|
1866
|
+
it("codeFilter: order + skip + limit applied after filter", () => {
|
|
1867
|
+
const q = make<CFRow>().pipe(
|
|
1868
|
+
where("tag", "neq", "z"),
|
|
1869
|
+
order("qty", "DESC"),
|
|
1870
|
+
page({ skip: 1, take: 2 })
|
|
1871
|
+
)
|
|
1872
|
+
expect(runCF(q)).toEqual(["2", "1"])
|
|
1873
|
+
})
|