@effect-app/infra 4.0.0-beta.30 → 4.0.0-beta.300
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 +2285 -0
- package/_check.sh +1 -1
- package/dist/CUPS.d.ts +29 -10
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +35 -14
- package/dist/ClusterCosmos.d.ts +64 -0
- package/dist/ClusterCosmos.d.ts.map +1 -0
- package/dist/ClusterCosmos.js +547 -0
- package/dist/ClusterServiceBus.d.ts +67 -0
- package/dist/ClusterServiceBus.d.ts.map +1 -0
- package/dist/ClusterServiceBus.js +82 -0
- package/dist/ContextProvider.d.ts +34 -0
- package/dist/ContextProvider.d.ts.map +1 -0
- package/dist/ContextProvider.js +40 -0
- package/dist/Emailer/Sendgrid.d.ts +110 -146
- package/dist/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/Emailer/Sendgrid.js +24 -19
- package/dist/Emailer/fake.d.ts +1 -1
- package/dist/Emailer/fake.d.ts.map +1 -1
- package/dist/Emailer/fake.js +4 -4
- package/dist/MainFiberSet.d.ts +12 -8
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +23 -7
- package/dist/QueueMaker/errors.d.ts +4 -2
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/errors.js +4 -2
- package/dist/{adapters/SQL → SQL}/Model.d.ts +31 -42
- package/dist/SQL/Model.d.ts.map +1 -0
- package/dist/SQL/Model.js +469 -0
- package/dist/SQL.d.ts.map +1 -0
- package/dist/{adapters/SQL.js → SQL.js} +1 -1
- package/dist/Store/Cosmos/query.d.ts +14 -3
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +179 -41
- package/dist/Store/Cosmos.d.ts +2 -2
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +385 -254
- package/dist/Store/Disk.d.ts +4 -4
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +79 -39
- package/dist/Store/Memory.d.ts +5 -9
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +324 -67
- 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 +232 -0
- package/dist/Store/SQL/query.d.ts +49 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +527 -0
- package/dist/Store/SQL.d.ts +21 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +456 -0
- package/dist/Store/codeFilter.d.ts +4 -4
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +6 -4
- package/dist/Store/index.d.ts +6 -4
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +18 -5
- package/dist/Store/utils.d.ts +5 -2
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +9 -5
- package/dist/WorkflowEngineCosmos.d.ts +29 -0
- package/dist/WorkflowEngineCosmos.d.ts.map +1 -0
- package/dist/WorkflowEngineCosmos.js +542 -0
- package/dist/WorkflowEngineSqlite.d.ts +24 -0
- package/dist/WorkflowEngineSqlite.d.ts.map +1 -0
- package/dist/WorkflowEngineSqlite.js +550 -0
- package/dist/arbs.d.ts +1 -1
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +5 -3
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +5 -0
- package/dist/{adapters/cosmos-client.d.ts → cosmos-client.d.ts} +4 -2
- package/dist/cosmos-client.d.ts.map +1 -0
- package/dist/cosmos-client.js +11 -0
- package/dist/errorReporter.d.ts +6 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +23 -27
- package/dist/fileUtil.d.ts +1 -1
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/fileUtil.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/internal/RequestContextMiddleware.d.ts +5 -0
- package/dist/internal/RequestContextMiddleware.d.ts.map +1 -0
- package/dist/internal/RequestContextMiddleware.js +45 -0
- package/dist/internal/auth.d.ts +53 -0
- package/dist/internal/auth.d.ts.map +1 -0
- package/dist/internal/auth.js +180 -0
- package/dist/internal/events.d.ts +11 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +49 -0
- package/dist/internal/health.d.ts.map +1 -0
- package/dist/internal/health.js +5 -0
- package/dist/{api/layerUtils.d.ts → layerUtils.d.ts} +14 -6
- package/dist/layerUtils.d.ts.map +1 -0
- package/dist/layerUtils.js +17 -0
- package/dist/logger/jsonLogger.d.ts +1 -1
- package/dist/logger/jsonLogger.d.ts.map +1 -1
- package/dist/logger/jsonLogger.js +5 -3
- package/dist/logger/logFmtLogger.d.ts +1 -1
- package/dist/logger/logFmtLogger.d.ts.map +1 -1
- package/dist/logger/logFmtLogger.js +3 -3
- package/dist/logger/shared.d.ts +2 -2
- package/dist/logger/shared.d.ts.map +1 -1
- package/dist/logger/shared.js +5 -5
- package/dist/logger.d.ts +4 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/middlewares.d.ts.map +1 -0
- package/dist/{api/middlewares.js → middlewares.js} +1 -1
- package/dist/{adapters/mongo-client.d.ts → mongo-client.d.ts} +3 -2
- package/dist/mongo-client.d.ts.map +1 -0
- package/dist/mongo-client.js +15 -0
- 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 +11 -3
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +7 -12
- package/dist/{adapters/redis-client.d.ts → redis-client.d.ts} +5 -2
- package/dist/redis-client.d.ts.map +1 -0
- package/dist/redis-client.js +98 -0
- package/dist/{api/reportError.d.ts → reportError.d.ts} +1 -1
- package/dist/reportError.d.ts.map +1 -0
- package/dist/reportError.js +28 -0
- package/dist/{api/routing → routing}/middleware/RouterMiddleware.d.ts +4 -3
- package/dist/routing/middleware/RouterMiddleware.d.ts.map +1 -0
- package/dist/{api/routing → routing}/middleware/RouterMiddleware.js +1 -1
- package/dist/routing/middleware/middleware.d.ts +48 -0
- package/dist/routing/middleware/middleware.d.ts.map +1 -0
- package/dist/routing/middleware/middleware.js +152 -0
- package/dist/{api/routing → routing}/middleware.d.ts +0 -1
- package/dist/routing/middleware.d.ts.map +1 -0
- package/dist/{api/routing → routing}/middleware.js +1 -2
- package/dist/routing/schema/jwt.d.ts.map +1 -0
- package/dist/routing/schema/jwt.js +13 -0
- package/dist/routing/tsort.d.ts.map +1 -0
- package/dist/routing/tsort.js +51 -0
- package/dist/{api/routing → routing}/utils.d.ts +3 -3
- package/dist/routing/utils.d.ts.map +1 -0
- package/dist/routing/utils.js +45 -0
- package/dist/routing.d.ts +182 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +256 -0
- package/dist/test.d.ts +2 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +2 -2
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +14 -0
- package/docs/cluster-storage.md +26 -0
- package/docs/workflow-engine.md +262 -0
- package/examples/query.ts +47 -39
- package/package.json +38 -352
- package/src/CUPS.ts +53 -14
- package/src/ClusterCosmos.ts +1118 -0
- package/src/ClusterServiceBus.ts +242 -0
- package/src/{api/ContextProvider.ts → ContextProvider.ts} +20 -17
- package/src/Emailer/Sendgrid.ts +83 -60
- package/src/Emailer/fake.ts +4 -4
- package/src/MainFiberSet.ts +32 -13
- package/src/QueueMaker/errors.ts +3 -1
- package/src/{adapters/SQL → SQL}/Model.ts +42 -41
- package/src/SQL.ts +1 -0
- package/src/Store/Cosmos/query.ts +218 -54
- package/src/Store/Cosmos.ts +529 -365
- package/src/Store/Disk.ts +112 -70
- package/src/Store/Memory.ts +365 -99
- package/src/Store/SQL/Pg.ts +363 -0
- package/src/Store/SQL/query.ts +603 -0
- package/src/Store/SQL.ts +746 -0
- package/src/Store/codeFilter.ts +8 -6
- package/src/Store/index.ts +25 -10
- package/src/Store/utils.ts +32 -25
- package/src/WorkflowEngineCosmos.ts +749 -0
- package/src/WorkflowEngineSqlite.ts +813 -0
- package/src/arbs.ts +5 -3
- package/src/{adapters/cosmos-client.ts → cosmos-client.ts} +5 -3
- package/src/errorReporter.ts +68 -78
- package/src/fileUtil.ts +1 -1
- package/src/index.ts +2 -1
- package/src/{api/internal → internal}/RequestContextMiddleware.ts +23 -6
- package/src/internal/auth.ts +272 -0
- package/src/{api/internal → internal}/events.ts +22 -13
- package/src/{api/layerUtils.ts → layerUtils.ts} +14 -10
- package/src/logger/jsonLogger.ts +5 -3
- package/src/logger/logFmtLogger.ts +3 -3
- package/src/logger/shared.ts +5 -4
- package/src/middlewares.ts +10 -0
- package/src/{adapters/mongo-client.ts → mongo-client.ts} +4 -2
- package/src/otel.ts +152 -0
- package/src/rateLimit.ts +34 -23
- package/src/{adapters/redis-client.ts → redis-client.ts} +7 -3
- package/src/{api/reportError.ts → reportError.ts} +3 -2
- package/src/{api/routing → routing}/middleware/RouterMiddleware.ts +5 -4
- package/src/routing/middleware/middleware.ts +196 -0
- package/src/routing/middleware.ts +4 -0
- package/src/{api/routing → routing}/schema/jwt.ts +2 -1
- package/src/{api/routing → routing}/utils.ts +2 -1
- package/src/routing.ts +823 -0
- package/src/test.ts +4 -4
- package/src/vitest.ts +1 -1
- package/test/auth.test.ts +101 -0
- package/test/cluster-cosmos.test.ts +612 -0
- package/test/cluster-servicebus.test.ts +180 -0
- package/test/cluster-sqlite.test.ts +205 -0
- package/test/contextProvider.test.ts +15 -12
- package/test/controller.test.ts +28 -32
- package/test/cosmos-query.test.ts +228 -0
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/cluster-cosmos.test.d.ts.map +1 -0
- package/test/dist/cluster-servicebus.test.d.ts.map +1 -0
- package/test/dist/cluster-sqlite.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/cosmos-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +29 -11
- 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-context-map-streaming.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/dist/workflow-engine-cosmos.test.d.ts.map +1 -0
- package/test/dist/workflow-engine-sqlite.test.d.ts.map +1 -0
- package/test/fixtures.ts +16 -9
- package/test/layerUtils.test.ts +2 -2
- package/test/query.test.ts +1007 -41
- package/test/rawQuery.test.ts +340 -22
- package/test/repository-ext.test.ts +178 -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-context-map-streaming.test.ts +262 -0
- package/test/rpc-e2e-invalidation.test.ts +418 -0
- package/test/rpc-multi-middleware.test.ts +85 -10
- package/test/rpc-stream-fullstack.test.ts +304 -0
- package/test/sql-store.test.ts +1711 -0
- package/test/validateSample.test.ts +26 -14
- package/test/workflow-engine-cosmos.test.ts +345 -0
- package/test/workflow-engine-sqlite.test.ts +297 -0
- package/tsconfig.examples.json +1 -1
- package/tsconfig.json +3 -1
- package/tsconfig.src.json +35 -35
- package/tsconfig.test.json +2 -2
- package/dist/Emailer/service.d.ts +0 -55
- package/dist/Emailer/service.d.ts.map +0 -1
- package/dist/Emailer/service.js +0 -6
- package/dist/Emailer.d.ts +0 -2
- package/dist/Emailer.d.ts.map +0 -1
- package/dist/Emailer.js +0 -2
- package/dist/Model/Repository/ext.d.ts +0 -41
- package/dist/Model/Repository/ext.d.ts.map +0 -1
- package/dist/Model/Repository/ext.js +0 -65
- package/dist/Model/Repository/internal/internal.d.ts +0 -59
- package/dist/Model/Repository/internal/internal.d.ts.map +0 -1
- package/dist/Model/Repository/internal/internal.js +0 -316
- package/dist/Model/Repository/legacy.d.ts +0 -19
- package/dist/Model/Repository/legacy.d.ts.map +0 -1
- package/dist/Model/Repository/legacy.js +0 -2
- package/dist/Model/Repository/makeRepo.d.ts +0 -49
- package/dist/Model/Repository/makeRepo.d.ts.map +0 -1
- package/dist/Model/Repository/makeRepo.js +0 -24
- package/dist/Model/Repository/service.d.ts +0 -89
- package/dist/Model/Repository/service.d.ts.map +0 -1
- package/dist/Model/Repository/service.js +0 -2
- package/dist/Model/Repository/validation.d.ts +0 -42
- package/dist/Model/Repository/validation.d.ts.map +0 -1
- package/dist/Model/Repository/validation.js +0 -32
- package/dist/Model/Repository.d.ts +0 -6
- package/dist/Model/Repository.d.ts.map +0 -1
- package/dist/Model/Repository.js +0 -6
- package/dist/Model/dsl.d.ts +0 -32
- package/dist/Model/dsl.d.ts.map +0 -1
- package/dist/Model/dsl.js +0 -44
- package/dist/Model/filter/filterApi.d.ts +0 -30
- package/dist/Model/filter/filterApi.d.ts.map +0 -1
- package/dist/Model/filter/filterApi.js +0 -2
- package/dist/Model/filter/types/errors.d.ts +0 -29
- package/dist/Model/filter/types/errors.d.ts.map +0 -1
- package/dist/Model/filter/types/errors.js +0 -2
- package/dist/Model/filter/types/fields.d.ts +0 -15
- package/dist/Model/filter/types/fields.d.ts.map +0 -1
- package/dist/Model/filter/types/fields.js +0 -2
- package/dist/Model/filter/types/path/common.d.ts +0 -316
- package/dist/Model/filter/types/path/common.d.ts.map +0 -1
- package/dist/Model/filter/types/path/common.js +0 -2
- package/dist/Model/filter/types/path/eager.d.ts +0 -95
- package/dist/Model/filter/types/path/eager.d.ts.map +0 -1
- package/dist/Model/filter/types/path/eager.js +0 -31
- package/dist/Model/filter/types/path/index.d.ts +0 -4
- package/dist/Model/filter/types/path/index.d.ts.map +0 -1
- package/dist/Model/filter/types/path/index.js +0 -3
- package/dist/Model/filter/types/utils.d.ts +0 -79
- package/dist/Model/filter/types/utils.d.ts.map +0 -1
- package/dist/Model/filter/types/utils.js +0 -2
- package/dist/Model/filter/types/validator.d.ts +0 -30
- package/dist/Model/filter/types/validator.d.ts.map +0 -1
- package/dist/Model/filter/types/validator.js +0 -2
- package/dist/Model/filter/types.d.ts +0 -5
- package/dist/Model/filter/types.d.ts.map +0 -1
- package/dist/Model/filter/types.js +0 -7
- package/dist/Model/query/dsl.d.ts +0 -248
- package/dist/Model/query/dsl.d.ts.map +0 -1
- package/dist/Model/query/dsl.js +0 -104
- package/dist/Model/query/new-kid-interpreter.d.ts +0 -28
- package/dist/Model/query/new-kid-interpreter.d.ts.map +0 -1
- package/dist/Model/query/new-kid-interpreter.js +0 -165
- package/dist/Model/query.d.ts +0 -15
- package/dist/Model/query.d.ts.map +0 -1
- package/dist/Model/query.js +0 -3
- package/dist/Model.d.ts +0 -4
- package/dist/Model.d.ts.map +0 -1
- package/dist/Model.js +0 -4
- 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/dist/QueueMaker/SQLQueue.d.ts +0 -16
- package/dist/QueueMaker/SQLQueue.d.ts.map +0 -1
- package/dist/QueueMaker/SQLQueue.js +0 -130
- package/dist/QueueMaker/memQueue.d.ts +0 -11
- package/dist/QueueMaker/memQueue.d.ts.map +0 -1
- package/dist/QueueMaker/memQueue.js +0 -76
- package/dist/QueueMaker/sbqueue.d.ts +0 -12
- package/dist/QueueMaker/sbqueue.d.ts.map +0 -1
- package/dist/QueueMaker/sbqueue.js +0 -91
- package/dist/QueueMaker/service.d.ts +0 -11
- package/dist/QueueMaker/service.d.ts.map +0 -1
- package/dist/QueueMaker/service.js +0 -4
- package/dist/RequestContext.d.ts +0 -63
- package/dist/RequestContext.d.ts.map +0 -1
- package/dist/RequestContext.js +0 -49
- package/dist/RequestFiberSet.d.ts +0 -36
- package/dist/RequestFiberSet.d.ts.map +0 -1
- package/dist/RequestFiberSet.js +0 -78
- package/dist/Store/ContextMapContainer.d.ts +0 -14
- package/dist/Store/ContextMapContainer.d.ts.map +0 -1
- package/dist/Store/ContextMapContainer.js +0 -16
- package/dist/Store/service.d.ts +0 -108
- package/dist/Store/service.d.ts.map +0 -1
- package/dist/Store/service.js +0 -71
- package/dist/Store.d.ts +0 -2
- package/dist/Store.d.ts.map +0 -1
- package/dist/Store.js +0 -2
- package/dist/adapters/SQL/Model.d.ts.map +0 -1
- package/dist/adapters/SQL/Model.js +0 -478
- package/dist/adapters/SQL.d.ts.map +0 -1
- package/dist/adapters/ServiceBus.d.ts +0 -58
- package/dist/adapters/ServiceBus.d.ts.map +0 -1
- package/dist/adapters/ServiceBus.js +0 -99
- package/dist/adapters/cosmos-client.d.ts.map +0 -1
- package/dist/adapters/cosmos-client.js +0 -9
- package/dist/adapters/index.d.ts +0 -2
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/index.js +0 -2
- package/dist/adapters/logger.d.ts +0 -9
- package/dist/adapters/logger.d.ts.map +0 -1
- package/dist/adapters/logger.js +0 -3
- package/dist/adapters/memQueue.d.ts +0 -13
- package/dist/adapters/memQueue.d.ts.map +0 -1
- package/dist/adapters/memQueue.js +0 -20
- package/dist/adapters/mongo-client.d.ts.map +0 -1
- package/dist/adapters/mongo-client.js +0 -13
- package/dist/adapters/redis-client.d.ts.map +0 -1
- package/dist/adapters/redis-client.js +0 -94
- package/dist/api/ContextProvider.d.ts +0 -31
- package/dist/api/ContextProvider.d.ts.map +0 -1
- package/dist/api/ContextProvider.js +0 -38
- package/dist/api/codec.d.ts.map +0 -1
- package/dist/api/codec.js +0 -5
- package/dist/api/internal/RequestContextMiddleware.d.ts +0 -5
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +0 -1
- package/dist/api/internal/RequestContextMiddleware.js +0 -35
- package/dist/api/internal/auth.d.ts +0 -15
- package/dist/api/internal/auth.d.ts.map +0 -1
- package/dist/api/internal/auth.js +0 -47
- package/dist/api/internal/events.d.ts +0 -9
- package/dist/api/internal/events.d.ts.map +0 -1
- package/dist/api/internal/events.js +0 -42
- package/dist/api/internal/health.d.ts.map +0 -1
- package/dist/api/internal/health.js +0 -5
- package/dist/api/layerUtils.d.ts.map +0 -1
- package/dist/api/layerUtils.js +0 -16
- package/dist/api/middlewares.d.ts.map +0 -1
- package/dist/api/reportError.d.ts.map +0 -1
- package/dist/api/reportError.js +0 -27
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +0 -1
- package/dist/api/routing/middleware/middleware.d.ts +0 -9
- package/dist/api/routing/middleware/middleware.d.ts.map +0 -1
- package/dist/api/routing/middleware/middleware.js +0 -92
- package/dist/api/routing/middleware.d.ts.map +0 -1
- package/dist/api/routing/schema/jwt.d.ts.map +0 -1
- package/dist/api/routing/schema/jwt.js +0 -12
- package/dist/api/routing/tsort.d.ts.map +0 -1
- package/dist/api/routing/tsort.js +0 -51
- package/dist/api/routing/utils.d.ts.map +0 -1
- package/dist/api/routing/utils.js +0 -44
- package/dist/api/routing.d.ts +0 -138
- package/dist/api/routing.d.ts.map +0 -1
- package/dist/api/routing.js +0 -166
- package/dist/api/setupRequest.d.ts +0 -12
- package/dist/api/setupRequest.d.ts.map +0 -1
- package/dist/api/setupRequest.js +0 -44
- package/dist/api/util.d.ts.map +0 -1
- package/dist/api/util.js +0 -14
- package/eslint.config.mjs +0 -24
- package/src/Emailer/service.ts +0 -52
- package/src/Emailer.ts +0 -1
- package/src/Model/Repository/ext.ts +0 -283
- package/src/Model/Repository/internal/internal.ts +0 -577
- package/src/Model/Repository/legacy.ts +0 -27
- package/src/Model/Repository/makeRepo.ts +0 -139
- package/src/Model/Repository/service.ts +0 -627
- package/src/Model/Repository/validation.ts +0 -31
- package/src/Model/Repository.ts +0 -5
- package/src/Model/dsl.ts +0 -128
- package/src/Model/filter/filterApi.ts +0 -60
- package/src/Model/filter/types/errors.ts +0 -47
- package/src/Model/filter/types/fields.ts +0 -50
- package/src/Model/filter/types/path/common.ts +0 -404
- package/src/Model/filter/types/path/eager.ts +0 -298
- package/src/Model/filter/types/path/index.ts +0 -4
- package/src/Model/filter/types/utils.ts +0 -128
- package/src/Model/filter/types/validator.ts +0 -46
- package/src/Model/filter/types.ts +0 -6
- package/src/Model/query/dsl.ts +0 -2110
- package/src/Model/query/new-kid-interpreter.ts +0 -210
- package/src/Model/query.ts +0 -13
- package/src/Model.ts +0 -3
- package/src/Operations.ts +0 -235
- package/src/OperationsRepo.ts +0 -16
- package/src/QueueMaker/SQLQueue.ts +0 -189
- package/src/QueueMaker/memQueue.ts +0 -130
- package/src/QueueMaker/sbqueue.ts +0 -137
- package/src/QueueMaker/service.ts +0 -17
- package/src/RequestContext.ts +0 -63
- package/src/RequestFiberSet.ts +0 -105
- package/src/Store/ContextMapContainer.ts +0 -20
- package/src/Store/index.test.ts.bak +0 -112
- package/src/Store/service.ts +0 -184
- package/src/Store.ts +0 -1
- package/src/adapters/SQL.ts +0 -1
- package/src/adapters/ServiceBus.ts +0 -209
- package/src/adapters/index.ts +0 -0
- package/src/adapters/logger.ts +0 -3
- package/src/adapters/memQueue.ts +0 -21
- package/src/api/internal/auth.ts +0 -68
- package/src/api/middlewares.ts +0 -10
- package/src/api/routing/middleware/middleware.ts +0 -128
- package/src/api/routing/middleware.ts +0 -6
- package/src/api/routing.ts +0 -598
- package/src/api/setupRequest.ts +0 -84
- package/tsconfig.json.bak +0 -12
- /package/dist/{adapters/SQL.d.ts → SQL.d.ts} +0 -0
- /package/dist/{api/codec.d.ts → codec.d.ts} +0 -0
- /package/dist/{api/internal → internal}/health.d.ts +0 -0
- /package/dist/{api/middlewares.d.ts → middlewares.d.ts} +0 -0
- /package/dist/{api/routing → routing}/schema/jwt.d.ts +0 -0
- /package/dist/{api/routing → routing}/tsort.d.ts +0 -0
- /package/dist/{api/util.d.ts → util.d.ts} +0 -0
- /package/src/{api/codec.ts → codec.ts} +0 -0
- /package/src/{api/internal → internal}/health.ts +0 -0
- /package/src/{api/routing → routing}/tsort.ts +0 -0
- /package/src/{api/util.ts → util.ts} +0 -0
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 { and, computed, count, expr, make, one, or, order, page, project, projectComputed, type QueryEnd, type QueryProjection, type QueryWhere, relation, toFilter, where } from "effect-app/Model/query"
|
|
8
|
+
import { makeRepo } from "effect-app/Model/Repository"
|
|
9
|
+
import { RepositoryRegistryLive } from "effect-app/Model/Repository/Registry"
|
|
10
|
+
import * as Option from "effect-app/Option"
|
|
11
|
+
import * as S from "effect-app/Schema"
|
|
12
|
+
import { setupRequestContextFromCurrent } from "effect-app/setupRequest"
|
|
13
|
+
import { flow, pipe } from "effect/Function"
|
|
14
|
+
import * as SchemaTransformation from "effect/SchemaTransformation"
|
|
15
|
+
import * as Struct from "effect/Struct"
|
|
5
16
|
import { inspect } from "util"
|
|
6
17
|
import { expect, expectTypeOf, it } from "vitest"
|
|
7
|
-
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 { makeRepo } from "../src/Model/Repository.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
|
|
|
@@ -162,6 +172,7 @@ it("works with repo", () =>
|
|
|
162
172
|
.pipe(
|
|
163
173
|
Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
|
|
164
174
|
setupRequestContextFromCurrent(),
|
|
175
|
+
Effect.scoped,
|
|
165
176
|
Effect.runPromise
|
|
166
177
|
))
|
|
167
178
|
|
|
@@ -233,18 +244,19 @@ it("collect", () =>
|
|
|
233
244
|
.pipe(
|
|
234
245
|
Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
|
|
235
246
|
setupRequestContextFromCurrent(),
|
|
247
|
+
Effect.scoped,
|
|
236
248
|
Effect.runPromise
|
|
237
249
|
))
|
|
238
250
|
|
|
239
|
-
class Person extends S.
|
|
251
|
+
class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
|
|
240
252
|
id: S.String,
|
|
241
253
|
surname: S.String
|
|
242
254
|
}) {}
|
|
243
|
-
class Animal extends S.
|
|
255
|
+
class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
|
|
244
256
|
id: S.String,
|
|
245
257
|
surname: S.String
|
|
246
258
|
}) {}
|
|
247
|
-
class Test extends S.
|
|
259
|
+
class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
|
|
248
260
|
id: S.String
|
|
249
261
|
}) {}
|
|
250
262
|
|
|
@@ -279,7 +291,7 @@ it(
|
|
|
279
291
|
expect(result).toEqual([])
|
|
280
292
|
expect(result2).toEqual([])
|
|
281
293
|
})
|
|
282
|
-
.pipe(Effect.provide(
|
|
294
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
283
295
|
)
|
|
284
296
|
|
|
285
297
|
it(
|
|
@@ -377,7 +389,6 @@ it(
|
|
|
377
389
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
378
390
|
const query5 = make<Union>().pipe(
|
|
379
391
|
where("id", "bla"),
|
|
380
|
-
// @ts-expect-error cannot project over fields that are not in common between the union members (you must refine the union first)
|
|
381
392
|
project(S.Struct({ id: S.String, a: S.Unknown }))
|
|
382
393
|
)
|
|
383
394
|
console.log(query5)
|
|
@@ -465,7 +476,7 @@ it(
|
|
|
465
476
|
|
|
466
477
|
expect([]).toEqual([])
|
|
467
478
|
})
|
|
468
|
-
.pipe(Effect.provide(
|
|
479
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
469
480
|
)
|
|
470
481
|
|
|
471
482
|
it(
|
|
@@ -508,7 +519,7 @@ it(
|
|
|
508
519
|
|
|
509
520
|
expect([]).toEqual([])
|
|
510
521
|
})
|
|
511
|
-
.pipe(Effect.provide(
|
|
522
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
512
523
|
)
|
|
513
524
|
|
|
514
525
|
it(
|
|
@@ -519,8 +530,8 @@ it(
|
|
|
519
530
|
const schema = S.Struct({
|
|
520
531
|
id: S.String,
|
|
521
532
|
createdAt: S.Date.pipe(
|
|
522
|
-
S.withDecodingDefault(() => new Date().toISOString()),
|
|
523
|
-
S.withConstructorDefault(() =>
|
|
533
|
+
S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
|
|
534
|
+
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
524
535
|
)
|
|
525
536
|
})
|
|
526
537
|
const repo = yield* makeRepo(
|
|
@@ -532,8 +543,8 @@ it(
|
|
|
532
543
|
const outputSchema = S.Struct({
|
|
533
544
|
id: S.Literal("123"),
|
|
534
545
|
createdAt: S.Date.pipe(
|
|
535
|
-
S.withDecodingDefault(() => new Date().toISOString()),
|
|
536
|
-
S.withConstructorDefault(() =>
|
|
546
|
+
S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
|
|
547
|
+
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
537
548
|
)
|
|
538
549
|
})
|
|
539
550
|
|
|
@@ -541,9 +552,347 @@ it(
|
|
|
541
552
|
|
|
542
553
|
expect(result).toEqual([])
|
|
543
554
|
})
|
|
544
|
-
.pipe(Effect.provide(
|
|
555
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
545
556
|
)
|
|
546
557
|
|
|
558
|
+
it(
|
|
559
|
+
"project with encodeKeys in projection maps encoded keys",
|
|
560
|
+
() =>
|
|
561
|
+
Effect
|
|
562
|
+
.gen(function*() {
|
|
563
|
+
const schema = S.Struct({
|
|
564
|
+
id: S.String,
|
|
565
|
+
a: S.Number
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
const repo = yield* makeRepo(
|
|
569
|
+
"test",
|
|
570
|
+
schema,
|
|
571
|
+
{
|
|
572
|
+
makeInitial: Effect.sync(() => [{ id: "1", a: 1 }])
|
|
573
|
+
}
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
const outputSchema = S.Struct({ b: S.Number }).pipe(S.encodeKeys({ b: "a" }))
|
|
577
|
+
|
|
578
|
+
const result = yield* repo.query(project(outputSchema))
|
|
579
|
+
|
|
580
|
+
expect(result).toStrictEqual([{ b: 1 }])
|
|
581
|
+
})
|
|
582
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
it("projectComputed sets computed IR and forces project mode", () => {
|
|
586
|
+
const baseSchema = S.Struct({
|
|
587
|
+
id: S.String,
|
|
588
|
+
items: S.Array(S.Struct({
|
|
589
|
+
state: S.Struct({
|
|
590
|
+
_tag: S.String
|
|
591
|
+
})
|
|
592
|
+
}))
|
|
593
|
+
})
|
|
594
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
595
|
+
projectComputed(
|
|
596
|
+
S.Struct({
|
|
597
|
+
pickedCount: S.NonNegativeInt
|
|
598
|
+
}),
|
|
599
|
+
computed({
|
|
600
|
+
pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count(where("state._tag", "Picked"))
|
|
601
|
+
})
|
|
602
|
+
)
|
|
603
|
+
)
|
|
604
|
+
const interpreted = toFilter(query, baseSchema)
|
|
605
|
+
expect(interpreted.mode).toBe("project")
|
|
606
|
+
expect(interpreted.select).toEqual([
|
|
607
|
+
{
|
|
608
|
+
key: "pickedCount",
|
|
609
|
+
computed: {
|
|
610
|
+
_tag: "relation-count",
|
|
611
|
+
path: "items",
|
|
612
|
+
filter: [{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }]
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
])
|
|
616
|
+
expect(interpreted.computed?.["pickedCount"]?._tag).toBe("relation-count")
|
|
617
|
+
expect(interpreted.computed?.["pickedCount"]?.path).toBe("items")
|
|
618
|
+
expect((interpreted.computed?.["pickedCount"] as { filter: unknown } | undefined)?.filter).toEqual([
|
|
619
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
620
|
+
])
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
it("projectComputed validates extra computed keys", () => {
|
|
624
|
+
const baseSchema = S.Struct({
|
|
625
|
+
id: S.String,
|
|
626
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
627
|
+
})
|
|
628
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
629
|
+
projectComputed(
|
|
630
|
+
S.Struct({ id: S.String }),
|
|
631
|
+
// @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
|
|
632
|
+
computed({
|
|
633
|
+
pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count()
|
|
634
|
+
})
|
|
635
|
+
)
|
|
636
|
+
)
|
|
637
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Computed projection keys must exist in projection schema")
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it("projectComputed constrains projection schema to encoded repo fields and computed outputs", () => {
|
|
641
|
+
const baseSchema = S.Struct({
|
|
642
|
+
id: S.String,
|
|
643
|
+
name: S.String,
|
|
644
|
+
items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
|
|
645
|
+
})
|
|
646
|
+
type Encoded = S.Codec.Encoded<typeof baseSchema>
|
|
647
|
+
|
|
648
|
+
make<Encoded>().pipe(
|
|
649
|
+
projectComputed(
|
|
650
|
+
S.Struct({
|
|
651
|
+
id: S.String,
|
|
652
|
+
itemCount: S.Number,
|
|
653
|
+
hasItems: S.Boolean,
|
|
654
|
+
articleIds: S.Array(S.String)
|
|
655
|
+
}),
|
|
656
|
+
({ relation }) => ({
|
|
657
|
+
itemCount: relation("items").count(),
|
|
658
|
+
hasItems: relation("items").any(),
|
|
659
|
+
articleIds: relation("items").collect("articleId")
|
|
660
|
+
})
|
|
661
|
+
)
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
make<Encoded>().pipe(
|
|
665
|
+
projectComputed(S.Struct({ missingField: S.String }), computed({}))
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
make<Encoded>().pipe(
|
|
669
|
+
projectComputed(
|
|
670
|
+
S.Struct({ name: S.Number }),
|
|
671
|
+
computed({})
|
|
672
|
+
)
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
make<Encoded>().pipe(
|
|
676
|
+
projectComputed(
|
|
677
|
+
S.Struct({ itemCount: S.String }),
|
|
678
|
+
computed({ itemCount: relation<Encoded>("items").count() })
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
make<Encoded>().pipe(
|
|
683
|
+
projectComputed(
|
|
684
|
+
S.Struct({ itemCount: S.Number }),
|
|
685
|
+
// @ts-expect-error extra computed keys must exist in the projection schema
|
|
686
|
+
computed({
|
|
687
|
+
itemCount: relation<Encoded>("items").count(),
|
|
688
|
+
extraCount: relation<Encoded>("items").count()
|
|
689
|
+
})
|
|
690
|
+
)
|
|
691
|
+
)
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
it("projectComputed supports union projection schemas", () => {
|
|
695
|
+
const baseSchema = S.Union([
|
|
696
|
+
S.Struct({
|
|
697
|
+
_tag: S.Literal("open"),
|
|
698
|
+
id: S.String,
|
|
699
|
+
items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
|
|
700
|
+
}),
|
|
701
|
+
S.Struct({
|
|
702
|
+
_tag: S.Literal("closed"),
|
|
703
|
+
id: S.String,
|
|
704
|
+
closedAt: S.String
|
|
705
|
+
})
|
|
706
|
+
])
|
|
707
|
+
type Encoded = S.Codec.Encoded<typeof baseSchema>
|
|
708
|
+
const query = make<Encoded>().pipe(
|
|
709
|
+
projectComputed(
|
|
710
|
+
S.Union([
|
|
711
|
+
S.Struct({
|
|
712
|
+
_tag: S.Literal("open"),
|
|
713
|
+
id: S.String,
|
|
714
|
+
articleCount: S.Number,
|
|
715
|
+
weight: S.Number,
|
|
716
|
+
articleIds: S.Array(S.String)
|
|
717
|
+
}),
|
|
718
|
+
S.Struct({
|
|
719
|
+
_tag: S.Literal("closed"),
|
|
720
|
+
id: S.String,
|
|
721
|
+
closedAt: S.String
|
|
722
|
+
})
|
|
723
|
+
]),
|
|
724
|
+
computed({
|
|
725
|
+
articleCount: relation<Encoded>("items").count(),
|
|
726
|
+
weight: relation<Encoded>("items").sum("weight"),
|
|
727
|
+
articleIds: relation<Encoded>("items").collect("articleId")
|
|
728
|
+
})
|
|
729
|
+
)
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
const interpreted = toFilter(query, baseSchema)
|
|
733
|
+
expect(interpreted.computed && Object.keys(interpreted.computed).toSorted()).toEqual([
|
|
734
|
+
"articleCount",
|
|
735
|
+
"articleIds",
|
|
736
|
+
"weight"
|
|
737
|
+
])
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
it("projection schema with computed fields fails without computed map", () => {
|
|
741
|
+
const baseSchema = S.Struct({
|
|
742
|
+
id: S.String,
|
|
743
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
744
|
+
})
|
|
745
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
746
|
+
projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
|
|
747
|
+
)
|
|
748
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
it("projectComputed.every emits relation-every IR", () => {
|
|
752
|
+
const baseSchema = S.Struct({
|
|
753
|
+
id: S.String,
|
|
754
|
+
items: S.Array(S.Struct({ state: S.Struct({ _tag: S.String }) }))
|
|
755
|
+
})
|
|
756
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
757
|
+
projectComputed(
|
|
758
|
+
S.Struct({ allPicked: S.Boolean }),
|
|
759
|
+
computed({
|
|
760
|
+
allPicked: relation<S.Codec.Encoded<typeof baseSchema>>("items").every(where("state._tag", "Picked"))
|
|
761
|
+
})
|
|
762
|
+
)
|
|
763
|
+
)
|
|
764
|
+
const interpreted = toFilter(query, baseSchema)
|
|
765
|
+
expect(interpreted.computed?.["allPicked"]?._tag).toBe("relation-every")
|
|
766
|
+
expect(interpreted.computed?.["allPicked"]?.path).toBe("items")
|
|
767
|
+
expect((interpreted.computed?.["allPicked"] as { filter: unknown } | undefined)?.filter).toEqual([
|
|
768
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
769
|
+
])
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
it("projectComputed.distinctCount emits relation-distinct-count IR with field", () => {
|
|
773
|
+
const baseSchema = S.Struct({
|
|
774
|
+
id: S.String,
|
|
775
|
+
items: S.Array(S.Struct({ rowId: S.String, state: S.Struct({ _tag: S.String }) }))
|
|
776
|
+
})
|
|
777
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
778
|
+
projectComputed(
|
|
779
|
+
S.Struct({ positionCount: S.NonNegativeInt }),
|
|
780
|
+
computed({
|
|
781
|
+
positionCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").distinctCount(
|
|
782
|
+
"rowId",
|
|
783
|
+
where("state._tag", "neq", "cancelled")
|
|
784
|
+
)
|
|
785
|
+
})
|
|
786
|
+
)
|
|
787
|
+
)
|
|
788
|
+
const interpreted = toFilter(query, baseSchema)
|
|
789
|
+
const ir = interpreted.computed?.["positionCount"]
|
|
790
|
+
expect(ir?._tag).toBe("relation-distinct-count")
|
|
791
|
+
expect((ir as { field: string } | undefined)?.field).toBe("rowId")
|
|
792
|
+
expect((ir as { filter: unknown } | undefined)?.filter).toEqual([
|
|
793
|
+
{ t: "where", path: "items.-1.state._tag", op: "neq", value: "cancelled" }
|
|
794
|
+
])
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
it("projectComputed.sum emits relation-sum IR with field", () => {
|
|
798
|
+
const baseSchema = S.Struct({
|
|
799
|
+
id: S.String,
|
|
800
|
+
items: S.Array(S.Struct({ weight: S.Number }))
|
|
801
|
+
})
|
|
802
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
803
|
+
projectComputed(
|
|
804
|
+
S.Struct({ totalWeight: S.Number }),
|
|
805
|
+
computed({ totalWeight: relation<S.Codec.Encoded<typeof baseSchema>>("items").sum("weight") })
|
|
806
|
+
)
|
|
807
|
+
)
|
|
808
|
+
const interpreted = toFilter(query, baseSchema)
|
|
809
|
+
const ir = interpreted.computed?.["totalWeight"]
|
|
810
|
+
expect(ir?._tag).toBe("relation-sum")
|
|
811
|
+
expect((ir as { field: string } | undefined)?.field).toBe("weight")
|
|
812
|
+
expect((ir as { filter: unknown } | undefined)?.filter).toEqual([])
|
|
813
|
+
})
|
|
814
|
+
|
|
815
|
+
it("projectComputed.collect / collectDistinct emit relation-collect IR", () => {
|
|
816
|
+
const baseSchema = S.Struct({
|
|
817
|
+
id: S.String,
|
|
818
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
819
|
+
})
|
|
820
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
821
|
+
projectComputed(
|
|
822
|
+
S.Struct({
|
|
823
|
+
all: S.Array(S.String),
|
|
824
|
+
distinct: S.Array(S.String)
|
|
825
|
+
}),
|
|
826
|
+
computed({
|
|
827
|
+
all: relation<S.Codec.Encoded<typeof baseSchema>>("items").collect("articleId"),
|
|
828
|
+
distinct: relation<S.Codec.Encoded<typeof baseSchema>>("items").collectDistinct("articleId")
|
|
829
|
+
})
|
|
830
|
+
)
|
|
831
|
+
)
|
|
832
|
+
const interpreted = toFilter(query, baseSchema)
|
|
833
|
+
const all = interpreted.computed?.["all"]
|
|
834
|
+
const distinct = interpreted.computed?.["distinct"]
|
|
835
|
+
expect(all?._tag).toBe("relation-collect")
|
|
836
|
+
expect((all as { distinct: boolean } | undefined)?.distinct).toBe(false)
|
|
837
|
+
expect(distinct?._tag).toBe("relation-collect")
|
|
838
|
+
expect((distinct as { distinct: boolean } | undefined)?.distinct).toBe(true)
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
it("projectComputed.sumExpr emits relation-sum-expr IR", () => {
|
|
842
|
+
const baseSchema = S.Struct({
|
|
843
|
+
id: S.String,
|
|
844
|
+
items: S.Array(S.Struct({
|
|
845
|
+
weight: S.Number,
|
|
846
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
847
|
+
}))
|
|
848
|
+
})
|
|
849
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
850
|
+
projectComputed(
|
|
851
|
+
S.Struct({ total: S.Number }),
|
|
852
|
+
computed({
|
|
853
|
+
total: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExpr(
|
|
854
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
855
|
+
)
|
|
856
|
+
})
|
|
857
|
+
)
|
|
858
|
+
)
|
|
859
|
+
const interpreted = toFilter(query, baseSchema)
|
|
860
|
+
const ir = interpreted.computed?.["total"]
|
|
861
|
+
expect(ir?._tag).toBe("relation-sum-expr")
|
|
862
|
+
expect((ir as { expression: unknown } | undefined)?.expression).toEqual({
|
|
863
|
+
_tag: "mul",
|
|
864
|
+
left: { _tag: "field", field: "weight" },
|
|
865
|
+
right: { _tag: "field", field: "tradeUnit.amount" }
|
|
866
|
+
})
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
it("projectComputed.sumExprBy emits relation-sum-expr-by IR", () => {
|
|
870
|
+
const baseSchema = S.Struct({
|
|
871
|
+
id: S.String,
|
|
872
|
+
items: S.Array(S.Struct({
|
|
873
|
+
weight: S.Number,
|
|
874
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
875
|
+
}))
|
|
876
|
+
})
|
|
877
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
878
|
+
projectComputed(
|
|
879
|
+
S.Struct({
|
|
880
|
+
totals: S.Array(S.Struct({ unit: S.String, total: S.Number }))
|
|
881
|
+
}),
|
|
882
|
+
computed({
|
|
883
|
+
totals: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExprBy(
|
|
884
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount")),
|
|
885
|
+
{ unit: "tradeUnit.unit" }
|
|
886
|
+
)
|
|
887
|
+
})
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
const interpreted = toFilter(query, baseSchema)
|
|
891
|
+
const ir = interpreted.computed?.["totals"]
|
|
892
|
+
expect(ir?._tag).toBe("relation-sum-expr-by")
|
|
893
|
+
expect((ir as { unit: string } | undefined)?.unit).toBe("tradeUnit.unit")
|
|
894
|
+
})
|
|
895
|
+
|
|
547
896
|
it(
|
|
548
897
|
"doesn't mess when refining fields",
|
|
549
898
|
() =>
|
|
@@ -551,7 +900,7 @@ it(
|
|
|
551
900
|
.gen(function*() {
|
|
552
901
|
const schema = S.Struct({
|
|
553
902
|
id: S.String,
|
|
554
|
-
literals: S.
|
|
903
|
+
literals: S.Literals(["a", "b", "c"])
|
|
555
904
|
})
|
|
556
905
|
|
|
557
906
|
type Schema = typeof schema.Type
|
|
@@ -571,7 +920,7 @@ it(
|
|
|
571
920
|
|
|
572
921
|
expect(result).toEqual([])
|
|
573
922
|
})
|
|
574
|
-
.pipe(Effect.provide(
|
|
923
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
575
924
|
)
|
|
576
925
|
|
|
577
926
|
it(
|
|
@@ -581,7 +930,7 @@ it(
|
|
|
581
930
|
.gen(function*() {
|
|
582
931
|
const schema = S.Struct({
|
|
583
932
|
id: S.String,
|
|
584
|
-
literals: S.Union([S.
|
|
933
|
+
literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
|
|
585
934
|
})
|
|
586
935
|
|
|
587
936
|
type Schema = typeof schema.Type
|
|
@@ -615,7 +964,7 @@ it(
|
|
|
615
964
|
|
|
616
965
|
expect(result).toEqual([])
|
|
617
966
|
})
|
|
618
|
-
.pipe(Effect.provide(
|
|
967
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
619
968
|
)
|
|
620
969
|
|
|
621
970
|
it(
|
|
@@ -659,7 +1008,7 @@ it(
|
|
|
659
1008
|
|
|
660
1009
|
expect(result).toEqual([])
|
|
661
1010
|
})
|
|
662
|
-
.pipe(Effect.provide(
|
|
1011
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
663
1012
|
)
|
|
664
1013
|
|
|
665
1014
|
it("remove null from one constituent of a tagged union", () =>
|
|
@@ -672,7 +1021,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
672
1021
|
|
|
673
1022
|
class BB extends S.Class<BB>("BB")({
|
|
674
1023
|
id: S.Literal("BB"),
|
|
675
|
-
b: S.NullOr(S.
|
|
1024
|
+
b: S.NullOr(S.Finite)
|
|
676
1025
|
}) {}
|
|
677
1026
|
|
|
678
1027
|
type Union = AA | BB
|
|
@@ -708,7 +1057,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
708
1057
|
})[]
|
|
709
1058
|
>()
|
|
710
1059
|
})
|
|
711
|
-
.pipe(Effect.provide(
|
|
1060
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
712
1061
|
|
|
713
1062
|
it("refine 3", () =>
|
|
714
1063
|
Effect
|
|
@@ -746,7 +1095,7 @@ it("refine 3", () =>
|
|
|
746
1095
|
const resQuer1 = yield* repo.query(where("id", "AA"))
|
|
747
1096
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
748
1097
|
})
|
|
749
|
-
.pipe(Effect.provide(
|
|
1098
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
750
1099
|
|
|
751
1100
|
it("my test", () =>
|
|
752
1101
|
Effect
|
|
@@ -764,7 +1113,7 @@ it("my test", () =>
|
|
|
764
1113
|
)
|
|
765
1114
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
766
1115
|
})
|
|
767
|
-
.pipe(Effect.provide(
|
|
1116
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
768
1117
|
|
|
769
1118
|
it("refine inner without imposing a projection", () =>
|
|
770
1119
|
Effect
|
|
@@ -808,7 +1157,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
808
1157
|
where("union._tag", "AA"),
|
|
809
1158
|
// But if I wanna the whole Data as output ignoring the inner refinement
|
|
810
1159
|
// I wanna be able to do so
|
|
811
|
-
project(
|
|
1160
|
+
project(Data.mapFields(Struct.pick(["union"])))
|
|
812
1161
|
)
|
|
813
1162
|
|
|
814
1163
|
expectTypeOf(query2).toEqualTypeOf<
|
|
@@ -839,7 +1188,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
839
1188
|
}[]
|
|
840
1189
|
>()
|
|
841
1190
|
})
|
|
842
|
-
.pipe(Effect.provide(
|
|
1191
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
843
1192
|
|
|
844
1193
|
it("does not allow string queries on arrays", () =>
|
|
845
1194
|
Effect
|
|
@@ -874,7 +1223,7 @@ it("does not allow string queries on arrays", () =>
|
|
|
874
1223
|
expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
875
1224
|
expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
876
1225
|
})
|
|
877
|
-
.pipe(Effect.provide(
|
|
1226
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
878
1227
|
|
|
879
1228
|
it("test array.length", () =>
|
|
880
1229
|
Effect
|
|
@@ -915,7 +1264,7 @@ it("test array.length", () =>
|
|
|
915
1264
|
QueryWhere<Something, Something>
|
|
916
1265
|
>()
|
|
917
1266
|
})
|
|
918
|
-
.pipe(Effect.provide(
|
|
1267
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
919
1268
|
|
|
920
1269
|
it("distribution over union", () =>
|
|
921
1270
|
Effect
|
|
@@ -939,7 +1288,7 @@ it("distribution over union", () =>
|
|
|
939
1288
|
})[]
|
|
940
1289
|
>()
|
|
941
1290
|
})
|
|
942
|
-
.pipe(Effect.provide(
|
|
1291
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
943
1292
|
|
|
944
1293
|
it("refine nested union", () =>
|
|
945
1294
|
Effect
|
|
@@ -980,7 +1329,158 @@ it("refine nested union", () =>
|
|
|
980
1329
|
}[]
|
|
981
1330
|
>()
|
|
982
1331
|
})
|
|
983
|
-
.pipe(Effect.provide(
|
|
1332
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1333
|
+
|
|
1334
|
+
it("find with transformed id", () =>
|
|
1335
|
+
Effect
|
|
1336
|
+
.gen(function*() {
|
|
1337
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1338
|
+
|
|
1339
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1340
|
+
configuratorId: ConfiguratorId,
|
|
1341
|
+
label: S.NonEmptyString50
|
|
1342
|
+
}) {}
|
|
1343
|
+
|
|
1344
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1345
|
+
S.decodeTo(
|
|
1346
|
+
S.toType(PreconfigurationId),
|
|
1347
|
+
SchemaTransformation.transformOrFail({
|
|
1348
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1349
|
+
const values = value.split("_")
|
|
1350
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1351
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1352
|
+
values.join("_")
|
|
1353
|
+
)
|
|
1354
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1355
|
+
}),
|
|
1356
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1357
|
+
})
|
|
1358
|
+
),
|
|
1359
|
+
S.revealCodec
|
|
1360
|
+
)
|
|
1361
|
+
|
|
1362
|
+
const Preconfiguration = S.Struct({
|
|
1363
|
+
id: PreconfigurationIdFromString,
|
|
1364
|
+
name: S.String
|
|
1365
|
+
})
|
|
1366
|
+
|
|
1367
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
|
|
1368
|
+
|
|
1369
|
+
const id = new PreconfigurationId({
|
|
1370
|
+
configuratorId: S.NonEmptyString255("myConfigurator"),
|
|
1371
|
+
label: S.NonEmptyString50("myLabel")
|
|
1372
|
+
})
|
|
1373
|
+
const item = { id, name: "test preconfig" }
|
|
1374
|
+
|
|
1375
|
+
yield* repo.saveAndPublish([item])
|
|
1376
|
+
|
|
1377
|
+
const found = yield* repo.find(id)
|
|
1378
|
+
expect(Option.isSome(found)).toBe(true)
|
|
1379
|
+
expect(Option.getOrThrow(found).name).toBe("test preconfig")
|
|
1380
|
+
expect(Option.getOrThrow(found).id).toEqual(id)
|
|
1381
|
+
|
|
1382
|
+
const notFound = yield* repo.find(
|
|
1383
|
+
new PreconfigurationId({
|
|
1384
|
+
configuratorId: S.NonEmptyString255("other"),
|
|
1385
|
+
label: S.NonEmptyString50("nope")
|
|
1386
|
+
})
|
|
1387
|
+
)
|
|
1388
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1389
|
+
})
|
|
1390
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1391
|
+
|
|
1392
|
+
it("find with transformed id in tagged union", () =>
|
|
1393
|
+
Effect
|
|
1394
|
+
.gen(function*() {
|
|
1395
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1396
|
+
|
|
1397
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1398
|
+
configuratorId: ConfiguratorId,
|
|
1399
|
+
label: S.NonEmptyString50
|
|
1400
|
+
}) {}
|
|
1401
|
+
|
|
1402
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1403
|
+
S.decodeTo(
|
|
1404
|
+
S.toType(PreconfigurationId),
|
|
1405
|
+
SchemaTransformation.transformOrFail({
|
|
1406
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1407
|
+
const values = value.split("_")
|
|
1408
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1409
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1410
|
+
values.join("_")
|
|
1411
|
+
)
|
|
1412
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1413
|
+
}),
|
|
1414
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1415
|
+
})
|
|
1416
|
+
),
|
|
1417
|
+
S.revealCodec
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1420
|
+
class Draft extends S.TaggedClass<Draft>()("Draft", {
|
|
1421
|
+
id: PreconfigurationIdFromString,
|
|
1422
|
+
name: S.String
|
|
1423
|
+
}) {}
|
|
1424
|
+
|
|
1425
|
+
class Published extends S.TaggedClass<Published>()("Published", {
|
|
1426
|
+
id: PreconfigurationIdFromString,
|
|
1427
|
+
name: S.String,
|
|
1428
|
+
publishedAt: S.String
|
|
1429
|
+
}) {}
|
|
1430
|
+
|
|
1431
|
+
class Archived extends S.TaggedClass<Archived>()("Archived", {
|
|
1432
|
+
id: PreconfigurationIdFromString,
|
|
1433
|
+
name: S.String,
|
|
1434
|
+
archivedAt: S.String
|
|
1435
|
+
}) {}
|
|
1436
|
+
|
|
1437
|
+
const Preconfiguration = S.Union([Draft, Published, Archived])
|
|
1438
|
+
|
|
1439
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
|
|
1440
|
+
|
|
1441
|
+
const id1 = new PreconfigurationId({
|
|
1442
|
+
configuratorId: S.NonEmptyString255("conf1"),
|
|
1443
|
+
label: S.NonEmptyString50("draft1")
|
|
1444
|
+
})
|
|
1445
|
+
const id2 = new PreconfigurationId({
|
|
1446
|
+
configuratorId: S.NonEmptyString255("conf2"),
|
|
1447
|
+
label: S.NonEmptyString50("pub1")
|
|
1448
|
+
})
|
|
1449
|
+
const id3 = new PreconfigurationId({
|
|
1450
|
+
configuratorId: S.NonEmptyString255("conf3"),
|
|
1451
|
+
label: S.NonEmptyString50("arch1")
|
|
1452
|
+
})
|
|
1453
|
+
|
|
1454
|
+
const draft = new Draft({ id: id1, name: "my draft" })
|
|
1455
|
+
const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
|
|
1456
|
+
const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
|
|
1457
|
+
|
|
1458
|
+
yield* repo.saveAndPublish([draft, published, archived])
|
|
1459
|
+
|
|
1460
|
+
// find each by their PreconfigurationId instance
|
|
1461
|
+
const foundDraft = yield* repo.find(id1)
|
|
1462
|
+
expect(Option.isSome(foundDraft)).toBe(true)
|
|
1463
|
+
expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
|
|
1464
|
+
expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
|
|
1465
|
+
|
|
1466
|
+
const foundPublished = yield* repo.find(id2)
|
|
1467
|
+
expect(Option.isSome(foundPublished)).toBe(true)
|
|
1468
|
+
expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
|
|
1469
|
+
|
|
1470
|
+
const foundArchived = yield* repo.find(id3)
|
|
1471
|
+
expect(Option.isSome(foundArchived)).toBe(true)
|
|
1472
|
+
expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
|
|
1473
|
+
|
|
1474
|
+
// not found
|
|
1475
|
+
const notFound = yield* repo.find(
|
|
1476
|
+
new PreconfigurationId({
|
|
1477
|
+
configuratorId: S.NonEmptyString255("nope"),
|
|
1478
|
+
label: S.NonEmptyString50("nope")
|
|
1479
|
+
})
|
|
1480
|
+
)
|
|
1481
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1482
|
+
})
|
|
1483
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
984
1484
|
|
|
985
1485
|
it("refine union with nested union", () =>
|
|
986
1486
|
Effect
|
|
@@ -1095,4 +1595,470 @@ it("refine union with nested union", () =>
|
|
|
1095
1595
|
})[]
|
|
1096
1596
|
>()
|
|
1097
1597
|
})
|
|
1098
|
-
.pipe(Effect.provide(
|
|
1598
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1599
|
+
|
|
1600
|
+
// ---------------------------------------------------------------------------
|
|
1601
|
+
// memFilter: computed projection execution (in-memory) and code filter coverage
|
|
1602
|
+
// ---------------------------------------------------------------------------
|
|
1603
|
+
|
|
1604
|
+
const computedBaseSchema = S.Struct({
|
|
1605
|
+
id: S.String,
|
|
1606
|
+
status: S.Literals(["active", "archived"]),
|
|
1607
|
+
items: S.Array(S.Struct({
|
|
1608
|
+
id: S.String,
|
|
1609
|
+
tag: S.Literals(["a", "b", "c"]),
|
|
1610
|
+
qty: S.Finite,
|
|
1611
|
+
note: S.String
|
|
1612
|
+
}))
|
|
1613
|
+
})
|
|
1614
|
+
type ComputedBase = S.Codec.Encoded<typeof computedBaseSchema>
|
|
1615
|
+
|
|
1616
|
+
const computedRows: ComputedBase[] = [
|
|
1617
|
+
{
|
|
1618
|
+
id: "r1",
|
|
1619
|
+
status: "active",
|
|
1620
|
+
items: [
|
|
1621
|
+
{ id: "i1", tag: "a", qty: 10, note: "alpha" },
|
|
1622
|
+
{ id: "i2", tag: "a", qty: 20, note: "alpha" },
|
|
1623
|
+
{ id: "i3", tag: "b", qty: 5, note: "beta" }
|
|
1624
|
+
]
|
|
1625
|
+
},
|
|
1626
|
+
{ id: "r2", status: "active", items: [] },
|
|
1627
|
+
{
|
|
1628
|
+
id: "r3",
|
|
1629
|
+
status: "archived",
|
|
1630
|
+
items: [
|
|
1631
|
+
{ id: "i4", tag: "b", qty: 7, note: "gamma" },
|
|
1632
|
+
{ id: "i5", tag: "b", qty: 7, note: "gamma" },
|
|
1633
|
+
{ id: "i6", tag: "c", qty: 3, note: "delta" }
|
|
1634
|
+
]
|
|
1635
|
+
}
|
|
1636
|
+
]
|
|
1637
|
+
|
|
1638
|
+
it("memFilter: relation-count with filter", () => {
|
|
1639
|
+
const q = make<ComputedBase>().pipe(
|
|
1640
|
+
projectComputed(
|
|
1641
|
+
S.Struct({ id: S.String, aCount: S.NonNegativeInt }),
|
|
1642
|
+
computed({
|
|
1643
|
+
aCount: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1644
|
+
})
|
|
1645
|
+
)
|
|
1646
|
+
)
|
|
1647
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1648
|
+
{ id: "r1", aCount: 2 },
|
|
1649
|
+
{ id: "r2", aCount: 0 },
|
|
1650
|
+
{ id: "r3", aCount: 0 }
|
|
1651
|
+
])
|
|
1652
|
+
})
|
|
1653
|
+
|
|
1654
|
+
it("memFilter: relation-any / every with filter", () => {
|
|
1655
|
+
const q = make<ComputedBase>().pipe(
|
|
1656
|
+
projectComputed(
|
|
1657
|
+
S.Struct({
|
|
1658
|
+
id: S.String,
|
|
1659
|
+
hasA: S.Boolean,
|
|
1660
|
+
allB: S.Boolean
|
|
1661
|
+
}),
|
|
1662
|
+
computed({
|
|
1663
|
+
hasA: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1664
|
+
allB: relation<ComputedBase>("items").every(where("tag", "b"))
|
|
1665
|
+
})
|
|
1666
|
+
)
|
|
1667
|
+
)
|
|
1668
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1669
|
+
{ id: "r1", hasA: true, allB: false },
|
|
1670
|
+
// empty array: any → false, every → true (JS Array.every on [] is true)
|
|
1671
|
+
{ id: "r2", hasA: false, allB: true },
|
|
1672
|
+
{ id: "r3", hasA: false, allB: false }
|
|
1673
|
+
])
|
|
1674
|
+
})
|
|
1675
|
+
|
|
1676
|
+
it("memFilter: relation-distinct-count with filter", () => {
|
|
1677
|
+
const q = make<ComputedBase>().pipe(
|
|
1678
|
+
projectComputed(
|
|
1679
|
+
S.Struct({ id: S.String, distinctNotes: S.NonNegativeInt }),
|
|
1680
|
+
computed({
|
|
1681
|
+
distinctNotes: relation<ComputedBase>("items").distinctCount("note", where("tag", "neq", "c"))
|
|
1682
|
+
})
|
|
1683
|
+
)
|
|
1684
|
+
)
|
|
1685
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1686
|
+
{ id: "r1", distinctNotes: 2 }, // alpha, beta
|
|
1687
|
+
{ id: "r2", distinctNotes: 0 },
|
|
1688
|
+
{ id: "r3", distinctNotes: 1 } // gamma (delta filtered out)
|
|
1689
|
+
])
|
|
1690
|
+
})
|
|
1691
|
+
|
|
1692
|
+
it("memFilter: relation-sum with filter", () => {
|
|
1693
|
+
const q = make<ComputedBase>().pipe(
|
|
1694
|
+
projectComputed(
|
|
1695
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
1696
|
+
computed({
|
|
1697
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
1698
|
+
})
|
|
1699
|
+
)
|
|
1700
|
+
)
|
|
1701
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1702
|
+
{ id: "r1", totalQty: 35 },
|
|
1703
|
+
{ id: "r2", totalQty: 0 },
|
|
1704
|
+
{ id: "r3", totalQty: 14 }
|
|
1705
|
+
])
|
|
1706
|
+
})
|
|
1707
|
+
|
|
1708
|
+
it("memFilter: relation-collect / collectDistinct with filter", () => {
|
|
1709
|
+
const q = make<ComputedBase>().pipe(
|
|
1710
|
+
projectComputed(
|
|
1711
|
+
S.Struct({
|
|
1712
|
+
id: S.String,
|
|
1713
|
+
notes: S.Array(S.String),
|
|
1714
|
+
distinctNotes: S.Array(S.String)
|
|
1715
|
+
}),
|
|
1716
|
+
computed({
|
|
1717
|
+
notes: relation<ComputedBase>("items").collect("note", where("tag", "neq", "c")),
|
|
1718
|
+
distinctNotes: relation<ComputedBase>("items").collectDistinct("note", where("tag", "neq", "c"))
|
|
1719
|
+
})
|
|
1720
|
+
)
|
|
1721
|
+
)
|
|
1722
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1723
|
+
{ id: "r1", notes: ["alpha", "alpha", "beta"], distinctNotes: ["alpha", "beta"] },
|
|
1724
|
+
{ id: "r2", notes: [], distinctNotes: [] },
|
|
1725
|
+
{ id: "r3", notes: ["gamma", "gamma"], distinctNotes: ["gamma"] }
|
|
1726
|
+
])
|
|
1727
|
+
})
|
|
1728
|
+
|
|
1729
|
+
it("memFilter: computed projection with multi-statement relation filter", () => {
|
|
1730
|
+
const q = make<ComputedBase>().pipe(
|
|
1731
|
+
projectComputed(
|
|
1732
|
+
S.Struct({ id: S.String, hits: S.NonNegativeInt }),
|
|
1733
|
+
computed({
|
|
1734
|
+
hits: relation<ComputedBase>("items").count(
|
|
1735
|
+
flow(
|
|
1736
|
+
where("tag", "a"),
|
|
1737
|
+
and("qty", "gt", 10)
|
|
1738
|
+
)
|
|
1739
|
+
)
|
|
1740
|
+
})
|
|
1741
|
+
)
|
|
1742
|
+
)
|
|
1743
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1744
|
+
{ id: "r1", hits: 1 }, // only i2 (a, qty 20)
|
|
1745
|
+
{ id: "r2", hits: 0 },
|
|
1746
|
+
{ id: "r3", hits: 0 }
|
|
1747
|
+
])
|
|
1748
|
+
})
|
|
1749
|
+
|
|
1750
|
+
it("memFilter: relation-sum-expr / sum-expr-by / sum-expr-normalized", () => {
|
|
1751
|
+
const schema = S.Struct({
|
|
1752
|
+
id: S.String,
|
|
1753
|
+
items: S.Array(S.Struct({
|
|
1754
|
+
weight: S.Finite,
|
|
1755
|
+
tradeUnit: S.Struct({ amount: S.Finite, unit: S.String })
|
|
1756
|
+
}))
|
|
1757
|
+
})
|
|
1758
|
+
type Row = S.Codec.Encoded<typeof schema>
|
|
1759
|
+
const rows: Row[] = [
|
|
1760
|
+
{
|
|
1761
|
+
id: "r1",
|
|
1762
|
+
items: [
|
|
1763
|
+
{ weight: 2, tradeUnit: { amount: 5, unit: "kg" } },
|
|
1764
|
+
{ weight: 4, tradeUnit: { amount: 1000, unit: "g" } },
|
|
1765
|
+
{ weight: 3, tradeUnit: { amount: 1, unit: "kg" } }
|
|
1766
|
+
]
|
|
1767
|
+
},
|
|
1768
|
+
{ id: "r2", items: [] }
|
|
1769
|
+
]
|
|
1770
|
+
const weighted = expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
1771
|
+
const q = make<Row>().pipe(
|
|
1772
|
+
projectComputed(
|
|
1773
|
+
S.Struct({
|
|
1774
|
+
id: S.String,
|
|
1775
|
+
totalRaw: S.Finite,
|
|
1776
|
+
totalsByUnit: S.Array(S.Struct({ unit: S.String, total: S.Finite })),
|
|
1777
|
+
totalKg: S.Finite
|
|
1778
|
+
}),
|
|
1779
|
+
computed({
|
|
1780
|
+
totalRaw: relation<Row>("items").sumExpr(weighted, where("weight", "gte", 0)),
|
|
1781
|
+
totalsByUnit: relation<Row>("items").sumExprBy(weighted, { unit: "tradeUnit.unit" }, where("weight", "gte", 0)),
|
|
1782
|
+
totalKg: relation<Row>("items").sumExprNormalized(weighted, {
|
|
1783
|
+
unit: "tradeUnit.unit",
|
|
1784
|
+
toBase: "kg",
|
|
1785
|
+
factors: { g: 0.001 }
|
|
1786
|
+
}, where("weight", "gte", 0))
|
|
1787
|
+
})
|
|
1788
|
+
)
|
|
1789
|
+
)
|
|
1790
|
+
expect(memFilter(toFilter(q, schema))(rows)).toEqual([
|
|
1791
|
+
{
|
|
1792
|
+
id: "r1",
|
|
1793
|
+
totalRaw: 4013,
|
|
1794
|
+
totalsByUnit: [{ unit: "kg", total: 13 }, { unit: "g", total: 4000 }],
|
|
1795
|
+
totalKg: 17
|
|
1796
|
+
},
|
|
1797
|
+
{ id: "r2", totalRaw: 0, totalsByUnit: [], totalKg: 0 }
|
|
1798
|
+
])
|
|
1799
|
+
})
|
|
1800
|
+
|
|
1801
|
+
it("memFilter: computed projection combined with root where filter", () => {
|
|
1802
|
+
const q = make<ComputedBase>().pipe(
|
|
1803
|
+
where("id", "neq", "r3"),
|
|
1804
|
+
projectComputed(
|
|
1805
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
1806
|
+
computed({
|
|
1807
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
1808
|
+
})
|
|
1809
|
+
)
|
|
1810
|
+
)
|
|
1811
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1812
|
+
{ id: "r1", totalQty: 35 },
|
|
1813
|
+
{ id: "r2", totalQty: 0 }
|
|
1814
|
+
])
|
|
1815
|
+
})
|
|
1816
|
+
|
|
1817
|
+
it("memFilter: computed projection with order/limit/skip applied to base rows", () => {
|
|
1818
|
+
const q = make<ComputedBase>().pipe(
|
|
1819
|
+
order("id", "DESC"),
|
|
1820
|
+
page({ skip: 1, take: 1 }),
|
|
1821
|
+
projectComputed(
|
|
1822
|
+
S.Struct({ id: S.String, total: S.NonNegativeInt }),
|
|
1823
|
+
computed({
|
|
1824
|
+
total: relation<ComputedBase>("items").count(where("qty", "gte", 0))
|
|
1825
|
+
})
|
|
1826
|
+
)
|
|
1827
|
+
)
|
|
1828
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1829
|
+
{ id: "r2", total: 0 }
|
|
1830
|
+
])
|
|
1831
|
+
})
|
|
1832
|
+
|
|
1833
|
+
it("memFilter: computed projection - relation missing on row returns empty value", () => {
|
|
1834
|
+
const partial: ComputedBase[] = [
|
|
1835
|
+
{ id: "x1" } as ComputedBase,
|
|
1836
|
+
{ id: "x2", status: "active", items: undefined as unknown as ComputedBase["items"] }
|
|
1837
|
+
]
|
|
1838
|
+
const q = make<ComputedBase>().pipe(
|
|
1839
|
+
projectComputed(
|
|
1840
|
+
S.Struct({
|
|
1841
|
+
id: S.String,
|
|
1842
|
+
c: S.NonNegativeInt,
|
|
1843
|
+
s: S.Finite,
|
|
1844
|
+
any_: S.Boolean,
|
|
1845
|
+
every_: S.Boolean,
|
|
1846
|
+
coll: S.Array(S.String)
|
|
1847
|
+
}),
|
|
1848
|
+
computed({
|
|
1849
|
+
c: relation<ComputedBase>("items").count(where("tag", "a")),
|
|
1850
|
+
s: relation<ComputedBase>("items").sum("qty", where("tag", "a")),
|
|
1851
|
+
any_: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1852
|
+
every_: relation<ComputedBase>("items").every(where("tag", "a")),
|
|
1853
|
+
coll: relation<ComputedBase>("items").collect("note", where("tag", "a"))
|
|
1854
|
+
})
|
|
1855
|
+
)
|
|
1856
|
+
)
|
|
1857
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(partial)).toEqual([
|
|
1858
|
+
{ id: "x1", c: 0, s: 0, any_: false, every_: true, coll: [] },
|
|
1859
|
+
{ id: "x2", c: 0, s: 0, any_: false, every_: true, coll: [] }
|
|
1860
|
+
])
|
|
1861
|
+
})
|
|
1862
|
+
|
|
1863
|
+
it("memFilter: rejects extra computed keys not in projection schema", () => {
|
|
1864
|
+
const q = make<ComputedBase>().pipe(
|
|
1865
|
+
projectComputed(
|
|
1866
|
+
S.Struct({ id: S.String }),
|
|
1867
|
+
// @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
|
|
1868
|
+
computed({
|
|
1869
|
+
bogus: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1870
|
+
})
|
|
1871
|
+
)
|
|
1872
|
+
)
|
|
1873
|
+
expect(() => toFilter(q, computedBaseSchema)).toThrowError(
|
|
1874
|
+
"Computed projection keys must exist in projection schema"
|
|
1875
|
+
)
|
|
1876
|
+
})
|
|
1877
|
+
|
|
1878
|
+
// ---------------------------------------------------------------------------
|
|
1879
|
+
// memFilter: code filter (where/and/or/scopes) execution coverage
|
|
1880
|
+
// ---------------------------------------------------------------------------
|
|
1881
|
+
|
|
1882
|
+
type CFRow = {
|
|
1883
|
+
readonly id: string
|
|
1884
|
+
readonly tag: "x" | "y" | "z"
|
|
1885
|
+
readonly qty: number
|
|
1886
|
+
readonly desc: string
|
|
1887
|
+
readonly tags: ReadonlyArray<string>
|
|
1888
|
+
readonly nested: { readonly kind: "k1" | "k2"; readonly v: number }
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
const cfRows: CFRow[] = [
|
|
1892
|
+
{ id: "1", tag: "x", qty: 10, desc: "Hello World", tags: ["red", "green"], nested: { kind: "k1", v: 1 } },
|
|
1893
|
+
{ id: "2", tag: "y", qty: 20, desc: "Goodbye", tags: ["blue"], nested: { kind: "k2", v: 5 } },
|
|
1894
|
+
{ id: "3", tag: "z", qty: 0, desc: "Hello again", tags: ["red", "blue", "green"], nested: { kind: "k1", v: 9 } },
|
|
1895
|
+
{ id: "4", tag: "y", qty: 30, desc: "World cup", tags: [], nested: { kind: "k2", v: 0 } }
|
|
1896
|
+
]
|
|
1897
|
+
|
|
1898
|
+
const runCF = (q: any) => (memFilter(toFilter(q))(cfRows) as unknown as readonly CFRow[]).map((_) => _.id)
|
|
1899
|
+
|
|
1900
|
+
it("codeFilter: where + and chain", () => {
|
|
1901
|
+
const q = make<CFRow>().pipe(
|
|
1902
|
+
where("tag", "y"),
|
|
1903
|
+
and("qty", "gt", 25)
|
|
1904
|
+
)
|
|
1905
|
+
expect(runCF(q)).toEqual(["4"])
|
|
1906
|
+
})
|
|
1907
|
+
|
|
1908
|
+
it("codeFilter: where + or chain", () => {
|
|
1909
|
+
const q = make<CFRow>().pipe(
|
|
1910
|
+
where("tag", "x"),
|
|
1911
|
+
or("tag", "z")
|
|
1912
|
+
)
|
|
1913
|
+
expect(runCF(q).sort()).toEqual(["1", "3"])
|
|
1914
|
+
})
|
|
1915
|
+
|
|
1916
|
+
it("codeFilter: nested scope precedence (a AND (b OR c))", () => {
|
|
1917
|
+
const q = make<CFRow>().pipe(
|
|
1918
|
+
where("tag", "y"),
|
|
1919
|
+
and(
|
|
1920
|
+
where("qty", "gt", 25),
|
|
1921
|
+
or("desc", "contains", "good")
|
|
1922
|
+
)
|
|
1923
|
+
)
|
|
1924
|
+
// tag=y AND (qty>25 OR desc contains "good") → row 2 (Goodbye) and row 4 (qty 30)
|
|
1925
|
+
expect(runCF(q).sort()).toEqual(["2", "4"])
|
|
1926
|
+
})
|
|
1927
|
+
|
|
1928
|
+
it("codeFilter: contains/startsWith/endsWith are case-insensitive", () => {
|
|
1929
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "contains", "WORLD"))).sort()).toEqual(["1", "4"])
|
|
1930
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "startsWith", "hello"))).sort()).toEqual(["1", "3"])
|
|
1931
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "endsWith", "AGAIN")))).toEqual(["3"])
|
|
1932
|
+
})
|
|
1933
|
+
|
|
1934
|
+
it("codeFilter: array includes / includes-any / includes-all", () => {
|
|
1935
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes", "red"))).sort()).toEqual(["1", "3"])
|
|
1936
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-any", ["blue", "green"]))).sort()).toEqual([
|
|
1937
|
+
"1",
|
|
1938
|
+
"2",
|
|
1939
|
+
"3"
|
|
1940
|
+
])
|
|
1941
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-all", ["red", "blue"])))).toEqual(["3"])
|
|
1942
|
+
})
|
|
1943
|
+
|
|
1944
|
+
it("codeFilter: in / notIn", () => {
|
|
1945
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "in", ["x", "z"]))).sort()).toEqual(["1", "3"])
|
|
1946
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "notIn", ["x", "z"]))).sort()).toEqual(["2", "4"])
|
|
1947
|
+
})
|
|
1948
|
+
|
|
1949
|
+
it("codeFilter: gt / gte / lt / lte / neq", () => {
|
|
1950
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gt", 10))).sort()).toEqual(["2", "4"])
|
|
1951
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gte", 10))).sort()).toEqual(["1", "2", "4"])
|
|
1952
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lt", 10)))).toEqual(["3"])
|
|
1953
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lte", 10))).sort()).toEqual(["1", "3"])
|
|
1954
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "neq", 0))).sort()).toEqual(["1", "2", "4"])
|
|
1955
|
+
})
|
|
1956
|
+
|
|
1957
|
+
it("codeFilter: nested path access through dot notation", () => {
|
|
1958
|
+
expect(runCF(make<CFRow>().pipe(where("nested.kind", "k1"))).sort()).toEqual(["1", "3"])
|
|
1959
|
+
expect(
|
|
1960
|
+
runCF(
|
|
1961
|
+
make<CFRow>().pipe(
|
|
1962
|
+
where("nested.kind", "k2"),
|
|
1963
|
+
and("nested.v", "gt", 0)
|
|
1964
|
+
)
|
|
1965
|
+
)
|
|
1966
|
+
)
|
|
1967
|
+
.toEqual(["2"])
|
|
1968
|
+
})
|
|
1969
|
+
|
|
1970
|
+
it("codeFilter: array length predicates", () => {
|
|
1971
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", 0)))).toEqual(["4"])
|
|
1972
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", "gte", 2))).sort()).toEqual(["1", "3"])
|
|
1973
|
+
})
|
|
1974
|
+
|
|
1975
|
+
it("codeFilter: order + skip + limit applied after filter", () => {
|
|
1976
|
+
const q = make<CFRow>().pipe(
|
|
1977
|
+
where("tag", "neq", "z"),
|
|
1978
|
+
order("qty", "DESC"),
|
|
1979
|
+
page({ skip: 1, take: 2 })
|
|
1980
|
+
)
|
|
1981
|
+
expect(runCF(q)).toEqual(["2", "1"])
|
|
1982
|
+
})
|
|
1983
|
+
|
|
1984
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1985
|
+
// memFilter: aggregate (GROUP BY) support
|
|
1986
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1987
|
+
|
|
1988
|
+
it("memFilter: agg-count-when groups rows and counts conditionally", () => {
|
|
1989
|
+
type Row = { city: string; status: string }
|
|
1990
|
+
const rows: Row[] = [
|
|
1991
|
+
{ city: "NYC", status: "active" },
|
|
1992
|
+
{ city: "NYC", status: "inactive" },
|
|
1993
|
+
{ city: "NYC", status: "active" },
|
|
1994
|
+
{ city: "LA", status: "active" }
|
|
1995
|
+
]
|
|
1996
|
+
const result = memFilter<Row>({
|
|
1997
|
+
t: {} as Row,
|
|
1998
|
+
select: [
|
|
1999
|
+
{ key: "city", path: "city" },
|
|
2000
|
+
{
|
|
2001
|
+
key: "activeCount",
|
|
2002
|
+
aggregate: {
|
|
2003
|
+
_tag: "agg-count-when",
|
|
2004
|
+
filter: [{ t: "where", path: "status", op: "eq", value: "active" }]
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
{ key: "total", aggregate: { _tag: "agg-count" } }
|
|
2008
|
+
] as any
|
|
2009
|
+
})(rows as any) as any[]
|
|
2010
|
+
|
|
2011
|
+
expect(result.length).toBe(2)
|
|
2012
|
+
const nyc = result.find((r: any) => r.city === "NYC")!
|
|
2013
|
+
expect(nyc.activeCount).toBe(2)
|
|
2014
|
+
expect(nyc.total).toBe(3)
|
|
2015
|
+
const la = result.find((r: any) => r.city === "LA")!
|
|
2016
|
+
expect(la.activeCount).toBe(1)
|
|
2017
|
+
expect(la.total).toBe(1)
|
|
2018
|
+
})
|
|
2019
|
+
|
|
2020
|
+
it("memFilter: agg-sum / agg-min / agg-max aggregate numerics", () => {
|
|
2021
|
+
type Row = { dept: string; salary: number }
|
|
2022
|
+
const rows: Row[] = [
|
|
2023
|
+
{ dept: "eng", salary: 100 },
|
|
2024
|
+
{ dept: "eng", salary: 200 },
|
|
2025
|
+
{ dept: "hr", salary: 50 }
|
|
2026
|
+
]
|
|
2027
|
+
const result = memFilter<Row>({
|
|
2028
|
+
t: {} as Row,
|
|
2029
|
+
select: [
|
|
2030
|
+
{ key: "dept", path: "dept" },
|
|
2031
|
+
{ key: "total", aggregate: { _tag: "agg-sum", field: "salary" } },
|
|
2032
|
+
{ key: "min", aggregate: { _tag: "agg-min", field: "salary" } },
|
|
2033
|
+
{ key: "max", aggregate: { _tag: "agg-max", field: "salary" } }
|
|
2034
|
+
] as any
|
|
2035
|
+
})(rows as any) as any[]
|
|
2036
|
+
|
|
2037
|
+
expect(result.length).toBe(2)
|
|
2038
|
+
const eng = result.find((r: any) => r.dept === "eng")!
|
|
2039
|
+
expect(eng.total).toBe(300)
|
|
2040
|
+
expect(eng.min).toBe(100)
|
|
2041
|
+
expect(eng.max).toBe(200)
|
|
2042
|
+
const hr = result.find((r: any) => r.dept === "hr")!
|
|
2043
|
+
expect(hr.total).toBe(50)
|
|
2044
|
+
})
|
|
2045
|
+
|
|
2046
|
+
it("memFilter: aggregate with nested path grouping", () => {
|
|
2047
|
+
type Row = { address: { city: string }; value: number }
|
|
2048
|
+
const rows: Row[] = [
|
|
2049
|
+
{ address: { city: "NYC" }, value: 10 },
|
|
2050
|
+
{ address: { city: "NYC" }, value: 20 },
|
|
2051
|
+
{ address: { city: "LA" }, value: 5 }
|
|
2052
|
+
]
|
|
2053
|
+
const result = memFilter<Row>({
|
|
2054
|
+
t: {} as Row,
|
|
2055
|
+
select: [
|
|
2056
|
+
{ key: "city", path: "address.city" },
|
|
2057
|
+
{ key: "count", aggregate: { _tag: "agg-count" } }
|
|
2058
|
+
] as any
|
|
2059
|
+
})(rows as any) as any[]
|
|
2060
|
+
|
|
2061
|
+
expect(result.length).toBe(2)
|
|
2062
|
+
expect(result.find((r: any) => r.city === "NYC")!.count).toBe(2)
|
|
2063
|
+
expect(result.find((r: any) => r.city === "LA")!.count).toBe(1)
|
|
2064
|
+
})
|