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